React Hooks用法詳解(二)

React Hooks

在了解React Hooks之前, 我們先看一下 Class函數(shù)式 的一般寫法

Class組件 一般寫法

import React from "react";
import ReactDOM from "react-dom";
import PropTypes from "prop-types";

class App extends React.Component {
  // 默認(rèn)參數(shù)
  static defaultProps = {
    message: "Hello World",
    firstName: 'Yang',
    lastName: 'yu'
  };

  // 傳參檢查
  static propTypes = {
    message: PropTypes.string
  };

  /**
   * 方便調(diào)試
   */
  static displayName = "App";

  // 狀態(tài)
  state = {
    count: 1
  };

  /**
   * 解決綁定 this 的幾種方法
   * 1. onClick={this.increment.bind(this)}
   * 2. 在 constructor中綁定 tihs ==> this.increment = this.increment.bind(this)
   * 3 箭頭函數(shù): 浪費(fèi)內(nèi)存
   */
  increment = () => {
    this.setState({
      count: 2
    });
  };

  // 計(jì)算屬性
  get name() {
    return this.props.firstName + this.props.lastName
  }

  // 生命周期
  componentDidMount() {}

  render() {
    return (
      <div>
        <div>{this.props.message}</div>
        <div>{this.state.count}</div>
        <button onClick={this.increment}>點(diǎn)擊</button>
        <div>{this.name}</div>
      </div>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

函數(shù)組件一般寫法

import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";

interface Props {
  message?: string;
}

const App: React.FunctionComponent<Props> = props => {
  /**
   * 如何使用 state,  React版本 > 16.8
   * useState 返回一個(gè)數(shù)組, 解構(gòu)
   */
  const [count, setCount] = useState(0);
  const [count1, setCount1] = useState(0);

  const increment = () => {
    setCount(count + 1);
  };

  const increment1 = () => {
    setCount1(count1 + 1);
  };

  /**
   * 生命周期 代替 componetDidMount componentDidUpdate
   */
  useEffect(() => {
    console.log('componentDidMount() 或者 componentDidUpdate()')
  })

  // 第二個(gè)參數(shù)是 []
  useEffect(() => {
    console.log('只在第一次執(zhí)行')
    // axios.get()
  }, [])
  
  // 當(dāng)某個(gè) 參數(shù)更新, 才觸發(fā)
  useEffect(() => {
    console.log('count 更新了之后執(zhí)行, 點(diǎn)擊 count1 不會(huì)更新')
  }, [count])

  useEffect(() => {
    return () => {
      console.log('我死了')
    }
  })

  return (
    <div>
      <div>{props.message}</div>
      <div>{count}</div>
      <div>{count1}</div>

      <button onClick={increment}>按鈕</button>
      <button onClick={increment1}>按鈕1</button>
    </div>
  );
};

App.defaultProps = {
  message: 'Hello World'
}
App.displayName = 'yym'

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);


下面我們來看看一些 Hooks API

useState

  • 使用

    const [n, setN] = React.useState(0)
    const [user, setUser] = React.useState({name: 'yym'})
    
  • 不可局部更新

    • 因?yàn)?code>useState不會(huì)幫我們合并屬性

      setUser({
          ...user,
          name: 'yym1'
      })
      
  • 地址會(huì)變

    • setState(obj) 如果 obj 地址不變, 那么 React 認(rèn)為數(shù)據(jù)沒有變化
  • 接收函數(shù)

    const [user, setUser] = useState(() => ({name: 'yym2', age: 12})) // 減少多余計(jì)算過程
    
