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

相关推荐
天涯学馆8 小时前
解锁WebAssembly与JavaScript交互的无限可能
前端·webassembly
哑巴语天雨8 小时前
React+Vite项目框架
前端·react.js·前端框架
初遇你时动了情8 小时前
react 项目打包二级目 使用BrowserRouter 解决页面刷新404 找不到路由
前端·javascript·react.js
码农老起9 小时前
掌握 React:组件化开发与性能优化的实战指南
react.js·前端框架
前端没钱9 小时前
从 Vue 迈向 React:平滑过渡与关键注意点全解析
前端·vue.js·react.js
高山我梦口香糖12 小时前
[react] <NavLink>自带激活属性
前端·javascript·react.js
撸码到无法自拔12 小时前
React:组件、状态与事件处理的完整指南
前端·javascript·react.js·前端框架·ecmascript
高山我梦口香糖12 小时前
[react]不能将类型“string | undefined”分配给类型“To”。 不能将类型“undefined”分配给类型“To”
前端·javascript·react.js
乐闻x15 小时前
VSCode 插件开发实战(四):使用 React 实现自定义页面
ide·vscode·react.js
irisMoon0615 小时前
react项目框架了解
前端·javascript·react.js