背景:
在做一个低UI工具时,为了减少体积和渲染成本,使用了 SolidJS 去代替 Vue,React。Solid 遵循与 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 效果
on: 或者 oncapture: 获取 event.target 效果 (只能拿到虚拟Dom)
编译后对比
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 的其他框架可能也会遇到这个问题