深刻理解 React Hooks

HOOKS

HookReact 16.8 中的新增功能。它們容許您在不編寫類的狀況下使用狀態和其餘 React 功能。HOOKS 只能在函數組件中使用react

memo

React.memo 是一個高階的組件。它相似於 React.PureComponent 也就是說若是組件的 props 沒有改變,是不會被從新渲染的。git

function Foo (props) {
    ...
  }
  export default React.memo(Foo)

複製代碼

React.PureComponent 如何實現性能優化的

咱們都知道類組件的中有個 shouldComponentUpdate 生命週期函數。當這個函數返回 false 時,表示組件不會從新渲染;當返回 true 時,表示組件會從新渲染。PureComponent 組件就是在 shouldComponentUpdate 函數中對組件接受的 props 進行比較(propsnextProps 進行比較)若是發生變化就返回 true。同時還有自身的 state 數據也會進行一個比較(statenextState),若是發生變化就返回 true。若是上面兩種數據的個自比較都返回 false,那麼組件就不會發生渲染,減小沒必要要的渲染,從而達到性能的優化。推薦一篇文章React PureComponent 源碼解析你們本身看看github

memo 同 React.PureComponent 有什麼區別

React.memo 爲高階組件。它與 React.PureComponent 很是類似,但它適用於函數組件,但不適用於 class 組件。若是你的函數組件接受了一個徹底相同的 props 那麼 memo 就不會使函數組件發生從新渲染。可是 memo 並不會對函數組件自身的 state 數據進行一個淺比較(useReduceruseState 鉤子返回的數據)。因此二者在做用上仍是有一些區別的。segmentfault

useState

相似於類組件中的state,不一樣的是 useState 接受一個任意類型的值 string, array, object, bool... 做爲參數並返回一個數組,且 useState 只會在組件初始化的時候執行數組

// 初始化的時候,age的值就是useState中參數的值
  const [ age, setAge ] = useState(20);
  const [ visible, setVisible ] = useState(props.visible);

複製代碼

數組中的第一個元素是狀態值,組件在運行過程當中會保留這個狀態值,相似於 this.state 數組中的第二個元素是改變這個狀體值的函數,相似於 this.setState()性能優化

function Hooks(props) {
    const [ age, setAge ] = useState(20);
    const [ visible, setVisible ] = useState(props.visible);

    return (
      <div className=""> <p>個人年齡是{age}歲</p> <button onClick={() => setAge(age + 1)}>點擊</button> <p>{`${visible}`}</p> </div>
    );
  };
複製代碼

咱們能夠在函數組件中屢次使用 useState,來建立多個狀態值供咱們使用。可是,必須在函數做用域的最頂層使用 useState,不能嵌套在循環內部或者其餘函數做用域內部或者是塊級做用域中。app

useEffect

對於使用過類組件的同窗來講,咱們能夠理解爲 useEffect 是類組件中 componentDidMountcomponentDidUpdate 兩個生命週期的一個集合。每次當函數組件掛載成功或者從新渲染完成後都會調用 useEffect 。 可是也有不一樣的地方,useEffect 不徹底同類組件中的 componentDidMountcomponentDidUpdate 生命週期函數同樣,useEffect 有延遲,在父組件 didMount 或 didUpdate 後,但在任何新渲染以前觸發。useEffect 能夠在組件中使用屢次, 和 useState 使用同樣。dom

useEffect 還能夠返回一個函數,並在組件即將銷燬時調用這個返回函數,沒錯,就是和類組件的 componentWillUnmount 同樣。咱們經過它來取消在 useEffect 中綁定的事件監聽等行爲。ide

通常狀況下,咱們能夠將一個行爲事件的綁定和取消綁定放在同一個 useEffect 中,這樣代碼的可讀性和維護行會更強一些。函數

function Hooks(props, ref) {
    const boxRef = useRef(null);

    useEffect(() => {
      function handle() {
        console.log(123456)
      }
      boxRef.current.addEventListener('click', handle, false);
      return () => {
        boxRef.current.removeEventListener('click', handle, false);
      }
    }, []);
    return (
      <div ref={boxRef}> 12344556 </div>
    );
  }
複製代碼

經過下面的這張圖,能夠看出來 useEffect 的有一個延遲

useEffect延遲執行

useEffect也能夠接收一個數組做爲第二個參數

useEffect(() => {
    document.title = `You clicked ${count} times`;
  }, [count]); // 僅在 count 更改時更新
複製代碼

上面這個示例中,咱們傳入 [count] 做爲第二個參數。這個參數是什麼做用呢?若是 count 的值是 5,並且咱們的組件重渲染的時候 count 仍是等於 5,React 將對前一次渲染的 [5] 和後一次渲染的 [5] 進行比較。由於數組中的全部元素都是相等的(5 === 5),React 會跳過這個 effect,這就實現了性能的優化。

