likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 5

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

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

本文对应 tag:v7

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

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

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

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) 表示返回错误:

enum Result {
  Ok(T),
  Err(E)
}

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

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();
}

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

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()?; 等价于:

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 中进行处理:

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 需要改成这样:

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 修改了返回值,并使用了 ? 来传播错误:

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

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

...
WorkTag::FunctionComponent => {
    self.bubble_properties(work_in_progress.clone());
    None
}
...

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

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

// 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 />)

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

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

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

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

function App() {
  return (
    <div>
      {/* will throw error */}
      <Comp>{dayjs.format()}</Comp>
    </div>
  )
}

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

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

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

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

转载自:https://juejin.cn/post/7359408423744733222
评论
请登录