$ ls ~yifei/notes/

学习 React Hooks

Posted on:

Last modified:

React 在 function 组件的 fiber 节点中加入了 memorizedState 属性用来存储数据,然后在 function 组件里面通过 api 来使用这些数据,这些 api 被叫做 hooks api。

因为是使用 fiber 节点上的数据,就把 api 命名为了 useXxx。

每个 hooks api 都要有自己存放数据的地方,怎么组织呢?有两种方案,一种是 map,一种是数组。 用 map 的话那么要 hooks api 要指定 key,按照 key 来存取 fiber 节点中的数据。 用数组的话顺序不能变,所以 hooks api 不能出现在 if 等逻辑块中,只能在顶层。 为了简化使用, hooks 最终使用了数组的方式。当然,实现起来用的是链表。 每个 hooks api 取对应的 fiber.memoriedState 中的数据来用。

hooks api 可以分为 3 类:

第一类是数据类的:

  • useState:在 fiber.memoriedState 的对应元素中存放数据
  • useMemo:在 fiber.memoriedState 的对应元素中存放数据,值是缓存的函数计算的结果,在 state 变化后重新计算值
  • useCallback:在 fiber.memoriedState 的对应元素中存放数据,值是函数,在 state 变化后重新 执行函数,是 useMemo 在值为函数的场景下的简化 api,比如 useCallback(fn, [a,b]) 相当 于 useMemo(() => fn, [a, b])
  • useReducer:在 fiber.memoriedState 的对应元素中存放数据,值为 reducer 返回的结果,可以 通过 action 来触发值的变更
  • useRef:在 fiber.memoriedState 的对应元素中存放数据,值为 {current: 具体值} 的形式,因为 对象不变,只是 current 属性变了,所以不会修改。

useState 是存储值最简单的方式,useMemo 基于 state 执行函数并且缓存结果,useCallback 是 针对值为函数的情况的简化,useReducer 通过 action 来触发值的修改。useRef 包了一层对象, 每次对比都是同一个,所以可以放一些不变的数据。

不管形式怎么样,这些 hooks 的 api 的作用都是返回值的。

第二类是逻辑类的:

  • useEffect:异步执行函数,当依赖 state 变化之后会再次执行,当组件销毁时会调用返回的清理函数
  • useLayoutEffect:在渲染完成后同步执行函数,可以拿到 dom

这两个 hooks api 都是用于执行逻辑的,不需要等渲染完的逻辑都可以放到 useEffect 里。

第三类是 ref 转发专用的:

数据可以通过各种方案共享,但是 dom 元素就得通过 ref 转发了,所谓的 ref 转发就是在父组件 创建 ref,然后子组件把元素传过去。传过去之前想做一些修改,就可以用 useImperativeHandle 来改。

通过这 3 类 hooks api,以及之后会添加的更多 hooks api ,函数组件里面也能做 state 的存储, 也能在一些阶段执行一段逻辑,是可以替代 class 组件的方案了。

而且更重要的是,hooks api 是传递参数的函数调用的形式,可以对 hooks api 进一步封装成功能 更强大的函数,也就是自定义 hooks。通过这种方式就可以做跨组件的逻辑复用了。

useState

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

// 正确
<button onClick={() => setCount(c => c + 1)}> Click me </button>
// 错误
<button onClick={() => setCount(count + 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>

useRef

useRef 通常用于保存 DOM 节点的引用

// 创建 ref
const anchor = useRef(null)

function getStyles() {
    // 使用 ref.current 访问引用
    return window.getComputedStyle(anchor.current)
}

// 使用 ref={xxx} 绑定引用
return <a ref={anchor}>hello</a>

useRef 钩子和 useState 有些类似,都用于在不同的重绘之间保存状态。区别也很明显: useState 中的 setState 会导致重绘,而改变 useRef 的 .current 则不会。

useEffect

useEffect hook 用来实现一些副作用,可以在某些变量更新时重绘组件。

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

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.

建议把不闭包依赖 props 和 state 的函数提到组件外,只在 effect 中使用的函数放到 effect 里。

useMemo/useCallback

用来保证子组件 props 的 Referential equality,实际很少用到。

useCallback(fn, [])  // is equal to
useMemo(() => fn, [])

网上有些博客中说到的「useCallback 改善性能」大多属于误用。

自定义钩子

通过灵活组合 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;

use-local-storage

参考

  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
  10. https://kentcdodds.com/blog/usememo-and-usecallback
  11. https://stackoverflow.com/questions/56455887/react-usestate-or-useref
  12. https://mp.weixin.qq.com/s/mZ7KuFjyCWNCAq7HnXg96A
WeChat Qr Code

© 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.

友情链接: MySQL 教程站