    setN(i => i + 1)
    setN(i => i + 1)  // 對(duì)  state 多次操作,  
    

簡(jiǎn)單實(shí)現(xiàn) useState

下面代碼可以放到 codesandbox 里面跑一下

看一個(gè)簡(jiǎn)單的例子

const App = () => {
  const [n, setN] = useState(0);
  return (
    <div>
      <div>{n}</div>
      <button onClick={() => setN(n + 1)}>incrementN</button>
    </div>
  );
};
ReactDOM.render(<App />, document.querySelector("#root"))

1. 進(jìn)入頁面, render App, 調(diào)用 App() , n = 0
2. 用戶點(diǎn)擊 button, 調(diào)用 setN(n + 1), 再次 render App, 得到虛擬 DOM, DOM diff 更新真 DOM
3. 每次調(diào)用 App, 都會(huì)運(yùn)行 useState(0), n 的值都會(huì)改變 


QA:
1. 執(zhí)行 setN 的時(shí)候會(huì)發(fā)生什么? n 會(huì)變嗎? App() 會(huì)重新執(zhí)行嗎?
  重新渲染; setN要把n改變, n 不變; App 會(huì)重新執(zhí)行;
2. 如果 App() 會(huì)重新執(zhí)行, 那么 useState(0) 的時(shí)候, n 每次值會(huì)不同
  n 的值會(huì)不同

上面的例子我們可以分析:

  • setN
    • setN 一定會(huì)修改數(shù)據(jù) X, 將n + 1 存入 X, (X 泛指)
    • setN一定會(huì)觸發(fā)<App />, 重新渲染
  • useState
    • useState 會(huì)從 X 讀取 n 的最新值
  • X
    • 每個(gè)組件有自己的數(shù)據(jù) X, 我們將其命名為state

根據(jù)上面的結(jié)論, 我們嘗試寫一下 useState

import React from "react";
import ReactDOM from "react-dom";

let _state: any;
const myUseState = (initialValue: any) => {
  _state = _state === undefined ? initialValue : _state;
  const setState = (newState: any) => {
    _state = newState;
    // 渲染頁面
    render();
  };
  return [_state, setState];
};

// 不管這個(gè)實(shí)現(xiàn)
const render = () => ReactDOM.render(<App />, document.querySelector("#root"));

const App = () => {
  console.log("App 渲染了");
  
  // 使用自己寫的 myUseState
  const [n, setN] = myUseState(0);
  return (
    <div>
      <div>{n}</div>
      <button onClick={() => setN(n + 1)}>incrementN</button>
    </div>
  );
};

export default App;

上面的代碼完全可以正常運(yùn)行, 但當(dāng)我們的 myUseState 有兩個(gè)的時(shí)候, 就是下面的代碼

import React from "react";
import ReactDOM from "react-dom";

let _state: any;
const myUseState = (initialValue: any) => {
  _state = _state === undefined ? initialValue : _state;
  const setState = (newState: any) => {
    _state = newState;
    // 渲染頁面
    render();
  };

  return [_state, setState];
};

// 不管這個(gè)實(shí)現(xiàn)
const render = () => ReactDOM.render(<App />, document.querySelector("#root" ));

const App = () => {
  console.log("App 渲染了");
  const [n, setN] = myUseState(0);
  const [m, setM] = myUseState(1);
  return (
    <div>
      <div>
        {n}-{m}
      </div>
      <button onClick={() => setN(n + 1)}>incrementN</button>
      <button onClick={() => setM(m + 1)}>incrementM</button>
    </div>
  );
};

export default App;

我們運(yùn)行上面的代碼, 發(fā)現(xiàn)更新 n, m 也會(huì)改變, 更新m, n 也會(huì)改變, 因?yàn)樗械臄?shù)據(jù)都放在 _state 會(huì)沖突, 該怎么解決?

  1. _state 做成對(duì)象

    _state = { m: 0, n: 0}, // 感覺不行,  useState(0) 并不知道變量叫 m 還是 n, 
    
  2. _state 做成數(shù)組, 嘗試一下

    _state = [0, 0]
    
    // 把 _state 初始為數(shù)組
    let _state: any[] = [];
    // 下標(biāo)
    let index = 0;
    
    const myUseState = (initialValue: any) => {
      // 設(shè)置唯一的值, 根據(jù) myUseState 的順序
      const currentIndex = index;
      _state[currentIndex] =
        _state[currentIndex] === undefined ? initialValue : _state[currentIndex];
      const setState = (newState: any) => {
        _state[currentIndex] = newState;
        // 渲染頁面
        render();
      };
      index += 1;
      return [_state[currentIndex], setState];
    };
    
    const render = () => {
      // 每次重新渲染 index 重置
      index = 0;
      ReactDOM.render(<App />, document.querySelector("#root"));
    };
    
  3. 上面數(shù)組的方法會(huì)有一定的缺點(diǎn)

    • useState的調(diào)用順序
      • 若第一次順序是 n m K
      • 第二次必須保證順序一致,
      • useState 不能寫在 if
      const [n, setN] = React.useState(0);
      let m, setM;
      if (n % 2 === 1) {
        [m, setM] = React.useState(0);
      }
    
    // 會(huì)報(bào)錯(cuò) 順序不能變
    React Hook "React.useState" is called conditionally. React Hooks must be called in the exact same order in every component render. (react-hooks/rules-of-hooks)eslint
    

