常见问题
- 为什么不能在循环,条件,嵌套函数中使用hooks
- 为什么fiber.updateQueue,hook.queue存储是环装链表
- 为什么React不建议使用componentWillxxx的生命周期
名词解释
diff算法
fiber将在reconcile阶段生成,当首次将vdom(深度优先遍历)生成fiber之后,再次触发更新将使用diff算法生成新的fiber。
diff算法原理
对比current fiber和vdom,生成workInProcess Fiber树
- 第一阶段遍历vdom看节点是否可以复用,如果可以复用打成更新的effectTag,新建更新fiber,如果节点不能复用,结束第一阶段,如果vdom都遍历完成可以复用之后,将多余旧fiber链表打成删除的effectTag。
- 遍历旧的fiber链表生成map对象,遍历vdom,查看节点是否存在map对象中,如果存在就移动过来,打effectTag。
- 遍历完vdom之后,将多余旧fiber链表打成删除的effectTag。
fiber
fiber架构,实现异步中断更新。将vdom转为fiber链表,然后在渲染fiber。主要用于之前需要递归遍历vdom,不能中断,当vdom较大时,存在性能问题。(每个ReactElment对应一个fiber)
- reconcile(调和)阶段将vdom采用前序遍历的方式转为fiber,确定节点操作,并打effectTag
- 在commit阶段执行实际dom操作
- react基于fiber的渲染流程render(reconcile+schedule)+commit阶段
数据结构
function FiberNode(
this: $FlowFixMe,
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
) {
// Instance
this.tag = tag; // 用于标记组件类型
this.key = key; // 唯一的key值,用于判断fiber是否可以复用
this.elementType = null;
this.type = null; // 描述对应的组件,自定义组件'App',对于默认元素'div','span'
this.stateNode = null; // 对应组件的dom
// Fiber
this.return = null; // 父
this.child = null; // 子
this.sibling = null; // 兄弟
this.index = 0;
this.ref = null;
this.refCleanup = null;
this.pendingProps = pendingProps; // 初始化props
this.memoizedProps = null; // 更新之后的props
// class 组件Fiber节点上的多个Update组成链表,函数组件的useEffect的effect组件的环状单向链表
this.updateQueue = null;
this.memoizedState = null; // hook组成的单向链表挂载的位置
this.dependencies = null;
this.mode = mode;
// Effects
this.flags = NoFlags; // 标记fiber更新的状态,比如是新建,更新,删除等
this.subtreeFlags = NoFlags;
this.deletions = null;
// 调度优先级
this.lanes = NoLanes;
this.childLanes = NoLanes;
// 指向改fiber在另一次更新的fiber
this.alternate = null;
.....
}
Hook
fiber中的memoizedState存的当前的hooks
const hook: Hook = {
memoizedState: null, // 保存不同数据类型的数据(useEffect,useCallback,useMemo,useState,useRef)
baseState: null,
baseQueue: null, // 上次中断,记录的就是尚未处理的最后一个update
queue: null, // 记录update的函数,setValue
next: null, // 下一个hook
};
第一步:初始化hook(mountWorkInProgressHook)
- 当workInProgressHook为null时,创建hook对象,赋值给fiber.memoizedState和workInProgressWork
- 否则改变workInProgressHook指针,将hook链接 第二步:更新hook(updateWorkInProgressHook)
- 当前执行的hook,workInProgressHook,取fiber.memoizedState
- workInProgressHook.next
function mountWorkInProgressHook() {
let hook = {
memoizedState: null,
baseState: null,
baseQueue: null,
queue: null,
next: null,
};
if (!workInProgressHook) {
workInProgressHook = fiber.memoizedState = hook;
} else {
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
function updateWorkInProgressHook() {
let hook = workInProgressHook;
if (!workInProgressHook) {
workInProgressHook = fiber.memoizedState;
} else {
workInProgressHook = workInProgressHook.next;
}
return hook;
}
useState源码步骤
mountState
- 执行mountWorkInProgressHook函数,创建hook
- 给hook中的memoizedState,baseState,queue,queue.dispath属性赋值
- 返回[hook.memoizedState,dispath];
function mountState(initState) {
let hook = mountWorkInProgressHook();
hook.memoizedState = hook.baseState =
typeof initState === "function" ? initState() : initState;
let queue = {
pending: null, // 存储update的环状单项链表
dispath: null,
lastRenderedState: null,
lastRenderedReducer: null,
};
let dispath = (queue.dispath = dispathSetState.bind(null, fiber, queue));
hook.queue = queue;
return [hook.memoizedState, dispath];
}
updateState
- 调用updateReducer函数,以下为改函数中的步骤
- 执行updateWorkInProgressWork函数,获取当前执行hook
- 将hook.baseQueue插入queue.pending头部
- 遍历baseQueue的生成最新的state,赋值给hook.memoizedState
- 返回[hook.memoizedState,dispath];
function basicStateReducer(state, action) {
return typeof action === "function" ? action(state) : action;
}
function updateReducer(reducer, initialArg) {
let hook = updateWorkInProgressHook();
let baseQueue = hook.baseQueue;
let queue = hook.queue;
let pendingQueue = queue.pending;
if (baseQueue) {
const firstQueue = baseQueue.next;
baseQueue.next = pendingQueue.next;
pendingQueue.next = firstQueue;
}
baseQueue = hook.baseQueue = pendingQueue;
queue.pending = null;
if (baseQueue) {
let first = baseQueue.next;
let update = first;
let newState = hook.baseState;
do {
newState = reducer(newState, update.action);
update = update.next;
} while (update !== null && update !== first);
hook.baseQueue = null;
hook.memoizedState = hook.baseState = newState;
}
return [hook.memoizedState, hook.queue.dispath];
}
function updateState(initState) {
return updateReducer(basicStateReducer, initState);
}
dispathSetState
- 创建update对象
- 插入到queue.pending的尾部,收尾相连的环状列表
- 执行调度
function schedule(fiber) {
workInProgressHook = fiber.memoizedState;
const app = fiber.stateNode();
isMount = false;
return app;
}
function dispathSetState(fiber, queue, action) {
let update = {
action,
next: null,
};
if (queue.pending) {
update.next = queue.pending.next;
queue.pending = queue.pending.next = update;
} else {
queue.pending = update.next = update;
}
schedule(fiber);
}
demo代码 该代码中仅实现usestate的链表存储,并没有实现批量更新及异步中断更新等功能。
知识点
react遍历vdom采用的深度优先遍历(根左右)
react更新
react更新分为两个阶段render+commit
render 阶段
创建fiber对象,生成effectList
commit阶段
- before motation (操作dom之前)
- motation(操作dom)
ref值更新
- layout (dom更新之后)
useLayoutEffect
运算符
- |=:两个二进制位数都为0,则为0,否则为1
- &=:两个二进数位数都为1,则为1,否则为0
- ^=:两个二进数位数的值都相同,则为0,否则为1
参考文档
zhuanlan.zhihu.com/p/553744711 juejin.cn/post/711910… zhuanlan.zhihu.com/p/63739227 xie.infoq.cn/article/977…