Table of Contents
三大框架都能自定义组件,也有 css 隔离方案。但组件不通用,css 隔离性也不是很好。如果要加载一段未知的 html,这段 html 里还包含了 style 或者 link 标签,框架自带的组件方案就不好解决了。常用的解决方案是 iframe。iframe 的隔离性非常好,但交互不方便,总是通过 postMessage 也比较麻烦。如果放弃 ie 的话,还可以使用浏览器原生提供的 Web Components 功能。
Web Components 可以让用户自定义元素,css 隔离性也较好,交互也方便。
自定义元素
比如我定义了一个 dict-content 的元素。
export default class DictContent extends HTMLElement {
static get observedAttributes() { return ['text'] }
constructor() {
super()
const shadowRoot = this.attachShadow({ mode: 'open' })
shadowRoot.innerHTML = ``
}
get text() {
return this.getAttribute('text' ||'')
}
set text(val) {
this.setAttribute('text', val)
}
connectedCallback() {
this.text = this.text || ''
}
attributeChangedCallback (name, oldValue, newValue) {
if (name === 'text') {
this.shadowRoot.innerHTML = newValue
setTimeout(() => {
const els = this.shadowRoot.querySelectorAll('a')
;[].map.call(els, el => {
if (el.attributes.href?.value?.startsWith('entry://')) {
let entry = el.attributes.href.value.replace('entry://', '')
el.setAttribute('href', 'javascript: void(0)')
el.onclick = () => {
this.dispatchEvent(new CustomEvent('click-entry', {
detail: {
entry
}
}))
console.log(entry)
}
}
})
}, 0)
}
}
}
if(!customElements.get('dict-content')){
customElements.define('dict-content', DictContent);
}
自定义元素命名必须包含连字符,内部有 dom 就需要使用 shadowRoot。事件可以通过 dispatchEvent 和 CustomEvent 实现。
参照 mdn 文档(Web Components | MDN (mozilla.org))和已有的库(XboxYan/xy-ui: 🎨面向未来的原生 web components UI组件库 (github.com))很容易实现一个自定义元素。
vue3 里使用
如果要在 vue3 里使用自定义元素,需要告诉 vue 哪些标签是自定义元素(不设置也没关系,vue3 会自动回退)。如果是 script 引入的 vue3,添加如下代码就可以:
app.config.compilerOptions.isCustomElement = tag => ['dict-content'].includes(tag)
如果是 webpack,并且使用的是 runtimeOnly 版本(默认就是),需要在 vue.config.js 里配置:
module.exports = {
chainWebpack: config => {
config.module
.rule('vue')
.use('vue-loader')
.tap(options => ({
...options,
compilerOptions: {
// 排除自定义元素处理
isCustomElement: tag => ['dict-content'].includes(tag)
}
}))
}
}
设置以后 vue 就不会把自定义的原生元素当成 vue 组件了。