SolidJS中代理事件对event.target的影响

162 阅读1分钟

背景:

在做一个低UI工具时,为了减少体积和渲染成本,使用了 SolidJS 去代替 VueReactSolid 遵循与 React 相同的理念,但体积只有 7KB(压缩 + Gzip)。由于工具是嵌入在其他web项目中运行的,我使用默认 onClick 影响了 event.target 的值从对业务造成了开发环境与线上环境不一致。默认情况下,点击到 shadow-dom ,会返回 shadow-root,但 SolidJS 中的代理事件会得到真正的触发元素

解决方案

根据官方文档:SolidJS 提供不使用委托方式的指令:on:xxx or oncapture:xxx

分析过程

由于React写的多,我默认就用了这种写法

<button onClick={handleClick} {...otherProps}>{props.children}</button>

改写后使用on: 或者 oncapture:

<button on:click={handleClick} {...otherProps}>{props.children}</button>
or
<button oncapture:click={handleClick} {...otherProps}>{props.children}</button>

获取 event.target对比

onClick 获取 event.target 效果

image.png

on: 或者 oncapture: 获取 event.target 效果 (只能拿到虚拟Dom)

image.png

编译后对比

onClick编译后, 将事件存在私有变量上$$click

var $eb8a8ae902707b7d$export$353f5b6fc5456de1 = function(props) {
    var // className = '',
    // type = 'default',
    onClick = props.onClick, otherProps = $eb8a8ae902707b7d$var$__rest(props, [
        "onClick"
    ]);
    var handleClick = function(ev) {
        if (onClick) onClick(ev);
    };
    return function() {
        var _el$ = $eb8a8ae902707b7d$var$_tmpl$.cloneNode(true);
        _el$.$$click = handleClick;
        $7e9be51355b034cf$export$3ae0fd4797ed47c8(_el$, otherProps, false, true);
        $7e9be51355b034cf$export$21a5ca8aa77d35ff(_el$, function() {
            return props.children;
        });
        return _el$;
    }();
};

on: 或者 oncapture: 编译后, 使用了 addEventListener

var $eb8a8ae902707b7d$export$353f5b6fc5456de1 = function(props) {
    var // className = '',
    // type = 'default',
    onClick = props.onClick, otherProps = $eb8a8ae902707b7d$var$__rest(props, [
        "onClick"
    ]);
    var handleClick = function(ev) {
        if (onClick) onClick(ev);
    };
    return function() {
        var _el$ = $eb8a8ae902707b7d$var$_tmpl$.cloneNode(true);
        _el$.addEventListener("click", handleClick);
        $7e9be51355b034cf$export$3ae0fd4797ed47c8(_el$, otherProps, false, true);
        $7e9be51355b034cf$export$21a5ca8aa77d35ff(_el$, function() {
            return props.children;
        });
        return _el$;
    }();
};

代理处理事件发现 e.target !== node 会修改target

function eventHandler(e) {
  const key = `$$${e.type}`;
  let node = (e.composedPath && e.composedPath()[0]) || e.target;
  // reverse Shadow DOM retargetting
  if (e.target !== node) {
    Object.defineProperty(e, "target", {
      configurable: true,
      value: node
    });
  }

  // simulate currentTarget
  Object.defineProperty(e, "currentTarget", {
    configurable: true,
    get() {
      return node || document;
    }
  });

  // cancel html streaming
  if (sharedConfig.registry && !sharedConfig.done) sharedConfig.done = _$HY.done = true;

  while (node) {
    const handler = node[key];
    if (handler && !node.disabled) {
      const data = node[`${key}Data`];
      data !== undefined ? handler.call(node, data, e) : handler.call(node, e);
      if (e.cancelBubble) return;
    }
    node = node._$host || node.parentNode || node.host;
  }
}

event.composedPath()是一个方法,用于获取事件触发时事件路径上的所有节点。它返回一个由事件路径上的节点组成的数组,数组中的第一个元素是事件目标节点,最后一个元素是文档的根节点(<html>元素或<shadow-root>元素)

最后,我猜测支持 Web Components 的其他框架可能也会遇到这个问题