當渲染時,若是 count 的值更新成了 6,React 將會把前一次渲染時的數組 [5] 和此次渲染的數組 [6] 中的元素進行對比。此次由於 5 !== 6,React 就會再次調用 effect。若是數組中有多個元素,即便只有一個元素髮生變化,React 也會執行 effect

若是參數中有多個元素 [ age, props.visible ] ,組件渲染時經過比較後只要有一個元素髮生變化,useEffect 就會執行。若是參數是一個空數組 [] ,那麼這個時候 useEffect 就和類組件中的 componentDidMount 同樣,只在組件掛載成功後調用一次。 useEffect 函數中 return 的函數,不受第二個參數的影響,仍在組件即將銷燬的時候調用。

其實,當第二個參數不是空的數組時,useEffect 也會在組件掛載成功後調用一次,這一點不能忘記。

不要在循環條件或嵌套函數中調用 Hook。相反,始終在 React 函數的頂層使用 Hooks。經過遵循此規則,您能夠確保每次組件呈現時都以相同的順序調用Hook 。這就是React 容許多個 useStateuseEffect 調用之間正確保留 Hook 狀態的緣由。

useLayoutEffect

useEffect 使用原理相同,可是惟一的區別在於 useLayoutEffect 不會延遲觸發,和類組件的 componentDidMountcomponentDidUpdate 這兩個生命週期函數基本處於同步的狀態。

customize hooks

自定義 Hook 是一個 JavaScript 函數,其名稱以 "use" 開頭,能夠調用其餘 Hook。構建本身的 Hook 能夠將組件邏輯提取到可重用的函數中 ,確保只在自定義 Hook 的頂層無條件地調用其餘 Hook。與 React 組件不一樣,自定義 Hook 不須要具備特定簽名。咱們能夠決定它做爲參數須要什麼,以及它應該返回什麼(若是有的話)

// useVisibleStatus 是一個自定義的鉤子,咱們在函數中調用的useEffect
  function useVisibleStatus(isShow) {
    const [ visible, setVisible ] = useState(isShow);
    useEffect(() => {
      setVisible(isShow);
    }, [ isShow ]);
    return visible;
  };

  function Hooks(props) {
    const [ count ] = useState(props.count);
    const visible = useVisibleStatus(props.visible);

    return (
      <div className=""> <h2>{count}</h2> <button onClick={() => setCount(count + 1)}>點擊</button> <h2>{`${visible} ${props.count}`}</h2> </div>
    );
  }
複製代碼

咱們也能夠將一些複雜或者重複的邏輯提取提取到自定義的 Hook 函數中,從而簡化咱們的代碼。其實自定義 hook 和函數組件沒有多大區別。

useReducer

useState 複雜的狀態邏輯涉及多個子值或下一個狀態取決於前一個狀態時,一般 useReducer 更可取。useReduce 還可讓您優化觸發深度更新的組件的性能

const initialState = {count: 0};

  function reducer(state, action) {
    switch (action.type) {
      case 'increment':
        return {count: state.count + 1};
      case 'decrement':
        return {count: state.count - 1};
      default:
        throw new Error();
    }
  }

  function Counter({initialState}) {
    const [state, dispatch] = useReducer(reducer, initialState);
    return (
      <> Count: {state.count} <button onClick={() => dispatch({type: 'increment'})}>+</button> <button onClick={() => dispatch({type: 'decrement'})}>-</button> </> ); } 複製代碼

咱們看看 useReducer 具體的實現(自定義一個 useReducer 鉤子):

function useReducer(reducer, initialState) {
    const [state, setState] = useState(initialState);

    function dispatch(action) {
      const nextState = reducer(state, action);
      setState(nextState);
    }

    return [state, dispatch];
  }
複製代碼

useImperativeHandle

能夠經過 useImperativeHandle ,給ref上綁定一些自定的事件,前提是咱們必須聯合 forwardRef 一塊兒使用,注意全部的事件都是綁定在 refcurrent 屬性上。 看下面的例子

