背景
前端组件库跨框架是什么?
前端组件库跨框架是指在不同的前端框架(如 React、Vue、Solid 等)之间共享和复用组件的能力。这种能力可以让开发者在不同的项目中使用同一套组件库,从而提高开发效率和代码复用性。
为什么需要做前端组件库跨框架?
首先,不同的前端框架有不同的语法和 API,如果每个框架都要写一套组件库,那么开发成本和维护成本都会很高。其次,跨框架的组件库可以让开发者更加灵活地选择框架,而不必担心组件库的兼容性问题。而 TinyVue 组件库是市面上实现的一种跨框架组件库实现方案,接下来我们用另一个角度,通过最近比较火的web component去探究一下可行性
web component
Web Components是由W3C Web Applications工作组提出的,最初于2011年在Web Components的规范草案中进行了描述。Web Components的概念被认为是一种在Web应用程序中开发可重用的自定义元素和组件的技术,旨在解决现有Web开发中存在的命名空间冲突、作用域问题和CSS样式污染等问题。最终它被分为三个部分:Custom Elements、Shadow DOM和HTML Templates,并在W3C标准化的过程中不断演变和改进。
虽然Web Components的概念最早于2011年提出,但是直到近些年(大约在2019年以后),随着浏览器支持的增加和相关框架(如React、Vue、Angular等)对其支持的改进,Web Components才开始得到广泛应用。
概念和基本用法
Custom elements(自定义元素):JavaScript API,允许定义custom elements及其行为,然后可以在我们的用户界面中按照需要使用它们。
Shadow DOM(影子DOM):JavaScript API,用于将封装的“影子”DOM树附加到元素(与主文档DOM分开呈现)并控制其关联的功能。通过这种方式,开发者可以保持元素的功能私有,这样它们就可以被脚本化和样式化,而不用担心与文档的其他部分发生冲突。
HTML templates(HTML模板):和元素使开发者可以编写与HTML结构类似的组件和样式。然后它们可以作为自定义元素结构的基础被多次重用。
eg:
<body>
<test-item-shadow></test-item-shadow>
<div class="container">Test item</div>
<style>
.container {
color: red !important;
}
</style>
<template id="tpl">
<style>
.container {
color: blue;
}
</style>
<div class="container">Test Item</div>
</template>
<script>
class TestItemShadow extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
const content = document.getElementById("tpl").content.cloneNode(true);
const shadow = this.attachShadow({ mode: "open" });
shadow.append(content);
}
}
window.customElements.define("test-item-shadow", TestItemShadow); </script>
</body>
ps:通过浏览器开发者工具,可以查看html的结构,生成了一个#shadow-root(open)标签,内部包含了我们自定义的组件,不会受到外部影响。
-
HTML template(HTML 模板):
<template>和<slot>元素使你可以编写不在呈现页面中显示的标记模板。然后它们可以作为自定义元素结构的基础被多次重用。
与vue封装组件的区别
| 区别 | Web Components | Vue封装组件 |
|---|---|---|
| 技术标准 | 一组标准化的浏览器API,包括自定义元素、Shadow DOM和HTML模板等 | 一个流行的JavaScript框架,提供了数据驱动的视图和组件系统 |
| 模板语法 | 相对较繁琐,并且需要使用JavaScript代码来动态操作DOM | 直观简洁的模板语法,易于描述组件的结构和行为 |
| 状态管理 | 没有预置的状态管理机制,需要开发者自己实现 | 提供了data、computed和watch等方式来管理组件的状态 |
| 兼容性 | 不同浏览器对Web Components的支持程度不同,可能会存在兼容性问题 | 可以在不同的浏览器和平台上正常运行,并提供了一致的开发体验 |
| 组件间通信 | 需要开发者自己实现组件间通信机制 | 可以通过props和自定义事件等方式来实现组件之间的通信 |
Web Components和Vue封装组件都可以用于创建可重用的自定义组件,但它们之间的区别在于技术标准、模板语法、状态管理、兼容性和组件间通信等方面。具体使用哪个方案,需要根据实际情况进行选择。如果需要更高的开发效率和更好的开发体验,可以选择使用Vue开发组件;如果需要更大的自由度和更强的跨平台兼容性,可以选择使用Web Components。
vue官方认为两者是互补的技术,vue3原生将Web Components结合了进来
Vue3+Web Components
Vue3在原生支持Web Components标准的自定义元素上进行了重大改进,并提供了更好的桥接能力,使得Vue3中的组件可以无缝地转换为Web Components标准的自定义元素,而无需使用第三方库。这使得Vue3更加适合于使用Web Components构建跨平台应用。
使用Vue3+Web Components封装组件库方案是一个更具有潜力和前瞻性的方案。Vue3提供了数据驱动的组件系统,并支持将Vue组件转换为符合 Web Components 标准的自定义元素,这使得 Vue 组件可以被其他框架或者原生的HTML应用所使用。Web Components则提供了标准化的浏览器API,可让开发者构建具有高度复用性和可扩展性的自定义组件。
Vue3+Web Components封装组件库方案具有以下优点:
- 跨平台兼容:由于Web Components标准是由W3C组织制定的,因此可以在所有现代浏览器中运行,并且不依赖于任何特定的JavaScript框架或库。
- 可重用性:由于Web Components具有标准化的API,因此组件具有高度的可重用性,并且可以轻松地在不同项目之间共享。
- 独立性:Vue3+Web Components封装组件库方案中的组件是独立的,不依赖于任何其他框架或库。这使得它们适用于各种不同的项目和应用程序,并且可以在不同的技术栈之间共享。
综上所述,Vue3+Web Components封装组件库方案是一个更具前瞻性和潜力的方案
具体实现
其中style展开是
web component实现微前端
见之前文章 juejin.cn/post/734609…
对webcomponent的一些思考
基础组件库 市面上已经有很多基于 Web Components 实现的跨框架 UI 组件库。 它可以同时在任意框架或无框架中使用。
- Svelte
svelte是目前比较火的前端框架,有一个特点就是可以自定义组件转成通用的web组件(web component)。在多团队协同完成的大项目中,各个团队可能使用不同的框架版本,甚至不同的框架,这让不同项目之间的组件复用变得困难。这种情况下Svelte就变成了沟通跨越框架鸿沟的桥梁,使用Svelte开发的无框架依赖的Web Components,可以在各个框架间复用。
- 微前端
微前端有几个基本概念: 技术栈无关、应用间隔离、独立开发。目前 Web Components 都符合。在一些微前端方案里就采用了Web Component的模式。
…
Web Component的缺点
再日常开发中,难免会遇到需要调整组件内部样式的时候,但由于shadowDOM的隔离机制,会导致我们很难去修改内部的样式。 在一些复杂的组件中,数据通信和事件传递存在一定使用成本。
| 问题 | 描述 | 原因 |
|---|---|---|
| 兼容性问题 | Vue 3 对 Web Component 的支持相对较弱,特别是在使用自定义事件和插槽时需要特殊处理,在不同浏览器上的兼容性也需要考虑。 | Vue 3 还不太稳定,Web Component 的规范也还在不断更新,这导致兼容性方面的问题比较多。 |
| 组件样式冲突 | 在使用 Web Component 开发组件库时,组件的样式通常是封装在 Web Component 内部的,但是在 Vue 3 中,scoped 样式仅限于 Vue 组件内部,如果 Web Component 和 Vue 组件样式共存,容易出现冲突。 | scoped 样式的限制也导致 Web Component 开发中的一些样式问题。 |
| 数据驱动问题 | 在 Vue 3 中,数据驱动是通过 props 和 emit 来实现的,但是在 Web Component 中,数据驱动需要自己实现,这增加了开发难度。 | 在使用 Web Component 开发组件库时,需要考虑数据驱动方面的问题,这也增加了开发的难度。 |
| 模板处理问题 | 在 Vue 3 中,可以使用模板来将复杂的 HTML 结构封装起来,但是在 Web Component 中,模板的处理需要自己实现。 | Web Component 不像 Vue 组件一样拥有内置的模板处理机制,这也增加了开发的难度。 |
| 文件大小问题 | 使用 Web Component 开发组件库时,每个组件都需要封装成单独的 Web Component,这会导致组件库的文件大小增加。 | 这是由 Web Component 的封装机制所决定的,同时也会影响到组件库的性能。 |
一些注意点
v-model的区别
在Vue2中,当我们在自定义组件中使用v-model指令时,它会被展开为一个名为value的prop属性和一个名为input的自定义事件。这个prop属性是用来将父组件中的数据传递给子组件的,而子组件通过触发input事件来将自己的数据传递回父组件。
也就是说,在Vue2中,v-model指令本质上是语法糖,用来简化父子组件之间双向数据绑定的操作。使用v-model指令时,只需要在父组件中使用v-model绑定子组件的值,然后在子组件中定义一个value prop和一个input事件,就可以实现父子组件之间的双向数据绑定了。
<my-component v-model="someValue"></my-component>`
它是下面这种写法的简写:
<my-component :value="someValue" @input="someValue = $event"></my-component>
而在Vue3中,v-model指令默认绑定到名为modelValue的prop属性和名为update:modelValue的自定义事件上,用于实现组件的双向绑定。与Vue2不同的是,Vue3中的v-model指令不再是简单的语法糖,而是被重构为一个真正的指令,其实现方式更加灵活。
<my-component v-model = "title"></my-component>`
它是下面这种写法的简写:
<my-component :modelValue = "title" @update:modelValue = "title = $event"></my-component>`
因此,Vue3中的v-model指令可以像Vue2一样实现父子组件之间的双向绑定,也可以通过其他方式实现双向绑定。例如,我们可以通过在父组件中使用v-bind绑定一个modelValue prop和一个update:modelValue事件来与子组件进行双向绑定。这种方式和Vue2中的v-model指令的实现相似,但更具有灵活性。
vue2组件迁移到vue3 v-model 的问题
解释:在你试图在一个
v-model上使用一个已经被定义为prop的属性。具体来说,这可能是因为你想在一个自定义组件中使用v-model,而这个组件的props中有一个与v-model绑定的同名属性。
在Vue2中,当我们在一个子组件上使用v-model指令时,Vue会自动将该组件的value属性作为prop传递给父组件,并且在值发生变化时触发input事件来更新这个value属性的值。如果组件中定义了一个与v-model绑定同名的prop属性,Vue会自动忽略这个prop属性
如果在Vue3版本中同时使用了v-model指令和同名的属性,则会出现类似于“v-model cannot be used on a prop that is already used on the component”的报错。
这是因为vue3更好地遵循单向数据流的思想,所有的 props 都遵循着单向绑定原则,props 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递。这避免了子组件意外修改父组件的状态的情况,不然应用的数据流将很容易变得混乱而难以理解。
父组件实现 v-model 指令是通过
<!-- CustomInput.vue -->
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>
<template>
//注意这里的@input里面的方法不是@input="someValue = $event" 而是通过emit子传父,让父组件去修改数据,更符合单向数据流
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
另一种实现 v-model 的方式是使用一个可写的,同时具有 getter 和 setter 的 computed 属性。get 方法需返回 modelValue prop,而 set 方法需触发相应的事件:
<!-- CustomInput.vue -->
<script setup>
import { computed } from 'vue'
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
const value = computed({
get() {
return props.modelValue
},
set(value) {
emit('update:modelValue', value)
}
})
</script>
<template>
<input v-model="value" />
</template>
封装组件的一般要素
以element-plus的Button看组件设计
如何更好的二次封装组件
属性的传递
通过$attr 属性进行
插槽的传递
获取ref
二次封装的组件需要获取到el-input
在vue3的setup语法糖的情况下,切记需要使用defineExpose这个方法!!!因为使用 <script setup> 的组件是默认关闭的——即通过模板引用或者 $parent 链获取到的组件的公开实例,不会暴露任何在 <script setup> 中声明的绑定。
是vue3新增的一个api,需要通过defineExpose编译器宏来显式指定在 <script setup> 组件中要暴露出去的属性:
提供文档和示例
Vue3官网对Web Component的解释
使用Vue3 + Web Component开发组件库的方案
Web Component的详解
zhuanlan.zhihu.com/p/150268517
实现跨框架的组件库文档的问题
二次封装组件的技巧总结
对比OpenTiny