生命周期
类组件
React v16 引入的 Fiber 协调引擎,比起老版本来说,为了提高协调效率,减少页面交互卡顿,React 的 Fiber引擎把协调从同步过程改进成了异步过程。
- 渲染阶段是异步过程,主要负责更新虚拟 DOM( FiberNode )树,而不会操作真实DOM,这一过程可能会被 React
暂停和恢复,甚至并发处理,因此要求渲染阶段的生命周期方法必须是没有任何副作用(Side-effect)的纯函数(Pure
Function);- 提交阶段是同步过程,根据渲染阶段的比对结果修改真实 DOM,这一阶段的生命周期方法可以包含副作用。
react中往往提交阶段很快,渲染阶段比较慢。
函数组件
1. 挂载阶段。
React 会执行组件函数,在函数执行过程中遇到的 useState 、 useMemo 等 Hooks 依次挂载到 FiberNode 上,useEffect 其实也会被挂载,但它包含的副作用(Side-effect,在 Fiber 引擎中称为 Effect)会保留到提交阶段。
组件函数的返回值通常会使用 JSX 语法,React 在渲染阶段根据返回值创建 FiberNode 树。在提交阶段,React 更新真实 DOM 之前会依次执行前面定义的 Effect。
2. 更新阶段。
当组件接收到新 props 或者 调用 useState 返回的 setter 或者 useReducer 返回的 dispatch 修改了状态,组件会进入更新阶段。
组件函数本身会被再次执行,Hooks 会依次与 FiberNode 上已经挂载的 Hooks 一一匹配,并根据需要更新。组件函数的返回值用来更新 FiberNode 树。
进入提交阶段,React 会更新真实 DOM。随后 React 会先执行上一轮 Effect 的清除函数,然后再次执行 Effect。这里的 Effect 包括 useEffect 与useLayoutEffect ,两者特性很相像。其中useLayoutEffect 的 Effect 是在更新真实 DOM 之后同步执行的,与类组件的 componentDidMount、componentDidUpdate 更相似一些;而 useEffect 的 Effect 是异步执行的,一般晚于 useLayoutEffect 。
3. 卸载阶段。
主要是执行 Effect 的清除函数。函数组件也有错误处理阶段,但没有对应的生命周期 Hooks,错误处理依赖于父组件或祖先组件提供的错误边界。
Hooks
我的建议是,首先精通三个基础 Hooks,也就是 useState 、 useEffect 和 useContext。然后在此基础上:掌握
useRef 的一般用法;
- 当需要优化性能,减少不必要的渲染时,学习掌握 useMemo 和 useCallback ;4
- 当需要在大中型React 项目中处理复杂 state 时,学习掌握 useReducer ;
- 当需要封装组件,对外提供命令式接口时,学习掌握 useRef加 useImperativeHandle;
- 当页面上用户操作直接相关的紧急更新(UrgentUpdates,如输入文字、点击、拖拽等),受到异步渲染拖累而产生卡顿,需要优化时,学习掌握 useDeferredValue 和useTransition 。
在使用 Hooks 时务必注意。
第一,只能在 React 的函数组件中调用 Hooks。
这也包括了在自定义的 Hook 中调用其他 Hooks 这样间接的调用方式,目的是保证 Hooks 能"勾"到 React 的虚拟 DOM 中去,脱离 React 环境的 Hooks 是无法起作用的。
第二,只能在组件函数的最顶层调用 Hooks。
无论组件函数运行多少遍,都要保证每个 Hook 的执行顺序,这样 React 才能识别每个 Hook,保持它们的状态。当然,这就要求开发者不能在循环、条件分支中或者任何 return 语句之后调用 Hooks。
其实从 Fiber 协调引擎的底层实现来看,也不难理解上面两个限制。函数组件首次渲染时会创建对应的 FiberNode,这个 FiberNode 上会保存一个记录 Hooks 状态的单向链表,链表的长度与执行组件函数时调用的 Hooks 个数相同。
当函数组件再次渲染时,每个 Hook 都会被再次调用,而这些 Hooks 会按顺序,去这个单向链表中一一认领自己上一次的状态,并根据需要沿用或者更新自己在链表中的状态:
如果不在 React 的函数组件中调用 Hooks,React 就不会创建记录 Hooks 状态的单向链表;如果在循环、条件分支等不稳定的代码位置调用 Hooks,就有可能导致再次渲染时,执行 Hooks 的数量、种类和参数与上次的单向链表不一致,Hooks 内部的逻辑就乱掉了。