跨框架实现方案
Web Components 简介
- Custom Elements
- Shadow DOM
- HTML Template
使用Stencil
使用原生语法去编写 Web Components 相当繁琐,因此我们需要一个框架帮助我们提高开发效率和开发体验。
<template id="template">
<h1>Hello World!</h1>
</template>
class App extends HTMLElement {
constructor () {
super(...arguments)
// 开启 Shadow DOM
const shadowRoot = this.attachShadow({ mode: 'open' })
// 复用 <template> 定义好的结构
const template = document.querySelector('#template')
const node = template.content.cloneNode(true)
shadowRoot.appendChild(node)
}
}
window.customElements.define('my-app', App)
在 React 与 Vue 中使用 Stencil
兼容 React
React 使用 setAttribute 的形式给 Web Components 传递参数。当参数为原始类型时是可以运行的,但是如果参数为对象或数组时,由于 HTML 元素的 attribute 值只能为字符串或 null,最终给 WebComponents 设置的 attribute 会是 attr="[object Object]"。
const reactifyWebComponent = WC => {
return class extends React.Component {
ref = React.createRef()
update () {
Object.entries(this.props).forEach(([prop, val]) => {
if (prop === 'children' || prop === 'dangerouslySetInnerHTML') {
return
}
if (prop === 'style' && val && typeof val === 'object') {
for (const key in val) {
this.ref.current.style[key] = val[key]
}
return
}
this.ref.current[prop] = val
})
}
componentDidUpdate () {
this.update()
}
componentDidMount () {
this.update()
}
render () {
const { children, dangerouslySetInnerHTML } = this.props
return React.createElement(WC, {
ref: this.ref,
dangerouslySetInnerHTML
}, children)
}
}
}
const MyComponent = reactifyWebComponent('my-component')
children、dangerouslySetInnerHTML 属性需要透传。
React 中 style 属性值可以接受对象形式,这里需要额外处理
Events
通过 ref 获取到Web Compoent 元素,通过 Object.entries(this.props) 遍历props,手动 addEventListenr 绑定事件
const reactifyWebComponent = WC => {
class Index extends React.Component {
update (prevProps) {
Object.entries(this.props).forEach(([prop, val]) => {
if (prop.toLowerCase() === 'classname') {
this.ref.current.className = prevProps
// getClassName 在保留内置类名的情况下,返回最新的类名
? getClassName(this.ref.current, prevProps, this.props)
: val
return
}
...
})
}
componentDidUpdate (prevProps) {
this.update(prevProps)
}
componentDidMount () {
this.update()
}
...
}
return React.forwardRef((props, ref) => (
React.createElement(Index, { ...props, forwardRef: ref })
))
}
Ref
domRef 会获取到 MyComponent,而不是
使用 forwardRef 传递 ref。
const reactifyWebComponent = WC => {
class Index extends React.Component {
...
render () {
const { children, forwardRef } = this.props
return React.createElement(WC, {
ref: forwardRef
}, children)
}
}
return React.forwardRef((props, ref) => (
React.createElement(Index, { ...props, forwardRef: ref })
))
}