模仿 big-react,使用 Rust 和 WebAssembly,从零实现 React v18 的核心功能。深入理解 React 源码的同时,还锻炼了 Rust 的技能,简直赢麻了!
代码地址:https://github.com/ParadeTo/big-react-wasm
本文对应 tag:v15
Based on big-react,I am going to implement React v18 core features from scratch using WASM and Rust.
Code Repository:https://github.com/ParadeTo/big-react-wasm
The tag related to this article:v15
本次更新详见这里,下面来过一遍整个流程。
The details of this update can be seen here. Let’s go through the entire process below.
跟 useState
一样,首先需要在 react
包中导出这个方法,它接收两个参数:
Like useState
, we first need to export this method from the react
package. It takes two parameters:
1 |
|
然后,我们需要分别为首次渲染和更新实现 mount_effect
和 update_effect
。其中 mount_effect
会给 FiberNode
的 Hook 链表上新增一个 Hook
节点,其 memoized_state
属性指向一个 Effect
对象,同时这个对象还会被加入 FiberNode
上的 update_queue
, 它是一个环形队列。另外,FiberNode
还被被标记为 PassiveEffect
:
Next, we need to implement mount_effect
and update_effect
for the initial render and updates, respectively. mount_effect
adds a new Hook
node to the linked list of Hooks on the FiberNode
, with its memoized_state
property pointing to an Effect
object. This object is also added to the update_queue
on the FiberNode
, which is a circular queue. Additionally, the FiberNode
is marked with PassiveEffect
:
update_effect
的工作与 mount_effect
类似,会更新 Effect
节点,不过他会把传入的 deps
与之前的 prev_deps
中的元素依次进行浅比较,如果全部相同就不会给 FiberNode
标记 PassiveEffect
。
The work of update_effect
is similar to mount_effect
, updating the Effect
node, but it performs a shallow comparison of the incoming deps
with the previous prev_deps
. If they are all the same, it will not mark the FiberNode
with PassiveEffect
.
Effect
中的属性包含这些:
The properties included in Effect
are as follows:
1 | pub struct Effect { |
接着,Render 阶段不需要更改,Commit 阶段我们需要在 commit_mutation_effects
前新增处理 useEffect
的逻辑:
During the Render phase, no changes are needed. In the Commit phase, we need to add logic to handle useEffect
before commit_mutation_effects
:
1 | // useEffect |
这里,我们使用上一篇文章实现的 scheduler
来调度一个任务执行 flush_passive_effects
方法:
Here, we use the scheduler
implemented in the previous article to schedule a task to execute the flush_passive_effects
method:
1 | fn flush_passive_effects(pending_passive_effects: Rc<RefCell<PendingPassiveEffects>>) { |
这里的 pending_passive_effects
是 FiberRootNode
上的一个属性,用于保存此次需要执行的 Effect
:
The pending_passive_effects
here is a property on the FiberRootNode
, used to store the Effect
that needs to be executed this time:
1 | pub struct PendingPassiveEffects { |
其中,因为组件卸载需要处理的 Effect
保存在 unmount
中,因为更新需要处理的 Effect
保存在 update
中。从代码中看到,这里会先处理因组件卸载需要处理的 Effect
,即使这个组件顺序比较靠后,比如这个例子:
Among them, the Effect
that needs to be handled due to component unmounting is saved in unmount
, and the Effect
that needs to be handled due to updates is saved in update
. From the code, we can see that the Effect
due to component unmounting is handled first, even if the component is later in the sequence, like in this example:
1 | function App() { |
点击后会先执行 Child2
的 useEffect
的 destroy
,打印 child2 destroy
。而如果换成这样:
After clicking, the destroy
of Child2
‘s useEffect
will be executed first, printing child2 destroy
. But if it’s changed to this:
1 | function App() { |
点击后会先执行 Child1
的 useEffect
的 destroy
,打印 child1 destroy
。
After clicking, the destroy
of Child1
‘s useEffect
will be executed first, printing child1 destroy
.
那 pending_passive_effects
里面的 Effect
是什么时候加进去的呢?答案是在 commit_mutation_effects
中,有两种情况:
So when are the Effect
in pending_passive_effects
added? The answer is in commit_mutation_effects
, there are two situations:
如果
FiberNode
节点被标记需要删除且为FunctionComponent
类型,则需要把update_queue
中的Effect
加入pending_passive_effects
中的unmount
列表中。If the
FiberNode
node is marked for deletion and is of theFunctionComponent
type, then theEffect
in theupdate_queue
needs to be added to theunmount
list inpending_passive_effects
.
1 | fn commit_deletion( |
如果
FiberNode
节点被标记为PassiveEffect
,则需要把update_queue
中的Effect
加入pending_passive_effects
中的update
列表中。If the
FiberNode
node is marked withPassiveEffect
, then theEffect
in theupdate_queue
needs to be added to theupdate
list inpending_passive_effects
.
1 | if flags & Flags::PassiveEffect != Flags::NoFlags { |
大致流程介绍完毕,更多细节请参考这里。
The general process is now complete, for more details please refer to here.