代碼在這里, 我們想一下 mpUseState 還有什么問題呢?

  1. App 組件用了 _stateindex , 那其它組件用什么?
    • 給每個(gè)組件創(chuàng)建一個(gè)_stateindex
  2. 放在全局作用域重名了怎么辦?
    • 放在組件對(duì)應(yīng)的虛擬節(jié)點(diǎn)對(duì)象上

總結(jié) :

  1. 每個(gè)函數(shù)組件對(duì)應(yīng)一個(gè) React 節(jié)點(diǎn),

  2. 每個(gè)節(jié)點(diǎn)保存著 _stateidnex

  3. useState 會(huì)讀取 state[index]

  4. indexuseState 出現(xiàn)順序決定

  5. setState 會(huì)修改 state, 并觸發(fā)更新

上面的屬于簡(jiǎn)化 useState 的實(shí)現(xiàn), 我們看一個(gè) useState 出現(xiàn)問題的 代碼

// 先點(diǎn)擊 log , 再點(diǎn)擊 incremwntN,  3s 后打印的是 n: 0, 而不是 n: 1
// 先點(diǎn)擊 incrementN, 再點(diǎn)擊 log, 3s 后 打印 n: 1

// ? 為啥是舊數(shù)據(jù)
1. setN 不會(huì)改變 n, 生成一個(gè) n 的分身, 改變的不是n, 所以 n: 0, 

import React, { useState } from "react";

const App = () => {
  const [n, setN] = useState(0);
  const log = () =>
    setTimeout(() => {
      console.log(`n: ${n}`);
    }, 3000);
  return (
    <div>
      <div>{n}</div>
      <button onClick={() => setN(n + 1)}>incrementN</button>
      <button onClick={log}>log</button>
    </div>
  );
};

export default App;

如何讓狀態(tài)始終只是一個(gè)?

  • 全局變量

    • window.xxx即可
  • useRef

    const App = () => {
      const nRef = React.useRef(0); // { current: 0}
      const log = () =>
        setTimeout(() => {
          console.log(`n: ${nRef.current}`);
        }, 3000);
      return (
        <div>
          <div>{nRef.current} 不是實(shí)時(shí)刷新</div>
          <button onClick={() => (nRef.current += 1)}>incrementN</button>
          <button onClick={log}>log</button>
        </div>
      );
    };
    
    
    // 手動(dòng)觸發(fā) App 更新, 強(qiáng)制, 更類似于 Vue3
    const App = () => {
      const nRef = React.useRef(0); // { current: 0}
      const log = () =>
        setTimeout(() => {
          console.log(`n: ${nRef.current}`);
        }, 3000);
      return (
        <div>
          <div>{nRef.current}</div>
          <button onClick={() => (nRef.current += 1)}>incrementN</button>
          <button onClick={log}>log</button>
        </div>
      );
    };
    
  • useContext

    • useContext 不僅能貫穿始終, 還能貫穿不同組件

      // 上下文
      const themeContext = createContext(null);
      
      const App = () => {
        const [theme, setTheme] = useState("blue");
        return (
          // Provider 類似于作用域, 里面的可以使用 theme, settheme
          <themeContext.Provider value={{ theme, setTheme }}>
            <div style={{ backgroundColor: theme === "red" ? "red" : "blue" }}>
              <p>{theme}</p>
              <div>
                <ChildA />
              </div>
              <div>
                <ChildB />
              </div>
            </div>
          </themeContext.Provider>
        );
      };
      
      const ChildA = () => {
        const { setTheme } = useContext(themeContext);
        return (
          <div>
            <button onClick={() => setTheme("red")}>red</button>
          </div>
        );
      };
      
      const ChildB = () => {
        const { setTheme } = useContext(themeContext);
        return (
          <div>
            <button onClick={() => setTheme("blue")}>blue</button>
          </div>
        );
      };
      

