从零实现 React v18,但 WASM 版 - [7] 支持 FunctionComponent 类型

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

代码地址:github.com/ParadeTo/bi...

本文对应 tag:v7

上篇文章已经实现了 HostComponentHostText 类型的首次渲染,这篇文章我们把 FunctionComponent 也加上,不过暂时不支持 Hooks

按照流程,首先是要在 begin_work 中增加对 FunctionComponent 的分支处理:

rust 复制代码
pub fn begin_work(work_in_progress: Rc<RefCell<FiberNode>>) -> Option<Rc<RefCell<FiberNode>> {
  let tag = work_in_progress.clone().borrow().tag.clone();
  return match tag {
      WorkTag::FunctionComponent => update_function_component(work_in_progress.clone()),
      ...
  };
}

fn update_function_component(
    work_in_progress: Rc<RefCell<FiberNode>>,
) -> Option<Rc<RefCell<FiberNode>>> {
    let fiber_hooks = &mut FiberHooks::new();
    let next_children = Rc::new(fiber_hooks.render_with_hooks(work_in_progress.clone())?);
    reconcile_children(work_in_progress.clone(), Some(next_children));
    work_in_progress.clone().borrow().child.clone()
}

从代码中可以看到,我们需要新建一个 FiberHooks 的 struct,并实现 render_with_hooks 方法,目前该方法的核心就是从 FiberNode 中提取 _typepending_props,并调用 _type 指向的函数(参数为 pending_props 所指向的对象),得到 children

rust 复制代码
impl FiberHooks {
    ...
    pub fn render_with_hooks(&mut self, work_in_progress: Rc<RefCell<FiberNode>>) -> Result<JsValue, JsValue> {
        ...
        let work_in_progress_borrow = work_in_progress_cloned.borrow();
        let _type = work_in_progress_borrow._type.as_ref().unwrap();
        let props = work_in_progress_borrow.pending_props.as_ref().unwrap();
        let component = JsValue::dyn_ref::<Function>(_type).unwrap();
        let children = component.call1(&JsValue::null(), props);
        children
    }
}

由于函数执行是有可能抛出异常的,Rust 中没有 try catch,而是使用 Result<T, E> 这种 enum 来处理异常,其中 T 表示返回值,E 表示抛出的异常。它包括两种变体,Ok(T) 表示正常的返回,Err(E) 表示返回错误:

rust 复制代码
enum Result {
  Ok(T),
  Err(E)
}

我们可以通过 match 来处理返回值或抛出的异常,比如下面这个例子:

rust 复制代码
fn fn1() -> Result<(), String> {
    Err("a".to_string())
}

fn my_fn() {
    match fn1() {
        Ok(return_value) => {
            println!("return: {:?}", return_value)
        }
        Err(e) => {
            println!("error: {:?}", e)
        }
    }
}

fn main() {
    my_fn();
}

还可以使用 ? 操作符来对错误进行传播,比如下面这个例子:

rust 复制代码
fn fn1() -> Result<(), String> {
    Err("a".to_string())
}

fn my_fn() -> Result<String, String> {
    fn1()?;
    Ok("my_fn succeed".to_string())
}


fn main() {
    match my_fn() {
        Ok(return_value) => {
            println!("return: {:?}", return_value)
        }
        Err(e) => {
            println!("error: {:?}", e)
        }
    }
}

其中 fn1()?; 等价于:

rust 复制代码
match fn1() {
    Ok(return_value) => {
        return_value
    }
    Err(e) => {
        return Err(e.into())
    }
};

回到我们的代码,component.call1(&JsValue::null(), props) 执行后返回类型为 Result<JsValue, JsValue>,我们需要将抛出的错误传播到 perform_sync_work_on_root 中进行处理:

rust 复制代码
fn perform_sync_work_on_root(&mut self, root: Rc<RefCell<FiberRootNode>>) {
  ...
  loop {
      match self.work_loop() {
          Ok(_) => {
              break;
          }
          Err(e) => {

              self.work_in_progress = None;
          }
      };
  }
  ...
}

所以,他们之间的函数返回值都需要改成 Result,比如 begin_work 需要改成这样:

rust 复制代码
pub fn begin_work(work_in_progress: Rc<RefCell<FiberNode>>) -> Result<Option<Rc<RefCell<FiberNode>>>, JsValue> {
    let tag = work_in_progress.clone().borrow().tag.clone();
    return match tag {
        WorkTag::FunctionComponent => update_function_component(work_in_progress.clone()),
        WorkTag::HostRoot => Ok(update_host_root(work_in_progress.clone())),
        WorkTag::HostComponent => Ok(update_host_component(work_in_progress.clone())),
        WorkTag::HostText => Ok(None),
    };
}

又比如 perform_unit_of_work 修改了返回值,并使用了 ? 来传播错误:

rust 复制代码
fn perform_unit_of_work(&mut self, fiber: Rc<RefCell<FiberNode>>) -> Result<(), JsValue> {
    let next = begin_work(fiber.clone())?;
    ...
}

接着是 complete_work,对于 FunctionComponent,很简单,只需要执行 bubble_properties 即可:

rust 复制代码
...
WorkTag::FunctionComponent => {
    self.bubble_properties(work_in_progress.clone());
    None
}
...

最后到 Commit,无需改动,非常 Nice。

接下来,我们来测试一下,下面是 Demo:

js 复制代码
// App.tsx
import dayjs from 'dayjs'

function App() {
  return (
    <div>
      <Comp>{dayjs().format()}</Comp>
    </div>
  )
}

function Comp({children}) {
  return (
    <span>
      <i>{`Hello world, ${children}`}</i>
    </span>
  )
}

export default App

// main.tsx
import {createRoot} from 'react-dom'
import App from './App.tsx'

const root = createRoot(document.getElementById('root'))
root.render(<App />)

重新构建并安装依赖,运行后可以看到如下效果:

日志以及页面渲染结果均正常。

然后,我们再来测试下抛出异常的场景:

js 复制代码
function App() {
  return (
    <div>
      {/* will throw error */}
      <Comp>{dayjs.format()}</Comp>
    </div>
  )
}

目前我们还没有对异常进行处理,只是中断了 Render 过程而已。

这样,首次渲染对于 FunctionComponent 的支持就完成了,由于目前还未实现 Hooks,所以需要修改的地方并不多。

跪求 star 并关注公众号"前端游"

相关推荐
六卿43 分钟前
react防止页面崩溃
前端·react.js·前端框架
许野平1 小时前
Rust: enum 和 i32 的区别和互换
python·算法·rust·enum·i32
z千鑫1 小时前
【前端】详解前端三大主流框架:React、Vue与Angular的比较与选择
前端·vue.js·react.js
小白学前端6663 小时前
React Router 深入指南:从入门到进阶
前端·react.js·react
outstanding木槿3 小时前
react+antd的Table组件编辑单元格
前端·javascript·react.js·前端框架
等一场春雨4 小时前
springboot 3 websocket react 系统提示,选手实时数据更新监控
spring boot·websocket·react.js
风无雨4 小时前
react杂乱笔记(一)
前端·笔记·react.js
前端小魔女4 小时前
2024-我赚到自媒体第一桶金
前端·rust
北海天空5 小时前
reactHooks到底钩到了什么?
前端·react.js
飞翔的渴望5 小时前
react18与react17有哪些区别
前端·javascript·react.js