-
React,使用JSX语法,ts支持较好,虚拟DOM -
Vue,使用独有vue格式模板,利用es5的getter/setter实现响应式,虚拟DOM -
Web Component,浏览器原生支持,其内部的shadow dom从v0升级到v1,从2011年发布至今,已经进入了一个浏览器普遍支持的稳定期。(IE作为可忽略因素)
React与Vue两套技术方案并存,使大量ui与业务组件需要重复编写,工作量至少翻倍,会造成大量无法统一的组件表现
React/Vue组件层级无论运行环境与实现都完全不同,太多说不过来,blablabla
-
在上下层组件在都是相同框架组件的时候这种机制并没有问题,但在直接使用dom元素时,dom元素从上层组件中接收到的属性数据,都会被dom层直接简化为基本类型,即String,Boolean这两种格式,因此我们在前端开发时,在探测dom属性时,经常会看到
xxxx="[object object]"这种情况如下图所示
使用Web Component构造的自定义dom,也面临着上面这种问题,在数据结构被限制的情况下,是无法构造出真正可用的跨技术栈组件的
Web Component解决方案
-
核心概念
-
它是原生dom的扩展
-
Shadow DOM
-
slot,分默认slot与命名slot
-
css封装,类似与css modules
-
-
几个核心api
-
attachShadow,一般在constructor中调用,创建内部的shadowDom -
connectedCallback,生命周期方法,当该dom被插入dom树时调用 -
disconnectedCallback,生命周期方法,当该dom从dom树中删除时调用,做一些清理工作,删除事件等释放内存的工作 -
attributeChangedCallback,生命周期方法,当监控的属性发生变化时调用 -
类属性
observedAttributes: string[]配合,只有在该数组中指定的方法才会触发
-
-
🐾 坑注意,由于
shadow dom经历了v0与v1两个版本,v0已经废弃,网上充斥这很多老版本教程,看了白看,还会往歪路上带,必须看官方最新文档。
解决方案当然是Web Component
Web Component的历史大约起始于2011年,其中的shadow dom标准从v0升级的到v1,经过了多年的浏览器大战,终于迎来了chrome掉打ie的大好局面 🎉 。只要不是需要兼容ie的项目,大都可以考虑使用。
此外还有如下几个优势,摘自
-
互操作性 — 组件超越框架而存在,可以在不同的技术栈中使用
-
寿命 — 因为组件的互操作性,它们将有更长的寿命,基本不需要为了适应新的技术而重写
-
移植性 — 组件可以在任何地方使用,因为很少甚至没有依赖,组件的使用障碍要明显低于依赖库或者框架的组件
Web Component是对原生DOM的自定义操作,api很少,阅读一下很快就能掌握,大概(◔◡◔)
大家可以想象成类似于(正则表达式30分钟入门教程) 🍌 === 🦍 === 🌴
其中一个重要特性observedAttributes,可以达到定制需要监控属性值的目的,与React/Vue通过props传递数据流的思想是一致的
于是有了如下中台项目的具体实现----scf-ui
在实现web component自定义组件时,覆盖从父类继承的setAttribute方法进行拦截,可获取上级virtual dom传递的任意数据结构
按这种思路,不限于React与Vue,只要是使用virtual dom来更新dom属性的框架都可以使用web component封装组件来获取数据
其关键步骤的最简化原理如下
class MyDom extends HTMLElement {
setAttribute(name: string, value: any) {
// 只有当前组件需要的属性,才需要接收原始值
if (MyDom.observedAttributes.includes(name)) {
this.data[name] = value
this.renderContent()
// 其他属性,按dom默认方法处理
} else {
super.setAttribute(name, value)
}
}
// 需要监听更新的属性名数组
static get observedAttributes(): string[] {
return ['menus', 'user']
}
}- html对属性是不区分大小写的,例如
setAttribute('myName', 'xxx'),myName会被自动转化为myname
如果是多单词组成的长名称属性,需要用-来组合
- 对于被setAttribute拦截的属性,没有调用
super.setAttribute的情况下,默认的attributeChangedCallback方法是不会被调用的,需要手动触发
此处需要吐槽一下React
-
属性值需要额外处理
Vue对属性并没有进行额外处理,而React为了保持某种兼容性,对属性值进行了强制字符串转化,见代码react对自定义dom的setAttribute的value进行了toString处理,需要额外处理一下 -
无法使用React开发
web component内部逻辑,原因是React的事件机制导致web component内部的react编写的组件的所有事件都无法运行参考react-shadow-dom-retarget-events
强制使用hack的方法解决该问题,会导致页面运行效率问题
-
一个React与Vue环境的区别,React会先将dom创建放到dom树中,之后再对比属性调用setAttribute,而vue会先创建dom,调用setAttribute,然后将处理好的dom放到dom树中。没有经过实际数据对比,但个人感觉vue这种处理应该效率会更好一些。
-
重置javascript经过调研发现,目前做不到,定制对象的+操作符,使加法操作返回对象toString或valueOf方法而改变的+行为最终的返回结果只能是字符串或数字 -
通过babel插件,达到重置需要引入额外的依赖,而且会改变所有代码操作符的行为,导致不可预知的问题+操作符的行为 -
修改要求用户的React版本与定制版本react-dom保持一致,且需要经常根据官方react-dom升级版本同步升级,已经被否定react-dom源码,更改强制转换字符串部分,之后打包为一个定制化的react-dom该方案的有点是,可以做到在React/Vue中完全一致的组件使用行为,以
scf-layout为例
import '@scf/ui'
<scf-layout
menus={props.menus}
basepath={basename}
router={history}
>
...
</scf-layout> import '@scf/ui'
<scf-layout
:menus="menus"
:basepath="basename"
:router="router"
>
...
</scf-layout>- 为react提供了一层额外的封装组件,通过ref向web component定义的dom传递数据,封装后可以做到在React与Vue环境中的除组件命名区别外,其他行为达到统一
React中使用大写字母开头的 大驼峰 命名组件
Vue中使用全小写的横线分割的 蛇形 命名组件
import { ScfLayout } from '@scf/ui'
<ScfLayout
menus={props.menus}
basepath={basename}
router={history}
>
...
</ScfLayout>import '@scf/ui'
<scf-layout
:menus="menus"
:basepath="basename"
:router="router"
>
...
</scf-layout>自定义dom的内部内容,理论上可以使用任何技术(目前React必须除外,原因如上)。当结构非常简单时,使用原生dom编程生成节点,或使用web component示例中的模板都可以
但当编写复杂逻辑组件是,这种方法会使前端开发退化到jquery时代
当前使用的方案是Preact / Vue
Preact是牺牲了很多React的兼容性与性能优化考虑来达到代码最小化简版React
- web component内部css完全封装在内部,不受外部影响,当需要通过外层定制时,可以通过原生
css variable特性来达到实时配置样式的效果,参考 css 变量
-
shadow dom内的部分调试不太友好,希望能找到类似react-devtool/vue-devtool的浏览器插件调试工具 -
调研
stencil开发组件内部逻辑,目前进度感觉stencil是一套独立的开发编译环境,与React与Vue开发环境不能兼容 -
调研omi,在
omi的文档中发现上下级组件传递复杂数据时,使用的是将数据序列化(JSON.stringify)之后传递的方式。但使用omi开发内部逻辑是否合适有待调研 -
以
vue为基础的web component组件开发是可行的,目前正在开发中 -
web component组件测试用例编写的最佳实践




