You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
importReactfrom'react'import"./styles.css";constShadowView: React.FC=({
children
})=>{constattachShadowRef=React.createRef<HTMLDivElement>()React.useEffect(()=>{consthost=attachShadowRef.currentconstroot=host?.attachShadow({mode: "open"});[].slice.call(host?.children).forEach(child=>{root.appendChild(child);});},[attachShadowRef,children])return<divref={attachShadowRef}>{children}</div>;}exportdefaultfunctionApp(){return(<divclassName="App"onClick={()=>{console.log('app click')}}><ShadowView><h2onClick={()=>{console.log('shadow dom inner click')}}>shadowdom</h2></ShadowView></div>);}
我们在 shadow dom 内部定义了 click 事件,当我们点击时会发现这个内部 click 事件并没有执行:
按照上面的原理分析,核心还是事件源不对,最容易想到的方式就是我们手动在 shadow dom 中监听这个事件,然后再次派发到对应的 dom 中。
在 React 内部给每个 dom 节点绑定了一个 __reactEventHandlers 的属性,通过这个属性可以获取到这个 dom 所有绑定的事件,再手动执行即可。
我们在 shadow dom 中添加这个事件派发过程:
constShadowView: React.FC=({
children
})=>{constattachShadowRef=React.createRef<HTMLDivElement>()React.useEffect(()=>{consthost=attachShadowRef.currentconstroot=host?.attachShadow({mode: "open"});[].slice.call(host?.children).forEach(child=>{root.appendChild(child);});constdispatchEvent=()=>{console.log('root click')root.childNodes.forEach(node=>{lethandlerKey=Object.keys(node).find(key=>key.includes('__reactEventHandlers'))if(!handlerKey){return}node[handlerKey]?.onClick?.()})}root?.addEventListener('click',dispatchEvent)return()=>{root?.removeEventListener('click',dispatchEvent)}},[attachShadowRef,children])return<divref={attachShadowRef}>{children}</div>;}
Shadow DOM 是在 web component 中常提到的概念,其核心作用就是做到与 shadow host 之外的代码做到隔离,把内部结构、样式、行为做隐藏,相当于是一个沙箱的。早期浏览器会用这个特性来封装一些内部标签,比如 video 标签:
在 chrome dev tool 中可以在设置中配置我们允许查看 shadow dom,设置路径为:settings -> preferences -> Elements -> show user agent shadow DOM
开启前:
开启后:
在实际使用中我们只需要将对应的 shadow host 节点使用 attachShadow 方法开启即可:
效果:
由于 shadow dom 隔离的特性,shadow dom 内部事件被外部捕获的时候,event 的 target 将会被重定向为 shadow host 元素,如下:
效果:
这种事件重定向是必要的,因为对于 shadow dom 外部来说,他的眼里只有一个元素,不关心内部的实现,就像我们使用 video 标签一样,我们只认为这是一个和 div 标签一样是一个单一的标签。
但这种重定向对于 React 17之前来说可能就不是那么友好了。众所周知,React 的事件是一个合成事件。
在 DOM 原生事件中,事件是先捕获再冒泡的:
在 React 17 之前,React 的合成事件都是委托在 document 上并且在冒泡阶段执行的,由于 shadow dom 事件重定向的缘故,最终会被认为是 shadow host 是事件源,看以下例子:
我们在 shadow dom 内部定义了 click 事件,当我们点击时会发现这个内部 click 事件并没有执行:
按照上面的原理分析,核心还是事件源不对,最容易想到的方式就是我们手动在 shadow dom 中监听这个事件,然后再次派发到对应的 dom 中。
在 React 内部给每个 dom 节点绑定了一个 __reactEventHandlers 的属性,通过这个属性可以获取到这个 dom 所有绑定的事件,再手动执行即可。
我们在 shadow dom 中添加这个事件派发过程:
效果:
可以看到正常执行了。
这也是大部分的解决思路,事实上,社区为了解决这个问题,有专门的库 react-shadow-dom-retarget-events ,代码量也非常少:
当然,React 也意识到了这个问题,所以在 React 17 之后他专门把事件统一绑定到了 ReactDOM.render 方法的第二个参数中,我们直接升级到 react 17 之后的版本就能直接解决这个问题。
The text was updated successfully, but these errors were encountered: