前言
Facebook 的研发能力真是惊人, Fiber
架构给 React 带来了新视野的同时,将调度一词介绍给了前端,然而这个架构实在不好懂,比起以前的 Vdom
树,新的 Fiber
树就麻烦太多。
可以说,React 16 和 React 15 已经是技巧上的分水岭,但是得益于 React 16 的 Fiber
架构,使得 React 即使在没有开启异步的情况下,性能依旧是得到了提高。
经过两个星期的痛苦研究,终于将 React 16 的渲染脉络摸得比较清晰,可以写文章来记录、回顾一下。
如果你已经稍微理解了 Fiber
架构,可以直接看代码:仓库地址
什么是 React Fiber ?
React Fiber
并不是所谓的纤程(微线程、协程),而是一种基于浏览器的单线程调度算法,背后的支持 API 是大名鼎鼎的: requestIdleCallback
,得到了这个 API 的支持,我们便可以将 React 中最耗时的部分放入其中。
回顾 React 历年来的算法都知道,reconcilation
算法实际上是一个大递归,大递归一旦进行,想要中断还是比较不好操作的,加上头大尾大的 React 15 代码已经膨胀到了不可思议的地步,在重重压力之下,React 使用了大循环来代替之前的大递归,虽然代码变得比递归难懂了几个梯度,但是实际上,代码量比原来少了非常多(开发版本 3W 行压缩到了 1.3W 行)
那问题就来了,什么是 Fiber
:一种将 recocilation
(递归 diff
),拆分成无数个小任务的算法;它随时能够停止,恢复。停止恢复的时机取决于当前的一帧( 16ms
)内,还有没有足够的时间允许计算。
React 异步渲染流程图
- 用户调用
ReactDOM.render
方法,传入例如<App />
组件,React 开始运作<App />
<App />
在内部会被转换成RootFiber
节点,一个特殊的节点,并记录在一个全局变量中,TopTree
- 拿到
<App />
的RootFiber
,首先创建一个<App />
对应的 Fiber ,然后加上 Fiber 信息,以便之后回溯。随后,赋值给之前的全局变量 TopTree - 使用
requestIdleCallback
重复第三个步骤,直到循环到树的所有节点 - 最后完成了
diff
阶段,一次性将变化更新到真实DOM
中,以防止 UI 展示的不连续性
其中,重点就是 3
和 4
阶段,这两个阶段将创建真实 DOM 和组件渲染 ( render
)拆分为无数的小碎块,使用 requestIdleCallback
连续进行。在 React 15 的时候,渲染、创建、插入、删除等操作是最费时的,在 React 16 中将渲染、创建抽离出来分片,这样性能就得到了极大的提升。
那为什么更新到真实 DOM 中不能拆分呢?理论上来说,是可以拆分的,但是这会造成 UI 的不连续性,极大的影响体验。
递归变成了循环
以简单的组件为例子:
- 从顶端的
div#root
向下走,先走左子树 div
有两个孩子span
,继续走左边的- 来到
span
,之下只有一个hello
,到此,不再继续往下,而是往上回到span
- 因为
span
有一个兄弟,因此往兄弟span
走去 - 兄弟
span
有孩子luy
,到此,不继续往下,而是回到luy
的老爹span
luy
的老爹span
右边没有兄弟了,因此回到其老爹div
div
没有任何的兄弟,因此回到顶端的div#root
每经过一个 Fiber
节点,执行 render
或者 document.createElement
(或者更新 DOM
)的操作
Fiber 数据结构
一个 Fiber
数据结构比较复杂
const Fiber = {
tag: HOST_COMPONENT,
type: 'div',
return: parentFiber,
child: childFiber,
sibling: null,
alternate: currentFiber,
stateNode: document.createElement('div') | instance,
props: { children: [], className: 'foo' },
partialState: null,
effectTag: PLACEMENT,
effects: []
}
这是一个比较完整的 Fiber object
,他复杂的原因是因为一个 Fiber
就代表了一个「正在执行或者执行完毕」的操作单元。这个概念不是那么好理解,如果要说得简单一点就是:以前的 VDOM
树节点的升级版。让我们介绍几个关键属性:
- 由「 递归改循环 」我们可以得知,当我们循环的遍历树到达底部时,需要回到其父节点,那么对应的就是
Fiber
中的return
属性(以前叫parent
)。child
和sibling
类似,代表这个Fiber
的子Fiber
和兄弟Fiber
stateNode
这个属性比较特殊,用于记录当前Fiber
所对应的真实DOM
节点 或者 当前虚拟组件的实例,这么做的原因第一是为了实现Ref
,第二是为了实现DOM
的跟踪tag
属性在新版的React
中一共有 14 种值,分别代表了不同的JSX
类型。effectTag
和effects
这两个属性为的是记录每个节点Diff
后需要变更的状态,比如删除,移动,插入,替换,更新等…
alternate
属性我想拿出来单独说一下,这个属性是 Fiber
架构新加入的属性。我们都知道,VDOM
算法是在更新的时候生成一颗新的 VDOM
树,去和旧的进行对比。在 Fiber
架构中,当我们调用 ReactDOM.render
或者 setState
之后,会生成一颗树叫做:work-in-progress tree
,这一颗树就是我们所谓的新树用来与我们的旧树进行对比,新的树和旧的树的 Fiber
是完全不一样的,此时,我们就需要 alternate
属性去链接新树和旧树。
司徒正美的研究中,一个 Fiber
和它的 alternate
属性构成了一个联婴体,他们有共同的 tag
,type
,stateNode
属性,这些属性在错误边界自爆时,用于恢复当前节点。
开始写代码:Component 构造函数
讲了那么多的理论,大家一定是晕了,但是没办法,Fiber
架构已经比之前的简单 React 要复杂太多了,因此不可能指望一次性把 Fiber
的内容全部理解,需要反复多看。
当然,结合代码来梳理,思路旧更加清晰了。我们在构建新的架构时,老的 Luy 代码大部分都要进行重构了,先来看看几个主要重构的地方:
export class Component {
constructor(props, context) {
this.props = props
this.context = context
this.state = this.state || {}
this.refs = {}
this.updater = {}
}
setState(updater) {
scheduleWork(this, updater)
}
render() {
throw 'should implement `render()` function'
}
}
Component.prototype.isReactComponent = true
- 这就是
React.Component
的代码 - 构造函数中,我们都进两个参数,一个是外部的
props
,一个是context
- 内部有
state
,refs
,updater
,updater
用于收集setState
的信息,便于之后更新用。当然,在这个版本之中,我并没有使用。 setState
函数也并没有做队列处理,只是调用了scheduleWork
这个函数Component.prototype.isReactComponent = true
,这段代码表饰着,如果一个组件的类型为function
且拥有isReactComponent
,那么他就是一个有状态组件,在创建实例时需要用new
,而无状态组件只需要fn(props,context)
调用
const tag = {
HostComponent: 'host',
ClassComponent: 'class',
HostRoot: 'root',
HostText: 6,
FunctionalComponent: 1
}
const updateQueue = []
export function render(Vnode, Container, callback) {
updateQueue.push({
fromTag: tag.HostRoot,
stateNode: Container,
props: { children: Vnode }
})
requestIdleCallback(performWork) //开始干活
}
export function scheduleWork(instance, partialState) {
updateQueue.push({
fromTag: tag.ClassComponent,
stateNode: instance,
partialState: partialState
})
requestIdleCallback(performWork) //开始干活
}
我们定义了一个全局变量 updateQueue
来记录我们所有的更新操作,每当 render
和 scheduleWork (setState)
触发时,我们都会往 updateQueue
中 push
一个状态,然后,进而调用大名鼎鼎的 requestIdleCallback
进行更新。在这里与之前的 react 15 最大不同是,更新阶段和首次渲染阶段得到了统一,都是使用了 updateQueue
进行更新。
实际上这里还有优化的空间,就是多次 setState
的时候,应该合并成一次再进行 requestIdleCallback
的调用,不过这并不是我们的目标,我们的目标是搞懂 Fiber
架构。requestIdleCallback
调用的是 performWork
函数,我们接下来看看
performWork 函数
const EXPIRATION_TIME = 1 // ms async 逾期时间
let nextUnitOfWork = null
let pendingCommit = null
function performWork(deadline) {
workLoop(deadline)
if (nextUnitOfWork || updateQueue.length > 0) {
requestIdleCallback(performWork) //继续干
}
}
function workLoop(deadline) {
if (!nextUnitOfWork) {
//一个周期内只创建一次
nextUnitOfWork = createWorkInProgress(updateQueue)
}
while (nextUnitOfWork && deadline.timeRemaining() > EXPIRATION_TIME) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
}
if (pendingCommit) {
//当全局 pendingCommit 变量被负值
commitAllwork(pendingCommit)
}
}
熟悉 requestIdleCallback
的同学一定对这两个函数并不陌生,这两个函数其实做的就是所谓的异步调度。
performWork
函数主要做了两件事,第一件事就是拿到 deadline
进入我们之前所谓的大循环,也就是正式进入处理新旧 Fiber
的 Diff
阶段,这个阶段比较的奇妙,我们叫他 workLoop
阶段。workLoop
会一次处理 1 个或者多个 Fiber
,具体处理多少个,要看每一帧具体还剩下多少时间,如果一个 Fiber
消耗太多时间,那么就会等到下一帧再处理下一个 Fiber
,如此循环,遍历整个 VDOM
树。
在这里我们注意到,如果一个
Fiber
消耗太多时间,可能会导致一帧时间的逾期,不过其实没什么问题啦,也仅仅是一帧逾期而已,对于我们视觉上并没有多大的影响。
workLoop
函数主要是三部曲:
createWorkInProgress
这个函数会构建一颗树的顶端,赋值给全局变量nextUnitOfWork
,通过迭代的方式,不断更新nextUnitOfWork
直到遍历完所有树的节点。performUnitOfWork
函数是第二步,不断的检测当前帧是否还剩余时间,进行WorkInProgress
tree 的迭代- 当
WorkInProgress
tree 迭代完毕以后,调用commitAllWork
,将所有的变更全部一次性的更新到DOM
中,以保证 UI 的连续性
所有的 Diff
和创建真实 DOM
的操作,都在 performUnitOfWork
之中,但是插入和删除是在 commitAllWork
之中。接下来,我们逐一分析三部曲的内部操作。
第一步:createWorkInProgress
export function createWorkInProgress(updateQueue) {
const updateTask = updateQueue.shift()
if (!updateTask) return
if (updateTask.partialState) {
// 证明这是一个setState操作
updateTask.stateNode._internalfiber.partialState = updateTask.partialState
}
const rootFiber =
updateTask.fromTag === tag.HostRoot
? updateTask.stateNode._rootContainerFiber
: getRoot(updateTask.stateNode._internalfiber)
return {
tag: tag.HostRoot,
stateNode: updateTask.stateNode,
props: updateTask.props || rootFiber.props,
alternate: rootFiber // 用于链接新旧的 VDOM
}
}
function getRoot(fiber) {
let _fiber = fiber
while (_fiber.return) {
_fiber = _fiber.return
}
return _fiber
这个函数的主要作用就是构建 workInProgress
树的顶端并赋值给全局变量 nextUnitOfWork。
首先,我们先从 updateQueue
中获取一个任务对象 updateTask
。随后,进行判断是否是更新阶段。然后获取 workInProgress
树的顶端。如果是第一次渲染, RootFiber
的值是空的,因为我们并没有构建任何的树。
最后,我们将返回一个 Fiber
对象,这个 Fiber
对象的标识符( tag
)是 HostRoot
。
第二步:performUnitOfWork
// 开始遍历
function performUnitOfWork(workInProgress) {
const nextChild = beginWork(workInProgress)
if (nextChild) return nextChild
// 没有 nextChild, 我们看看这个节点有没有 sibling
let current = workInProgress
while (current) {
//收集当前节点的effect,然后向上传递
completeWork(current)
if (current.sibling) return current.sibling
//没有 sibling,回到这个节点的父亲,看看有没有sibling
current = current.return
}
}
我们调用 performUnitOfWork
处理我们的 workInProgress
。
整个函数做的事情其实就是一个左遍历树的过程。首先,我们调用 beginWork
,获得一个当前 Fiber
下的第一个孩子,如果有直接返回出去给 nextUnitOfWork
,当作下一个处理的节点;如果没有找到任何孩子,证明我们已经到达了树的底部,通过下面的 while
循环,回到当前节点的父节点,将当前 Fiber
下拥有 Effect
的孩子全部记录下来,以便于之后更新 DOM
。
然后查找当前节点的父亲节点,是否有兄弟,有就返回,当成下一个处理的节点,如果没有,就继续回溯。
整个过程用图来表示,就是:
在讨论第三部之前,我们仍然有两个迷惑的地方:
beginWork
是如何创建孩子的completeWork
是如何收集effect
的接下来,我们就来一起看看
beginWork
function beginWork(currentFiber) {
switch (currentFiber.tag) {
case tag.ClassComponent: {
return updateClassComponent(currentFiber)
}
case tag.FunctionalComponent: {
return updateFunctionalComponent(currentFiber)
}
default: {
return updateHostComponent(currentFiber)
}
}
}
function updateHostComponent(currentFiber) {
// 当一个 fiber 对应的 stateNode 是原生节点,那么他的 children 就放在 props 里
if (!currentFiber.stateNode) {
if (currentFiber.type === null) {
//代表这是文字节点
currentFiber.stateNode = document.createTextNode(currentFiber.props)
} else {
//代表这是真实原生 DOM 节点
currentFiber.stateNode = document.createElement(currentFiber.type)
}
}
const newChildren = currentFiber.props.children
return reconcileChildrenArray(currentFiber, newChildren)
}
function updateFunctionalComponent(currentFiber) {
let type = currentFiber.type
let props = currentFiber.props
const newChildren = currentFiber.type(props)
return reconcileChildrenArray(currentFiber, newChildren)
}
function updateClassComponent(currentFiber) {
let instance = currentFiber.stateNode
if (!instance) {
// 如果是 mount 阶段,构建一个 instance
instance = currentFiber.stateNode = createInstance(currentFiber)
}
// 将新的state,props刷给当前的instance
instance.props = currentFiber.props
instance.state = { ...instance.state, ...currentFiber.partialState }
// 清空 partialState
currentFiber.partialState = null
const newChildren = currentFiber.stateNode.render()
// currentFiber 代表老的,newChildren代表新的
// 这个函数会返回孩子队列的第一个
return reconcileChildrenArray(currentFiber, newChildren)
}
beginWork
其实是一个判断分支的函数,整个函数的意思是:
- 判断当前的
Fiber
是什么类型,是class
的走class
分支,是stateless
的走stateless,是原生节点的走原生分支
- 如果没有
stateNode
,则创建一个stateNode
- 如果是
class
,则创建实例,调用render
函数,渲染其儿子;如果是原生节点,调用DOM API
创建原生节点;如果是stateless
,就执行它,渲染出VDOM
节点 - 最后,走到最重要的函数,
recocileChildrenArray
函数,将其每一个孩子进行链表的链接,进行diff
,然后返回当前Fiber
之下的第一个孩子
我们来看看比较重要的 classComponent
的构建流程
function updateClassComponent(currentFiber) {
let instance = currentFiber.stateNode
if (!instance) {
// 如果是 mount 阶段,构建一个 instance
instance = currentFiber.stateNode = createInstance(currentFiber)
}
// 将新的state,props刷给当前的instance
instance.props = currentFiber.props
instance.state = { ...instance.state, ...currentFiber.partialState }
// 清空 partialState
currentFiber.partialState = null
const newChildren = currentFiber.stateNode.render()
// currentFiber 代表老的,newChildren代表新的
// 这个函数会返回孩子队列的第一个
return reconcileChildrenArray(currentFiber, newChildren)
}
function createInstance(fiber) {
const instance = new fiber.type(fiber.props)
instance._internalfiber = fiber
return instance
}
如果是首次渲染,那么组件并没有被实例话,此时我们调用 createInstance
实例化组件,然后将当前的 props
和 state
赋值给 props
、state
,随后我们调用 render
函数,获得了新儿子 newChildren
。
渲染出新儿子之后,来到了新架构下最重要的核心函数 reconcileChildrenArray
.
reconcileChildrenArray
const PLACEMENT = 1
const DELETION = 2
const UPDATE = 3
function placeChild(currentFiber, newChild) {
const type = newChild.type
if (typeof newChild === 'string' || typeof newChild === 'number') {
// 如果这个节点没有 type ,这个节点就可能是 number 或者 string
return createFiber(tag.HostText, null, newChild, currentFiber, PLACEMENT)
}
if (typeof type === 'string') {
// 原生节点
return createFiber(tag.HOST_COMPONENT, newChild.type, newChild.props, currentFiber, PLACEMENT)
}
if (typeof type === 'function') {
const _tag = type.prototype.isReactComponent ? tag.CLASS_COMPONENT : tag.FunctionalComponent
return {
type: newChild.type,
tag: _tag,
props: newChild.props,
return: currentFiber,
effectTag: PLACEMENT
}
}
}
function reconcileChildrenArray(currentFiber, newChildren) {
// 对比节点,相同的标记更新
// 不同的标记 替换
// 多余的标记删除,并且记录下来
const arrayfiyChildren = arrayfiy(newChildren)
let index = 0
let oldFiber = currentFiber.alternate ? currentFiber.alternate.child : null
let newFiber = null
while (index < arrayfiyChildren.length || oldFiber !== null) {
const prevFiber = newFiber
const newChild = arrayfiyChildren[index]
const isSameFiber = oldFiber && newChild && newChild.type === oldFiber.type
if (isSameFiber) {
newFiber = {
type: oldFiber.type,
tag: oldFiber.tag,
stateNode: oldFiber.stateNode,
props: newChild.props,
return: currentFiber,
alternate: oldFiber,
partialState: oldFiber.partialState,
effectTag: UPDATE
}
}
if (!isSameFiber && newChild) {
newFiber = placeChild(currentFiber, newChild)
}
if (!isSameFiber && oldFiber) {
// 这个情况的意思是新的节点比旧的节点少
// 这时候,我们要将变更的 effect 放在本节点的 list 里
oldFiber.effectTag = DELETION
currentFiber.effects = currentFiber.effects || []
currentFiber.effects.push(oldFiber)
}
if (oldFiber) {
oldFiber = oldFiber.sibling || null
}
if (index === 0) {
currentFiber.child = newFiber
} else if (prevFiber && newChild) {
// 这里不懂是干嘛的
prevFiber.sibling = newFiber
}
index++
}
return currentFiber.child
}
这个函数做了几件事
- 将孩子
array
化,这么做能够使得react
的render
函数返回数组 currentFiber
是新的workInProgress
上的一个节点,是属于新的VDOM
树 ,而此时,我们必须要找到旧的VDOM
树来进行比对。那么在这里,Alternate
属性就起到了关键性作用,这个属性链接了旧的VDOM
,使得我们能够获取原来的VDOM
- 接下来我们进行对比,如果新的节点的
type
与原来的相同,那么我们将新建一个Fiber
,标记这个Fiber
为UPDATE
- 如果新的节点的
type
与原来的不相同,那我们使用PALCEMENT
来标记他 - 如果旧的节点数量比新的节点少,那就证明,我们要删除旧的节点,我们把旧节点标记为
DELETION
,并构建一个effect list
记录下来 - 当前遍历的是组件的第一个孩子,那么我们将他记录在
currentFiber
的child
字段中 - 当遍历的不是第一个孩子,我们将 新建的
newFiber
用链表的形式将他们一起推入到currentFiber
中 - 返回当前
currentFiber
下的第一个孩子
看着比较啰嗦,但是实际上做的就是构建链表和 diff
孩子的过程,这个函数有很多优化的空间,使用 key
以后,在这里能提高很多的性能,为了简单,我并没有对 key
进行操作,之后的 Luy
版本一定会的。
completeWork: 收集 effectTag
// 开始遍历
function performUnitOfWork(workInProgress) {
const nextChild = beginWork(workInProgress)
if (nextChild) return nextChild
// 没有 nextChild, 我们看看这个节点有没有 sibling
let current = workInProgress
while (current) {
//收集当前节点的effect,然后向上传递
completeWork(current)
if (current.sibling) return current.sibling
//没有 sibling,回到这个节点的父亲,看看有没有sibling
current = current.return
}
}
//收集有 effecttag 的 fiber
function completeWork(currentFiber) {
if (currentFiber.tag === tag.classComponent) {
// 用于回溯最高点的 root
currentFiber.stateNode._internalfiber = currentFiber
}
if (currentFiber.return) {
const currentEffect = currentFiber.effects || [] //收集当前节点的 effect list
const currentEffectTag = currentFiber.effectTag ? [currentFiber] : []
const parentEffects = currentFiber.return.effects || []
currentFiber.return.effects = parentEffects.concat(currentEffect, currentEffectTag)
} else {
// 到达最顶端了
pendingCommit = currentFiber
}
}
这个函数做了两件事,第一件事情就是收集当前 currentFiber
的 effectTag
,将其 append
到父 Fiber
的 effectlist
中去,通过循环一层一层往上,最终到达顶端 currentFiber.return === void 666
的时候,证明我们到达了 root
,此时我们已经把所有的 effect
收集到了顶端的 currentFiber.effect
上,并把它赋值给 pendingCommit
,进入 commitAllWork
阶段。
第三步:commitAllWork
终于,我们已经通过不断不断的调用 requestIdleCallback
和 大循环,将我们的所有变更都找出来放在了 workInProgress tree
里,我们接下来就要做最后一步:将所有的变更一次性的变更到真实 DOM
中,注意,这个阶段里我们不再运行创建 DOM
和 render
,因此,虽然我们一次性变更所有的 DOM
,但是性能来说并不是太差。
function commitAllwork(topFiber) {
topFiber.effects.forEach(f => {
commitWork(f)
})
topFiber.stateNode._rootContainerFiber = topFiber
topFiber.effects = []
nextUnitOfWork = null
pendingCommit = null
}
我们直接拿到 TopFiber
中的 effects list
,遍历,将变更全部打到 DOM
中去,然后我们将全局变量清理干净。
function commitWork(effectFiber) {
if (effectFiber.tag === tag.HostRoot) {
// 代表 root 节点没什么必要操作
return
}
// 拿到parent的原因是,我们要将元素插入的点,插在父亲的下面
let domParentFiber = effectFiber.return
while (domParentFiber.tag === tag.classComponent || domParentFiber.tag === tag.FunctionalComponent) {
// 如果是 class 就直接跳过,因为 class 类型的fiber.stateNode 是其本身实例
domParentFiber = domParentFiber.return
}
//拿到父亲的真实 DOM
const domParent = domParentFiber.stateNode
if (effectFiber.effectTag === PLACEMENT) {
if (effectFiber.tag === tag.HostComponent || effectFiber.tag === tag.HostText) {
//通过 tag 检查是不是真实的节点
domParent.appendChild(effectFiber.stateNode)
}
// 其他情况
} else if (effectFiber.effectTag == UPDATE) {
// 更新逻辑 只能是没实现
} else if (effectFiber.effectTag == DELETION) {
//删除多余的旧节点
commitDeletion(effectFiber, domParent)
}
}
function commitDeletion(fiber, domParent) {
let node = fiber
while (true) {
if (node.tag == tag.classComponent) {
node = node.child
continue
}
domParent.removeChild(node.stateNode)
while (node != fiber && !node.sibling) {
node = node.return
}
if (node == fiber) {
return
}
node = node.sibling
}
}
这一部分代码是最好理解的了,就是做的是删除和插入或者更新 DOM
的操作,值得注意的是,删除操作依旧使用的链表操作。
最后来一段测试代码:
import React from './Luy/index'
import { Component } from './component'
import { render } from './vdom'
class App extends Component {
state = {
info: true
}
constructor(props) {
super(props)
setTimeout(() => {
this.setState({
info: !this.state.info
})
}, 1000)
}
render() {
return (
<div>
<span>hello</span>
<span>luy</span>
<div>{this.state.info ? 'imasync' : 'iminfo'}</div>
</div>
)
}
}
render(<App />, document.getElementById('root'))
我们来看看动图吧!当节点 mount
以后,过了 1 秒,就会更新,我们简单的更新就到此结束了
再看以下调用栈,我们的 requestIdleCallback
函数已经正确的运行了。
如果你想下载代码亲自体验,可以到 Luy 仓库中:
git clone https://github.com/Foveluy/Luy.git
cd Luy
npm i --save-dev
npm run start
目前我能找到的所有资料都放在仓库中:资料
回顾本文几个重要的点
一开始我们就使用了一个数组来记录 update
的信息,通过调用 requestIdleCallback
来将更新一个一个的取出来,大部分时间队列里只有一个。
取出来以后,使用从左向右遍历的方式,用链表链接一个一个的 Fiber
,并做 diff
和创建,最后一次性的 patch
到真实 DOM
中去。
现在 react 的架构已经变得极其复杂,而本文也只是将 React 的整体架构通篇流程描述了一遍,里面的细节依旧值得我们的深究,比如,如何传递 context
,如何实现 ref
,如何实现错误边界处理,声明周期的处理,这些都是很大的话题,在接下去的文章里,我会一步一步的将这些关系讲清楚。
最后,感谢支持我的迷你框架项目:Luy ,现在正在向 Fiber
晋级!如果你喜欢,请给我一点 star🌟 表示鼓励!谢谢
如果有什么问题,可以加入我们的学习 QQ 群: 370262116
,群里几乎所有的迷你 React
作者都在了,包括 anu
作者司徒正美, omi
作者,我等,一起来学习吧!