useReducer

使用方法:

  • useReducer 是復(fù)雜點(diǎn)的 useState
import React, { useReducer } from "react";
// 1. 創(chuàng)建初始值
const initial = {
  n: 0
};
// 2. 創(chuàng)建所有操作 reducer(state, action)
const reducer = (state: any, action: any) => {
  if (action.type === "add") {
    return { n: state.n + action.number };
  } else if (action.type === "multiple") {
    return { n: state.n * 2 };
  } else {
    throw new Error("unknown word");
  }
};

const App = () => {
  // 3. 傳給 useReducer  得到讀和寫 API
  const [state, dispatch] = useReducer(reducer, initial);

  const onClick = () => {
    // 4. 寫
    dispatch({ type: "add", number: 1 });
  };

  const onClick1 = () => {
    dispatch({ type: "add", number: 2 });
  };

  return (
    <div>
      {/* 4. 讀取值 */}
      <h1>n: {state.n}</h1>

      <button onClick={onClick}>add+1</button>
      <button onClick={onClick1}>add+2</button>
    </div>
  );
};

export default App;

但如何代替 Redux

我們先弄一個(gè)初始的頁面, 在里面一步步實(shí)現(xiàn) Redux 功能, 示例Demo

// 這是我們初始頁面, 有三個(gè)組件 User Books Movies
import React, { useReducer } from "react";
const App = () => {
  return (
    <div>
      <User />
      <hr />
      <Books />
      <Movies />
    </div>
  );
};
const User = () => {
  return (
    <div>
      <h1>個(gè)人信息</h1>
    </div>
  );
};
export default App;

  1. 先把數(shù)據(jù)集中到 store

    // 代碼和初始一樣, 只是往里面加代碼
    import React, { useReducer } from "react";
    
    const store = {
      user: null,
      books: null,
      movies: null,
    }
    
  2. 創(chuàng)建 reducer, 使用 usereducer 一樣的

    const reducer = (state, action) => {
      switch (action.type) {
        case "setUser":
          return { ...state, user: action.user };
        case "setBooks":
          return { ...state, books: action.books };
        case "setMovies":
          return { ...state, movies: action.movies };
        default:
          throw new Error();
      }
    };
    
  1. 我們使用 createContext, 創(chuàng)建一個(gè)上下文

    // 創(chuàng)建一個(gè) context
    const Context = createContext(null);
    
  2. 使用useReducer 并把 useReducer 的讀寫 API, 放進(jìn) Context Value

    const App = () => {
      // 使用 useReducer
      const [state, dispatch] = useReducer(reducer, store);
      return (
        // 把 useReducer 的讀寫 API 放到 Context value里
        <Context.Provider value={{ state, dispatch }}>
          <User />
          <hr />
          <Books />
          <Movies />
        </Context.Provider>
      );
    };
    
    
  3. 經(jīng)過前四步, 我們就可以在在 User, Books, Movie 的組件中使用 Context.Providevalue

    const User = () => {
      // 使用 useContext
      const { state, dispatch } = useContext(Context);
     
      // 異步請(qǐng)求值
      useEffect(() => {
        axios.get("/user").then((user: any) => {
          dispatch({ type: "setUser", user });
        });
      }, []);
    
      return (
        <div>
          <h1>個(gè)人信息</h1>
          <div>name: {state.user ? state.user.name : ""}</div>
        </div>
      );
    };
    
    
  4. 所以可以帶到一個(gè)簡(jiǎn)單的 Redux 使用,

    // 所有代碼
    import React, { useReducer, createContext, useContext, useEffect } from "react";
    
    // store
    const store = {
      user: null,
      books: null,
      movies: null
    };
    
    // reducer
    const reducer = (state, action) => {
      switch (action.type) {
        case "setUser":
          return { ...state, user: action.user };
        case "setBooks":
          return { ...state, books: action.books };
        case "setMovies":
          return { ...state, movies: action.movies };
        default:
          throw new Error();
      }
    };
    
    // 創(chuàng)建一個(gè) context
    const Context = createContext(null);
    
    const App = () => {
      // 使用 useReducer
      const [state, dispatch] = useReducer(reducer, store);
      return (
        // 把 useReducer 的讀寫 API 放到 Context value里
        <Context.Provider value={{ state, dispatch }}>
          <User />
        </Context.Provider>
      );
    };
    
    // user 可以使用 store 的值
    const User = () => {
      const { state, dispatch } = useContext(Context);
    
      useEffect(() => {
        axios.get("/user").then((user: any) => {
          dispatch({ type: "setUser", user });
        });
      }, []);
    
      return (
        <div>
          <h1>個(gè)人信息</h1>
          <div>name: {state.user ? state.user.name : ""}</div>
        </div>
      );
    };
    
    export default App;
    
    

