从零实现 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,则 pushbubble 这个列表中,是 onClickCapture 的话则 insertcapture 这个列表中:

然后先从头到尾依次执行 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 并关注公众号"前端游"

相关推荐
gaolei_eit22 分钟前
Vue3项目ES6转ES5,兼容低版本的硬件设备,React也
javascript·react.js·es6
百锦再7 小时前
Reactive编程入门:Project Reactor 深度指南
前端·javascript·python·react.js·django·前端框架·reactjs
Ashley的成长之路7 小时前
2025 年最新:VSCode 中提升 React 开发效率的必备插件大全
ide·vscode·react.js·工作提效·react扩展
百锦再7 小时前
React编程高级主题:测试代码
android·前端·javascript·react.js·前端框架·reactjs
光影少年10 小时前
react的hooks防抖和节流是怎样做的
前端·javascript·react.js
m0_7190841112 小时前
React笔记张天禹
前端·笔记·react.js
Ziky学习记录12 小时前
从零到实战:React Router 学习与总结
前端·学习·react.js
青青家的小灰灰13 小时前
React 19 核心特性与版本优化深度解析
react.js
却尘13 小时前
Next.js 请求最佳实践 - vercel 2026一月发布指南
前端·react.js·next.js
布列瑟农的星空14 小时前
前端都能看懂的Rust入门教程(三)——控制流语句
前端·后端·rust