从零实现 React v18,但 WASM 版 – [5] 实现 Render 流程的 completeWork 阶段

模仿 big-react(https://github.com/BetaSu/big-react),使用 Rust 和 WebAssembly,从零实现 React v18 的核心功能。深入理解 React 源码的同时,还锻炼了 Rust 的技能,简直赢麻了!

代码地址:https://github.com/ParadeTo/big-react-wasm

本文对应 tag:v5

上篇文章实现了 Render 流程中的 begin work 阶段,这篇来实现 complete work。

更多关于 Render 流程的内容可以参考 React 技术揭秘(https://react.iamkasong.com/process/reconciler.html) 或 React 源码解读之首次渲染流程(含例子)

因为是仿写,代码仍然不赘述了,这里(https://github.com/ParadeTo/big-react-wasm/pull/1/files#diff-b572860610fbe556d94c04680b61e47a6a5724b351cdc1d3d1e89ad2ef556ca7)可以看到两个版本的对比,下面解释一些特别的地方。

  • completeWork

CompleteWork 仍然是定义为一个含有 host_config 属性的 struct:

// complete_work.rs
pub struct CompleteWork {
    pub host_config: Rc<dyn HostConfig>,
}

并且它还作为 WorkLoop 的属性,在 WorkLoop 实例初始化时也一并初始化:

// work_loop.rs
pub struct WorkLoop {
    work_in_progress: Option<Rc<RefCell<FiberNode>>>,
    complete_work: CompleteWork,
}
impl WorkLoop {
    pub fn new(host_config: Rc<dyn HostConfig>) -> Self {
        Self {
            work_in_progress: None,
            complete_work: CompleteWork::new(host_config),
        }
    }
    ...
}
  • 修改 react-dom 中的 HostConfig 实现

原来的 create_text_instancecreate_instance 中的返回值分别为 TextElement(虽然最后他们都作为 dyn Any 返回),但是当在 append_initial_child 需要把他们 downcast 回来时就比较麻烦了,因为 child 可以是 TextElement,所以需要尝试两次。所以,为了后续使用方便,这里统一转为 Node 返回:

fn create_text_instance(&self, content: String) -> Rc<dyn Any> {
  ...
  Rc::new(Node::from(document.create_text_node(content.as_str())))
}

fn create_instance(&self, _typeString) -> Rc<dyn Any> {
  ...
  match document.create_element(_type.as_ref()) {
      Ok(element) => {
          Rc::new(Node::from(element))
      }
      Err(_) => todo!(),
  }
}

这样,在 append_initial_child 我们只需要 downcastNode 即可:

fn append_initial_child(&self, parent: Rc<dyn Any>, child: Rc<dyn Any>) {
  let p = parent.clone().downcast::<Node>().unwrap();
  let c = child.clone().downcast::<Node>().unwrap();
  ...
}
  • 代码中有一些看似多余的 {} 代码块,是为了解决 Rust 中关于所有权的一些限制,因为 Rust 中规定“在同一个作用域下,我们不能同时有活跃的可变借用和不可变借用”。比如下面这个例子就会报 already borrowed: BorrowMutError 的错误:
use std::cell::RefCell;

fn main() {
    let data = RefCell::new(5);

    let b1 = data.borrow();
    let b2 = data.borrow_mut();

    println!("{}", b1);
}

改成这样就没问题了:

use std::cell::RefCell;

fn main() {
    let data = RefCell::new(5);
    {
      let b1 = data.borrow();
    }
    {
      let b2 = data.borrow_mut();
    }
    println!("{}", data.borrow());
}

原因在于现在 b1b2 的借用作用域都被限定在 {} 之中。

对 React 最新几个版本不太熟悉的人可能会疑惑,怎么 Render 阶段没有生成 Effect List?原因在于 React 为了支持 Suspense,在 v16.14.0 版本中去掉了 Effect List,改为使用 subtreeFlags 来标记子树中是否有副作用,更多信息可以参考这篇文章

为了验证 complete work 阶段代码是否正确,我们在 FiberRootNodefmt 方法中增加一些关于 flags 的调式信息:

...
WorkTag::HostRoot => {
    write!(f, "{:?}(subtreeFlags:{:?})", WorkTag::HostRoot, current_ref.subtree_flags);
}
WorkTag::HostComponent => {
    let current_borrowed = current.borrow();
    write!(
        f,
        "{:?}(flags:{:?}, subtreeFlags:{:?})",
        ...
    );
}
WorkTag::HostText => {
    let current_borrowed = current.borrow();

    write!(
        f,
        "{:?}(state_node:{:?}, flags:{:?})",
...

重新构建并安装依赖,运行 hello world 项目

import {createRoot} from 'react-dom'

const comp = (
  <div>
    <p>
      <span>Hello World</span>
    </
p>
  </div>
)
const root = createRoot(document.getElementById('root'))
root.render(comp)

可以看到如下结果:

从零实现 React v18,但 WASM 版 - [5] 实现 Render 流程的 completeWork 阶段

Render 流程到这里暂时告一段落了,后续加入其它功能会再修改这里的内容。下一篇文章我们来实现 Commit 流程。

最后,推荐下卡老师的名著:

原文始发于微信公众号(前端游):从零实现 React v18,但 WASM 版 – [5] 实现 Render 流程的 completeWork 阶段

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/282000.html

(0)
前端游的头像前端游bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!