當(dāng)一個(gè)項(xiàng)目比較大, 用到的組件比較多, 使用到的 reducer 比較多, 我們可以模塊化

  • user, books, movies 單獨(dú)建一個(gè)文件

  • 把對(duì)應(yīng)的東西 export (default) 出去

    // 例: 重構(gòu) reducer
    // reducer
    const reducer = (state, action) => {
      switch (action.type) {
        case "setUser":
          return { ...state, user: action.user };
        case "setBooks":
          return { ...state, books: action.books };
        case "setMovies":
          return { ...state, movies: action.movies };
        default:
          throw new Error();
      }
    };
    
    // ==>
    
    const obj = {
      'setUser': (state, action) => {
        return { ...state, user: action.user };
      },
      // ...
    }
    
    const reducer_copy = (state, action) => {
      const fn = obj[action.type]
      if(fn) {
        return fn(state, action)
      } else {
        throw new Error('type 錯(cuò)誤')
      }
    }
    
    
    // user_reducer.js
    export default {
      'setUser': (state, action) => {
        return { ...state, user: action.user };
      },
    }
    
    // books_reducer.js
    // ...
    
    // idnex.js
    import userReducer from './reducers/user_reducer'
    
    const obj = {
      ...userReducer,
      ...otherReducer
    }
    

useContext

useContext 改變一個(gè)數(shù)的時(shí)候, 是通過自頂向下, 逐級(jí)更新數(shù)據(jù)做到的

上下文

  • 全局變量是全局的上下文
  • 上下文是局部的全局變量

在上面的 useReducer 中我們也是用了 useContext,

import React, { createContext, useState, useContext } from "react";

// 1. 創(chuàng)建一個(gè) context 上下文
const Context = createContext(null);

function App() {
  console.log("App 執(zhí)行了");
  const [n, setN] = useState(0);
  return (
    // 2. 使用 Context.Provide 弄一個(gè) 作用域 value 值
    <Context.Provider value={{ n, setN }}>
       <div className="App">
         <Parent />
       </div>
    </Context.Provider>
  );
}

function Parent() {
  // 3. 作用域內(nèi)使用 useContext
  const { n, setN } = useContext(Context);
  return (
    <div>
      我是爸爸 n: {n} <Child />
    </div>
  );
}

function Child() {
  const { n, setN } = useContext(Context);
  const onClick = () => {
    setN(i => i + 1);
  };
  return (
    <div>
      我是兒子 我得到的 n: {n}
      <button onClick={onClick}>+1</button>
    </div>
  );
}

