从零实现 React v18,但 WASM 版 - [11] 实现事件系统

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

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

本文对应 tag:v11

没有事件系统的 React 是没有灵魂的,用户完全无法进行交互,所以这篇文章我们以 click 事件为例来介绍如何实现。

以下面的代码为例,我们先来回顾一下,正版的 React 是怎么实现的。

js 复制代码
const App = () => {
  innerClick = () => {
    console.log('A: react inner click.')
  }

  outerClick = () => {
    console.log('B: react outer click.')
  }

  return (
    <div id='outer' onClickCapture={this.outerClick}>
      <button id='inner' onClick={this.innerClick}>
        button
      </button>
    </div>
  )
}

当事件在根元素上触发的时候,我们可以拿到原生事件对象 NativeEvent,通过 target 可以访问到当前点击的元素 button,通过其属性 __reactFiber$*****(*表示随机数)可以获取 button 所对应的 FiberNode。同时,React 还会利用 NativeEvent 来生成 SyntheticEvent,其中 SyntheticEvent 有几个重要的属性值得关注:

  • nativeEvent,指向 NativeEvent。
  • _dispatchListeners,存储要执行的事件监听函数。
  • _dispatchInstances,存储要执行的事件监听函数所属的 FiberNode 对象。

接下来分别按照捕获和冒泡两个阶段来收集要执行的事件监听函数:

最后,按照顺序执行 _dispatchListeners 中的方法,并通过 _dispatchInstances 中的 FiberNode 来得到对应的 stateNode 作为 SyntheticEvent 上的 currentTarget。

SyntheticEvent 上也有 stopPropagation 方法,调用它以后 _dispatchListeners 后面的方法就不会执行了,从而达到了阻止事件传播的效果。

React 事件系统的介绍就到这,更多内容可以参考这篇文章。

不过,big react 的实现方式跟正版的 React 不同,它是这样做的。

在 complete work 阶段,创建 FiberNode 节点的 stateNode 时,将 FiberNode 节点上的事件监听函数复制到 Element 上:

当事件触发时,通过 NativeEvent 上的 target 一路往上收集事件监听函数,如果是 onClick,则 push 到 bubble 这个列表中,是 onClickCapture 的话则 insert 到 capture 这个列表中:

然后先从头到尾依次执行 capture 中的事件监听函数,再从头到尾依次执行 bubble 中的事件监听函数。

阻止事件传播是怎么实现的呢?答案是修改了 NativeEvent 上的该方法:

rust 复制代码
fn create_synthetic_event(e: Event) -> Event {
    Reflect::set(&*e, &"__stopPropagation".into(), &JsValue::from_bool(false));

    let e_cloned = e.clone();
    let origin_stop_propagation = derive_from_js_value(&*e, "stopPropagation");
    let closure = Closure::wrap(Box::new(move || {
        // set __stopPropagation to true
        Reflect::set(
            &*e_cloned,
            &"__stopPropagation".into(),
            &JsValue::from_bool(true),
        );
        if origin_stop_propagation.is_function() {
            let origin_stop_propagation = origin_stop_propagation.dyn_ref::<Function>().unwrap();
            origin_stop_propagation.call0(&JsValue::null());
        }
    }) as Box<dyn Fn()>);
    let function = closure.as_ref().unchecked_ref::<Function>().clone();
    closure.forget();
    Reflect::set(&*e.clone(), &"stopPropagation".into(), &function.into());
    e
}

fn trigger_event_flow(paths: Vec<Function>, se: &Event) {
    for callback in paths {
        callback.call1(&JsValue::null(), se);
        // If __stopPropagation is true, break
        if derive_from_js_value(se, "__stopPropagation")
            .as_bool()
            .unwrap()
        {
            break;
        }
    }
}

不过这里还有个问题,就是 currentTarget 并没有像正版的 React 那样进行修正,这里的 currentTarget 一直都是根元素,因为事件监听函数是绑定在该元素上的:

rust 复制代码
pub fn init_event(container: JsValue, event_type: String) {
  ...
  let element = container
      .clone()
      .dyn_into::<Element>()
      .expect("container is not element");
  let on_click = EventListener::new(&element.clone(), event_type.clone(), move |event| {
      dispatch_event(&element, event_type.clone(), event)
  });
  on_click.forget();
}

本次更新详见这里。

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

相关推荐
leikooo11 小时前
用餐厅和盘子理解栈和堆
java·数据结构·链表·rust
@大迁世界18 小时前
JEV RAG:更高效的 RAG 系统解决方案?
开发语言·rust
进击的雷神1 天前
OpenClaw 太重、启动太慢?我试了试这个 Rust 重写的轻量版 AI 助手网关
开发语言·人工智能·rust·zeroclaw
薛一半1 天前
React-Redux核心原理与容器组件实战
前端·javascript·react.js
选择即世界1 天前
高性能的 SGLang Rust 运行时
rust
FungLeo1 天前
成为全栈·Next.js 网站前台篇·评论系统:叠楼、回复、删除与内容审核如何落到前台
react.js·内容审核·next.js·评论系统·树形数据·成为全栈
Alice-YUE1 天前
React 渲染机制速通:Render 树、Fiber 树与更新调度
前端·react.js·前端框架·react·fiber·前端性能·渲染机制
Ramble_Naylor2 天前
async/await:让一个线程同时等很多件事
开发语言·rust