跨框架实现方案

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 })
  ))
}

posted @ 2020-04-26 17:39  浮云随笔  阅读(257)  评论(0编辑  收藏  举报