useEffect & useLayoutEffect

useEffect

  • 副作用: 對(duì)環(huán)境的改變就是副作用
  • 其實(shí)也可以當(dāng)做afterRender: 每次render后運(yùn)行
  1. 用途

    • 作為 componentDidMount 使用, []作為第二個(gè)參數(shù)
    • 作為componentDidUpdate使用, 可制定依賴
    • 作為componentWillUnMount 使用, 通過 return
    • 以上三種可以同時(shí)存在
  2. 特點(diǎn)

    • 如果同時(shí)存在多個(gè) useEffect, 會(huì)按照出現(xiàn)次序執(zhí)行
  3. Demo

    import React, { useState, useEffect } from "react";
    
    const App = () => {
      const [n, setN] = useState(0);
      const [m, setM] = useState(0);
      const onClick = () => {
        setN(n + 1);
      };
      const onClcik1 = () => {
        setM(m + 1);
      };
      
      // componentDidMount
      useEffect(() => {
        console.log("第一次渲染, n或m變化我也不打印了");
        document.title = "Hello";
      }, []);
    
      // componentDidUpdate
      useEffect(() => {
        console.log("第一二..N次渲染, 任何state變化我就渲染");
      });
      // componentDidUpdate
      useEffect(() => {
        console.log("第一次渲染, 并且只有m變化我再渲染");
      }, [m]);
     
      // componentDidMount & componentWillUnMount
      useEffect(() => {
        const timerId: any = setInterval(() => {
          console.log("定時(shí)器");
        }, 1000);
        return () => {
          window.clearInterval(timerId);
        };
      });
      return (
        <div>
          n: {n}
          <button onClick={onClick}>+n</button>
          <hr />
          m: {m}
          <button onClick={onClcik1}>+m</button>
        </div>
      );
    };
    
    export default App;
    
    

useLayoutEffect

  • 其函數(shù)簽名與 useEffect 相同,但它會(huì)在所有的 DOM 變更之后同步調(diào)用 effect??梢允褂盟鼇碜x取 DOM 布局并同步觸發(fā)重渲染。在瀏覽器執(zhí)行繪制之前,useLayoutEffect 內(nèi)部的更新計(jì)劃將被同步刷新。
  • useEffect在瀏覽器渲染完成后執(zhí)行
  • useLayout 在瀏覽器渲染前執(zhí)行

需要截圖 老是的白板

  1. 特點(diǎn)
    • useLayoutEffect總是比 useEffect 先執(zhí)行 (上面說useEffect按次序執(zhí)行, 如果有useLayoutEffect, 先執(zhí)行)
    • useLayoutEffect 里的任務(wù)最好影響了 Layout
    • 盡可能使用標(biāo)準(zhǔn)的 useEffect 以避免阻塞視覺更新
    • 優(yōu)先使用useEffect

