$ ls ~yifei/notes/

学习 React Hooks

Posted on:

Last modified:

使用 useState hook

useState 可以用来管理一个组件比较简单的一两个状态,如果状态多了不适合使用 useState 管理, 可以使用 useReducer.

import React, {useState} from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(c => c + 1)}> Click me </button>
    </div>
  );
}

export default Counter;

setState 在更新函数中可能表现为异步,保险起见,应该使用一个函数作为它的参数,而不是直接 使用值作为参数。比如在上面的例子中:

// 正确
<button onClick={() => setCount(c => c + 1)}> Click me </button>
// 错误
<button onClick={() => setCount(c + 1)}> Click me </button>

还有一个比较常见的模式是使用一个 object 保存多个状态,在部分更新 state 的时候,可以这样做:

const [state, setState] = useState({
    "showText": true,
    "showShadow": true
})

// 使用 {...s, foo: bar} 避免重复代码
<button onClick={() => setState(s => {...s, showText: false})}>Hide Text</button>

使用 useEffect hook

useEffect hook 用来实现一些副作用,一般可以用作页面首次加载时读取数据。

import React, {useState, useEffect} from 'react';

function App() {
  const [isOn, setIsOn] = useState(false);

  useEffect(() => {
    let interval;
    if (isOn) {
      interval = setInterval(() => console.log('tick'), 1000);
    }
    return () => clearInterval(interval);
  }, [isOn]);
  ...
}

export default App;

在 useEffect 中返回的函数会被用来做垃圾清理。另外需要注意的是,初始化的时候总会触发一次 useEffect.

默认情况下,每次 state 有改变的时候,都会调用 useEffect 函数。如果需要更改触发的时机, 那么需要使用 useEffect 的第二个参数来指定监听的事件或者说状态。当第二个参数只使用一个空 数组 [] 的时候就只会在组件加载的时候调用。数组中有哪些变量,表示在这些变量变化的时候调用。

一般建议把不依赖 props 和 state 的函数提到组件外面,并且把那些仅被 effect 使用的函数放到 effect 里面。

使用 useReducer hook

useReducer 和 useState 的用途基本一样,但是当状态比较复杂的时候,最好使用 useReducer. 有了 useReducer,基本可以不用 redux 了。

const [state, dispatch] = useReducer(reducer, initialState [, init]);

useReducer 通常放在一组状态的根元素层级,比如一个页面。dispatch 函数触发事件,reducer 函数用来处理事件,更新 state.

注意,useReducer 的 reducer 函数,和 redux 不同,不需要 state=initialState 参数。 默认参数在调用 useReducer 的时候已经给出了。

const initialState = {count: 0};

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

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

Context API

Context 用来向所有后代元素广播状态,可以跳跃组件树层级,而不需要层层传递。

  1. 首先需要通过 React.createContext 定义一个高层次 Context,
  2. 然后在最外层使用 <MyContext.Provider /> 来包裹需要接受这个 context 的所有组件。
  3. 在需要使用状态的元素中调用 <MyContext.Consumer />, 访问 Context 中的值
const AppContext = createContext()

function App({children}) {
  return <AppContext.Provider value={42}>
    {children}
  </AppContext.Provider>
}

function Page() {
  return <AppContext.Consumer>
    <span>{value}</span>
  </AppContext.Consumer>
}

使用 useContext hook

除了使用使用 <Consumer/> 以外,我们还可以使用 useContext 钩子来读取 Context 中的值。 这样就不用额外再嵌套一层 Consumer 了。

function Page() {
  // 注意这里的参数是 Context,而不是 Consumer
  const value = useContext(AppContext);
  return <span>{value}</span>;
}

useReducer + useContext = (Better) Redux

可以组合使用 useReducer 和 useContext 来实现 Redux 的功能。只需要用 Context.Provider 把 state 和 dispatch 这两个变量广播给 root 下的所有元素,这样在需要使用 state 和 dispatch 的 地方直接 useContext(Context) 就好了。

useMemo/useCallback 钩子

用来避免重复计算或者重复生成函数。

useRef 钩子

https://stackoverflow.com/questions/56455887/react-usestate-or-useref

自定义钩子

通过灵活组合 useState, 和 useEffect, 我们完全可以创建自己的钩子。

import React from 'react';

function useOffline() {
  const [isOffline, setIsOffline] = React.useState(false);

  function onOffline() {
    setIsOffline(true);
  }

  function onOnline() {
    setIsOffline(false);
  }

  React.useEffect(() => {
    window.addEventListener('offline', onOffline);
    window.addEventListener('online', onOnline);

    return () => {
      window.removeEventListener('offline', onOffline);
      window.removeEventListener('online', onOnline);
    };
  }, []);

  return isOffline;
}

function App() {
  const isOffline = useOffline();

  if (isOffline) {
    return <div>Sorry, you are offline ...</div>;
  }

  return <div>You are online!</div>;
}

export default App;

参考

  1. https://www.robinwieruch.de/react-hooks
  2. https://www.robinwieruch.de/react-hooks-fetch-data
  3. https://medium.com/@nazrhan.mohcine/react-hooks-work-with-usestate-and-usereducer-effectively-471646cdf925
  4. https://daveceddia.com/usecontext-hook/
  5. https://medium.com/@wisecobbler/using-a-function-in-setstate-instead-of-an-object-1f5cfd6e55d1
  6. https://stackoverflow.com/questions/56615931/react-hook-setstate-arguments
  7. https://overreacted.io/zh-hans/a-complete-guide-to-useeffect/
  8. https://mp.weixin.qq.com/s/gzwKs-M7ip3ccW3jnYK9tg
  9. https://medium.com/suyeonme/using-usecontext-and-usereducer-together-lets-create-redux-like-global-state-in-react-87470e3ce7fa

© 2016-2022 Yifei Kong. Powered by ynotes

All contents are under the CC-BY-NC-SA license, if not otherwise specified.

Opinions expressed here are solely my own and do not express the views or opinions of my employer.