从零实现 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 并关注公众号"前端游"

相关推荐
化作繁星3 小时前
如何在 React 中测试高阶组件?
前端·javascript·react.js
初遇你时动了情3 小时前
react module.scss 避免全局冲突类似vue中scoped
vue.js·react.js·scss
Au_ust3 小时前
千峰React:函数组件使用(2)
前端·javascript·react.js
来一碗刘肉面4 小时前
React - ajax 配置代理
前端·react.js·ajax
Hello.Reader6 小时前
深入理解 Rust 的 `Rc<T>`:实现多所有权的智能指针
开发语言·后端·rust
yoona10206 小时前
Rust编程语言入门教程(八)所有权 Stack vs Heap
开发语言·后端·rust·区块链·学习方法
界面开发小八哥6 小时前
可视化工具SciChart如何结合Deepseek快速创建一个React仪表板?
react.js·信息可视化·数据可视化·原生应用·scichart
guyoung7 小时前
DeepSeek轻量级本地化部署工具——AIMatrices DeepSeek
rust·llm·deepseek
Java知识技术分享9 小时前
使用LangChain构建第一个ReAct Agent
python·react.js·ai·语言模型·langchain
谢尔登11 小时前
【React】React 性能优化
前端·react.js·性能优化