前言
- React通过JSX编写视图代码,babel会编译JXS为
React.createElement
的函数调用,产生虚拟DOM,这也意味着React不能像Vue那样可以静态分析视图代码。
- React没有使用
defineProperty
或proxy
收集依赖,每次更新需要手动触发。
// React
function A() {
return <div><span>hello world</span></div>
}
// 编译为
React.createElement('div', {}, React.createElement('span', {}, 'hello world'))
setState
this.setState({ cnt: this.state.cnt + 1 }, () => {
// 执行完更新后执行该回调函数
})
this.setState((prevState) => ({ cnt: prevState.cnt + 1 }), () => {})
class Component {
constructor() {
this.updater = classComponentUpdater
}
setState(partialState) {
this.updater.enqueueSetState(this, partialState)
}
}
const classComponentUpdater = {
enqueueSetState(instance, payload) {
const fiber = instance._reactInternals // 获取实例的fiber
const eventTime = performance.now() // 计算超时时间
const lane = requestUpdateLane(fiber) // 计算本次更新的优先级
const update = { eventTime, lane, payload } // 创建更新对象
fiber.updateQueue.push(update) // 加入fiber的更新队列
scheduleUpdateOnFiber(fiber)
queueMicrotask(flushSyncCallbackQueue)
}
}
function scheduleUpdateOnFiber(fiber) {
const root = markUpdateLaneFromFiberRoot(fiber) // 寻找fiber的根节点,递归fiber.return节点直到fiber.return为null且fiber.tag为HostRoot
if (root === null) {
return null
}
ensureRootIsScheduled(root) // 创建一个任务,从根节点开始更新
}
function ensureRootIsScheduled(root) {
let nextLanes = SyncLane // 1
let newCallbackPriority = SyncLanePriority // 暂定最高优先级12
let existingCallbackPriority = root.callbackPriority // 当前根节点上正在执行更新任务的优先级
if (existingCallbackPriority === newCallbackPriority) { // 相等则复用更新,不再创建
return
}
scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root))
}
let syncQueue = []
function scheduleSyncCallback(cb) {
syncQueue.push(cb)
}
function performSyncWorkOnRoot(workInProgress) { // 真正的渲染任务:比较新旧dom,更新dom
let root = workInProgress
while (workInProgress) {
if (workInProgress.tag === ClassComponent) {
const inst = workInProgress.stateNode // 获取fiber的dom实例
inst.state = processUpdateQueue(inst, workInProgress)
inst.render() // 进行dom diff,更新dom
}
workInProgress = workInProgress.child
}
commitRoot(root)
}
function processUpdateQueue(inst, fiber) {
fiber.updateQueue.reduce((state, update) => {
if (typeof update.payload === 'function') {
update.payload = update.payload(state)
}
reutrn { ...state, ...update.payload } // 拿到更新后的state
}, inst.state)
}
function commitRoot(root) {
root.callbackPriority = NoLanePriority // 0
}
function queueMicrotask() {
syncQueue.forEach(cb => cb())
syncQueue = []
}
总结
setState
是异步更新的,不选择同步更新是因为如果多次更新的话需要多次dom diff,性能不好。
- 实现异步更新则需要一个队列收集同步更新数据,
setState
的时候将实例和更新参数加入更新队列方便后续进行更新,构建好更新对象加入到fiber的更新队列,之后从root fiber开始进行更新,这是因为setState
手动触发的更新,react并不知道是哪里需要更新(粗粒度,Vue使用Watcher类+收集依赖可以得知),所以要从root fiber开始,执行fiber中的更新队列。
- 紧接着开启异步更新,期间可以继续接收同步的更新数据,更新结束后进行render开始dom diff