// hook.js
  function Hooks(props, ref) {
    const [ count, setCount ] = useState(props.count);
    useImperativeHandle(ref, () => ({
      // 自定義一些事件
      click: () => {
        setCount(count + 1);
      },
    }));

    return (
      <div className=""> <h2>{count}</h2> <button onClick={() => setCount(count + 1)}>點擊</button> </div>
    );
  };
  export default React.forwardRef(Hooks);

  // Application.js
  export default class App extends PureComponent {
    componentDidMount() {
      this.ref = React.createRef();
    }

    return (
      <div onClick={() => this.ref.current.click()} > // ... <Hooks ref={this.ref} count={this.state.count} visible={this.state.visible}/> // ... </div> ); } 複製代碼

或者

function FancyInput(props, ref) {
    // 獲取真是DOM節點
    const inputRef = React.useRef();
    useImperativeHandle(ref, () => ({
      // 自定義一些事件
      focus: () => {
        // 在DOM節點執行一些操做均可以
        inputRef.current.focus();
      }
    }));
    return <input ref={inputRef} />; } FancyInput = React.forwardRef(FancyInput); 複製代碼

useRef

useRef 返回一個可變的ref對象,其 current 屬性值爲初始化傳遞的參數(initialValue)。返回的對象將持續整個組件的生命週期。和類組件中的實例屬性很像

const ref = usRef(20);
  console.log(ref.current) // 20
  // 能夠從新賦值
  ref.current = 200;

複製代碼

固然最多見的就是訪問一個元素節點

function TextInputWithFocusButton() {
    const inputEl = useRef(null);
    const onButtonClick = () => {
      // `current` points to the mounted text input element
      inputEl.current.focus();
    };
    return (
      <>
        <input ref={inputEl} type="text" />
        <button onClick={onButtonClick}>Focus the input</button>
      </>
    );
  }
複製代碼

useMemo

使用的場景:函數組件中,咱們定義了一些方法,可是咱們並不但願每次組件更新的時候都從新執行一次個函數,那麼這個時候咱們就可使用 useMemo。有個地方須要注意點那就是,useMemo 是在 useLayoutEffect 以前執行,這和類組件中的 componentWillMountcomponentWillUpdate 相似。可是若是 useMemo 所接收的條件是 props 中的相關屬性的話,那麼咱們將 useMemo 看成類組件的 componentWillReceiveProps(nextProps) 使用,能夠查看的咱們demo

// 組件初始化的時候會調用 `Func`,相似 `componentWillMount``
  // 當數組中的元素的值發生改變,那麼就會調用 `Func`,這個條件 a 和 b 有一個發生變化的時候 就會觸發 `useMemo`
  useMemo(() => Func(a, b), [a, b]);
複製代碼

在看這個帶返回值的

function Hooks(props) {
    const [ count, setCount ] = useState(props.count);
    useLayoutEffect(() => {
      console.log('useLayoutEffect 後執行');
      setCount(props.count);
    }, [ props.count ]);

    const dom = useMemo(() => {
      console.log('useMemo 優先執行');
      return <h2>{count * 10}</h2>;
    }, [count]);

    return (
      <div className=""> <h2>{count}</h2> <button onClick={() => setCount(count + 1)}>點擊</button> {dom} </div>
    );
  }
複製代碼

注意:傳入 useMemo 的函數會在渲染期間執行。請不要在這個函數內部執行與渲染無關的操做,諸如反作用這類的操做屬於 useEffec 的適用範疇,而不是 useMemo

useCallback

useCallback 的使用和 useMemo 是同樣的,且 useCallback(fn, deps) 至關於 useMemo(() => fn, deps)

這是個人demo

useContext

const value = useContext(MyContext);
複製代碼

接收一個 context 對象(React.createContext 的返回值)並返回該 context 的當前值。當前的 context 值由上層組件中距離當前組件最近的 <MyContext.Provider>value prop 決定。

當組件上層最近的 <MyContext.Provider> 更新時,該 Hook 會觸發重渲染,並使用最新傳遞給 MyContext providercontext value 值。

調用了 useContext 的組件總會在 context 值變化時從新渲染

const Thems = {
    light: {
      color: '#f90',
    },
    dack: {
      color: '#222',
    }
  }

  const ThemsContext = React.createContext({
    them: Thems.light,
    toggleTheme: () => {},
  })

  class Detail extends PureComponent {
    state = {
      context: {
        them:  Thems.light.color,
        toggleTheme: this.handle,
      }
    }

    handle = () => {
      const { context: { them } } = this.state;
      let color = Thems.light.color;
      if (them === Thems.light.color) {
        color = Thems.dack.color
      }
      this.setState({
        context: {
          ...this.state.context,
          them: color,
        },
      });
    }

    render() {
      return (
        <ThemsContext.Provider value={this.state.context}> <Hooks/> </ThemsContext.Provider> ); } } function Hooks(props, ref) { const themContext = React.useContext(ThemsContext); return ( <div style={{ background: themContext.them }} onClick={() => { themContext.toggleTheme() }} > 1234567890 </div> ); } 複製代碼

上面的這個 demo 中,當 ThemsContext.Providervalue props 發生變化時 Hooks 組件就會發生從新渲染。因此說這個時候咱們的目的就已經達到了。

以上就是我在工做中對 Hooks 的一個總結,若有問題請麻煩糾正,謝謝。

相關文章
相關標籤/搜索