合作机构:阿里云 / 腾讯云 / 亚马逊云 / DreamHost / NameSilo / INWX / GODADDY / 百度统计
本来是准备优先分享两个官方定义的 Hook useMemo,useCallback,不过这两个 hook 本身其实没有太多探讨的空间,他们只是两个记忆函数,本身并没有特殊的、更进一步的含义。
许多人的困惑往往来源于对于它们两个过度解读,认为他们的存在对 React 性能的优化有非常重要的意义。过渡解读导致了对他们的滥用。在我看过的项目中,有个别优秀前端团队里的项目规范里,也错误抬高了他们的作用,把他们用在了每一个组件里。
出现这样问题的根源就在于对 React 的自身机制理解不够精准。因此我决定换一个角度去带大家理解 React 本身的优化机制,从而能够正确的使用 useMemo 与 useCallback。
本文将会从应用层面来为大家分析我们应该怎么做。后续的章节将会从 Fiber 的双缓存策略开始分享底层的优化机制。
首先我们要明确一些前置知识。
React 在内存中维护了一颗 虚拟 DOM 树,这颗树的每一个节点是一个 Fiber,每一个 Fiber 都由 JSX 中的组件解析而来。
type Fiber = {
// 用于标记fiber的WorkTag类型,主要表示当前fiber代表的组件类型如FunctionComponent、ClassComponent等
tag: WorkTag,
// ReactElement里面的key
key: null | string,
// ReactElement.type,调用`createElement`的第一个参数
elementType: any,
// The resolved function/class/ associated with this fiber.
// 表示当前代表的节点类型
type: any,
// 表示当前FiberNode对应的element组件实例
stateNode: any,
// 指向他在Fiber节点树中的`parent`,用来在处理完这个节点之后向上返回
return: Fiber | null,
// 指向自己的第一个子节点
child: Fiber | null,
// 指向自己的兄弟结构,兄弟节点的return指向同一个父节点
sibling: Fiber | null,
index: number,
ref: null | (((handle: mixed) => void) & { _stringRef: ?string }) | RefObject,
// 当前处理过程中的组件props对象
pendingProps: any,
// 上一次渲染完成之后的props
memoizedProps: any,
// 该Fiber对应的组件产生的Update会存放在这个队列里面
updateQueue: UpdateQueue<any> | null,
// 上一次渲染的时候的state
memoizedState: any,
// 一个列表,存放这个Fiber依赖的context
firstContextDependency: ContextDependency<mixed> | null,
mode: TypeOfMode,
// Effect
// 用来记录Side Effect
effectTag: SideEffectTag,
// 单链表用来快速查找下一个side effect
nextEffect: Fiber | null,
// 子树中第一个side effect
firstEffect: Fiber | null,
// 子树中最后一个side effect
lastEffect: Fiber | null,
// 代表任务在未来的哪个时间点应该被完成,之后版本改名为 lanes
expirationTime: ExpirationTime,
// 快速确定子树中是否有不在等待的变化
childExpirationTime: ExpirationTime,
// fiber的版本池,即记录fiber更新过程,便于恢复
alternate: Fiber | null,
}
TOP