useMemo & useCallback

  1. 在了解 React.useMemo 之前, 我們先來了解一下 React.memo, 直接看代碼

    • 我們平常寫React, 經(jīng)常會(huì)有多余的render
    • React.memo 幫助我們控制何時(shí)重新渲染組件, 可以和函數(shù)式組件一起使用
    // 這是一個(gè)普通的父子組件, 子組件使用了父組件的 state
    const App = () => {
      const [n, setN] = useState(0);
      const [m, setM] = useState(0);
      const onClick = () => {
        setN(n + 1)
      }
      return (
        <div>
          <button onClick={onClick}>update: {n}</button>
          <Child data={m} />
        </div>
    };
    
    const Child = (props: any) => {
      console.log('child 執(zhí)行了');
      // TODO: 大量代碼
      return <div>child: {props.data}</div>
    }
    
    
    • 上面的代碼我每次更新 n , 在控制臺(tái)看到會(huì)更新 Child組件, 但是我們的 Child 組件, 只依賴m, 但 m 沒有變化, 我們不希望Child 執(zhí)行
    • props 不變, 沒必要執(zhí)行函數(shù)組件
    • 但我們?cè)趺磧?yōu)化呢? React.memo
    // 上面的代碼改變一下
    
    // 使用 React.memo 包裹一下, 只有當(dāng)我們改變 m 的時(shí)候, Child 才會(huì)執(zhí)行
    const Child = React.memo((props: any) => {
      console.log("child 執(zhí)行了");
      // TODO: 大量代碼
      return <div>child: {props.data}</div>;
    });
    
    • React.memo 使得一個(gè)組件只有在它的 props變化在執(zhí)行一遍, 再次執(zhí)行
  1. 但是React.memo 有一個(gè)問題: 添加監(jiān)聽函數(shù), 還是會(huì)執(zhí)行 Child, 看代碼

    const App = () => {
      const [n, setN] = useState(0);
      // 每次執(zhí)行 m = 0, 值是一樣的
      const [m, setM] = useState(0);
      const onClick = () => {
        // n 變化不會(huì)執(zhí)行 Child
        setN(n + 1);
      };
      
      // 每次App 執(zhí)行, 都會(huì)重新執(zhí)行
      // 之前是一個(gè)空函數(shù), 現(xiàn)在是另一個(gè)空函數(shù), 不是同一個(gè)空函數(shù), 引用類型, 地址不同
      const onClickChild = () => {};
      return (
        <div>
          <button onClick={onClick}>update: {n}</button>
          {/* 傳一個(gè)函數(shù) */}
          <Child data={m} onClick={onClickChild} />
        </div>
      );
    };
    
    const Child = React.memo((props: any) => {
      console.log("child 執(zhí)行了");
      // TODO: 大量代碼
      // 在這里添加了 onClick事件
      return <div onClick={props.onClick}>child: {props.data}</div>;
    });
    
    • 怎么解決呢? react.useMemo
  1. React.useMemo

    • 如何使用?

      // render 時(shí): 先根據(jù)[name]里面的 name判斷, 因?yàn)?useMemo 作為一個(gè)有著暫存能力, 暫存了上一次的結(jié)果
      // 對(duì)比上一次 name, name值不變, data就不重新賦值成新的對(duì)象, 沒有新的對(duì)象, 沒有新的內(nèi)存地址, 就不會(huì)
      // 重新渲染
      
      const data = useMemo(()=>{
        return {}
      },[m, n])
      
      // 1. 第一個(gè)參數(shù) () => value
      // 2. 第二個(gè)參數(shù)依賴 [m, n]
      // 3. 當(dāng)依賴變化, 重新計(jì)算新的 value
      // 4. 以來不變, 使用之前的 value
      
      //函數(shù)
      const data = useMemo(() => {
        return () => {}
      })
      
    • 上面使用 React.memo 改造成React.useMemo

      const App = () => {
        const [n, setN] = useState(0);
        const [m, setM] = useState(0);
        const onClick = () => {
          setN(n + 1);
        };
        
        // useMemo
        const onClickChild = useMemo(() => {
          return () => {};
        }, [m]);
        return (
          <div>
            <button onClick={onClick}>update: {n}</button>
            {/* dlkfs */}
            <Child data={m} onClick={onClickChild} />
          </div>
        );
      };
      
      const Child = React.memo((props: any) => {
        console.log("child 執(zhí)行了");
        // TODO: 大量代碼
        return <div onClick={props.onClick}>child: {props.data}</div>;
      });
      
  1. 有點(diǎn)難用, 于是有了 useCallback, 作用和 useMemo 一樣, 是useMemo的語法糖

    • 用法

      useCallback( () => { callback }, [input],)
      
      // 等價(jià)于
      
      useMemo(() => () => { callback }, [input])
      
      const onClickChild = useMemo(() => {
        return () => {};
      }, [m]);
      
      const onClickChild = useCallback(() => {}, [m]);
      

