一篇你能看懂的 React Hooks 全解析

1,274 阅读4分钟

React Hooks

Hook 是 React 16.8 的新增特性。它可以让你在编写函数组件的情况下使用 class组件 的生命周期函数以及 state 等 React特性。

1. useState()

基本用法

useState()是React提供的用来读取和修改 stateHook
它传入的第一个参数initialStatestate的初始值,它的返回一个 state 以及 更新state的函数setState。 在初始渲染期间,返回的状态 state 与传入的第一个参数 initialState 值相同。

const [state, setState] = useState(initialState);

setState函数用于更新 state。它接收一个新的 state 值并将组件的一次重新渲染加入队列。

setState(newState);

函数式更新

如果新的 state 需要通过使用先前的 state 计算得出,那么可以将函数传递给 setState。该函数将接收先前的 state,并返回一个更新后的值。下面的计数器组件示例展示了 setState 的两种用法:

function Counter({initialCount}) {
  const [count, setCount] = useState(initialCount);
  return (
    <>
      Count: {count}
      <button onClick={() => setCount(initialCount)}>Reset</button>
      <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
      <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
    </>
  );
}

“+” 和 “-” 按钮采用函数式形式,因为被更新的 state 需要基于之前的 state。但是“重置”按钮则采用普通形式,因为它总是把 count 设置回初始值。

注意!

与 Vue 框架中的更新数据不同,useState 不会自动合并更新对象。你可以用函数式的 setState 结合展开运算符来达到合并更新对象的效果。

setState(prevState => {
  return {...prevState, ...updatedValues};
});

2. useEffect()

基本概念

useEffect() 给函数组件增加了使用生命周期的能力。它跟 class 组件中的 componentDidMountcomponentDidUpdatecomponentWillUnmount 具有相同的用途,只不过被合并成了一个 API。

1.模拟 componentDidMount 生命周期函数

useEffect(() => {
    // 组件挂载后执行
  },[]);

2.模拟 componentDidUpdate 生命周期函数

const [count,setCount] = useState(1);

// 任意 state 变动都会触发
useEffect(() => {
    // 任意 state 变动后执行
  });

// 指定依赖 state 变动才会触发
useEffect(() => {
    // count 变动后执行
},[count]);

3. 模拟 componentWillUnmount 生命周期函数

const [count,setCount] = useState(1);
useEffect(() => {
    // 组件挂载后执行
    return function(){
        // 组件卸载前执行
    }
  },[]);

3. useContext()

基本用法

const value = useContext(MyContext);

useContext()是用来解决子组件在接收爷爷辈及往上的 先祖组件传递的 props 嵌套传递的问题的。
它的代码组织方式类似于发布订阅模式:先祖组件提供 <MyContext.Provider>value属性提供context -> 子组件间通过useContext(MyContext)拿到contextuseContext() 接收一个 context(上下文) 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider>value 属性决定。
当组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext providercontext value 值。

代码示例

const themes = {
  light: {
    foreground: "#000000",
    background: "#eeeeee"
  },
  dark: {
    foreground: "#ffffff",
    background: "#222222"
  }
};

// 创建 Context
const ThemeContext = React.createContext(themes.light);

// 标明 Context 作用范围 以及 传递的 属性值(value)
function App() {
  return (
    <ThemeContext.Provider value={themes.dark}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function ThemedButton() {
  // 拿到 ThemeContext.Provider value 提供的属性值
  const theme = useContext(ThemeContext);
  return (
    <button style={{ background: theme.background, color: theme.foreground }}>
      I am styled by theme context!
    </button>
  );
}

4. useReducer()

基本概念

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

useReducer()useState() 的一个替代方案。较之于 useState()useReducer()额外传入一个对state的操作——reducerreducer 是一个形如 (state, action) => newState 的函数 ),并返回当前的 state 以及与其配套的 dispatch 方法。

示例程序

以下是用 reducer 重写 useState 一节的计数器示例:

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

5. useRef()

基本用法

const refContainer = useRef(initialValue);

ref的语义是 “引用”,我们通常用 ref 来访问DOM节点。
useRef() 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变

代码示例

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` 指向已挂载到 DOM 上的文本输入元素
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

保存数据

在一个组件中有什么东西可以跨渲染周期,也就是在组件被多次渲染之后依旧不变的属性?第一个想到的应该是state。没错,一个组件的state可以在多次渲染之后依旧不变。但是,state的问题在于一旦修改了它就会造成组件的重新渲染。
那么这个时候就可以使用useRef来跨越渲染周期存储数据,而且对它修改也不会引起组件渲染。

6. useMemo()

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

把“创建”函数和依赖项数组作为参数传入 useMemo(),它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。

7. useCallback()

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

把内联回调函数及依赖项数组作为参数传入 useCallback(),它将返回该回调函数的 memoized 回调函数,该回调函数仅在某个依赖项改变时才会更新。
useCallback(fn, deps) 相当于 useMemo(() => fn, deps)