useRef

  1. 使用

    目的: 
    1. 如果需要一個(gè)值, 在組建不斷 render 時(shí)保持不變
    使用:
    1. 初始化 => const count = useRef(0)
    2. 讀取: count.current
    
    const App = () => {
      const [n, setN] = useState(0);
      // 初始化一個(gè)值
      const count = useRef(0);
      const onClick = () => {
        setN(n + 1);
      };
    
      useEffect(() => {
        // 通過 .current 來讀取, 更新一次 +1
        count.current += 1;
        console.log(count.current);
      });
      return (
        <div>
          <button onClick={onClick}>update: {n}</button>
        </div>
      );
    };
    
  2. 為什么需要current ?

    • 為了保證兩次 useRef 是同一個(gè)值 (只有引用能做到)
  3. 做幾個(gè) Hook 對(duì)比

    • useState/useReducer 每次都變
    • useMemo/useCallback 依賴變化才變
    • useRef 永遠(yuǎn)不變
  4. 對(duì)比 Vue3 的 ref

    // vue
    <template>
     <div @click="increment">
        ref會(huì)更新: {count}
      </div>
    </template>
    <script>
      import  { ref } from 'vue'
     export default {
        setup() {  
          // 直接使用 ref
          const count = ref(0)
             const increment = () => {
            count.value++
          }
          return { count, increment}
        }
      }
    </script>
    
    // react 的 ref 不會(huì)自動(dòng)更新ui 
    const App = () => {
      const [n, setN] = useState(0);
      const count = useRef(0);
      const onClick = () => {
        setN(n + 1);
      };
    
      const onClick2 = () => {
        count.current += 1;
        // 會(huì)改變值 +1
        console.log(count.current, "count...");
      };
    
      return (
        <div>
          <button onClick={onClick}>update: {n}</button>
          {/* 頁面沒有更新 */}
          <button onClick={onClick2}>update count: {count.current}</button>
        </div>
      );
    };
    
    // 在 實(shí)現(xiàn) useState 中我們有如何手動(dòng)刷新頁面的方法, 使用 useState
    

forwardRef & useImperativeHandle

  1. 簡(jiǎn)單看個(gè)例子

    const App = () => {
      const buttonRef = useRef(null);
      return (
        <div>
          {/* 我們想要獲取子組件的 DOM 引用 */}
          <ButtonComponent ref={buttonRef}>按鈕</ButtonComponent>
        </div>
      );
    };
    
    const ButtonComponent = (props: any) => {
      console.log(props, 'props....')  // {children: "按鈕"} 沒有得到 ref
      return <button className="red" {...props} />;
    };
    
    // warning:  Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
    
    // 上面的報(bào)錯(cuò), 函數(shù)組件不支持 ref, 可以使用 React.forwardRef()
    
  1. 使用forwardRef

    // 函數(shù)組件希望接收別的傳來的 ref, 需要 forwardRef 包起來
    // forwardRef 只是多加了一個(gè) ref 參數(shù)
    // 改造上面的
    const App = () => {
      const buttonRef = useRef(null);
      return (
        <div>
          <Button2 ref={buttonRef}>按鈕</Button2>
        </div>
      );
    };
    
    // forwardRed 包裹
    const Button2 = forwardRef((props, ref) => {
      return <button className="red" ref={ref} {...props} />;
    });
    
  1. 總結(jié):

    useRef: 
    1. 可以用來引用 DOM 對(duì)象
    2. 也可以用來引用普通對(duì)象
    
    forwardRef
    1. 函數(shù)式組件 由于 props 不包含 ref, 所以需要 forwardRef
    
  1. forwardRef 相關(guān)的 useImperativeHandle, 減少暴露給父組件的屬性

    // 官方例子:
    function FancyInput(props, ref) {
      const inputRef = useRef();
      
      // 可以自定義暴露出去的實(shí)例值
      // 其實(shí)可以叫做 setRef
      useImperativeHandle(ref, () => ({
        focus: () => {
          inputRef.current.focus();
        }
      }));
      return <input ref={inputRef} ... />;
    }
    FancyInput = forwardRef(FancyInput);
    

自定義 Hook

  • 自定義 Hook 是一個(gè)函數(shù),其名稱以 “use” 開頭,函數(shù)內(nèi)部可以調(diào)用其他的 Hook
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容