开发项目的时候为什么不手写原生 JS,而是要用现如今非常流行的前端框架,原因有很多,例如:
Web Components 就是为了解决“组件化”而诞生的,它是浏览器原生支持的组件化,不依赖任何库、依赖和打包工具就可以在浏览器中运行。
Vue、React 的组件化并不是真正的组件化,虽然写代码时写的是确实的组件化代码,但是编译后就不再是组件化了。
例如用 Vue + ElementUI 开发的应用,ElementUI 的组件都是 el 开头的,如
,但编译后显示在页面上的就不再是
标签了。
这有点类似于 CSS 预处理器(如 Sass、Less),那些在开发阶段编译的变量(如 $color: red;
)其实并不是真正的变量,而是伪变量。在编译过后就没有变量的概念了,所以很难和 JS 通信。
例如有一个需求,在页面上给用户提供一个输入框,用户输入什么颜色(如 red、#ff00000),网站就会变成相应颜色的主题色,可是在我们获取到用户输入后,却没有变法将它们赋值给 Sass 变量上去。因为 Sass 代码在编译后已经变成了 CSS 代码,没有 Sass 变量了,例如 color: $color;
编译为 color: red;。
所以此时就需要一个浏览器原生就支持的,不需要编译就能够运行的变量,于是 CSS 变量就出现了(--color: red;
、color: var(--color)
),可以非常方便地与 JS 进行通信,因为它是浏览器级别地原生变量。
同理,框架的组件化也不是真正的标准,每家都用自己的组件化标准,这就导致了生态的分裂,而且这些框架的组件化也都是靠编译才能实现的,并且非常依赖于这个框架,是一种共生的关系,就像使用 Vue 时,后缀以 .vue 结尾的文件,根本没有办法在浏览器中运行,必须下载 Node、Webpack、vue-loader 等工具进行打包,但还是无法在脱离 Vue 这个框架的安装包的情况下进行运行。
通常来说,浏览器厂商会吸收一些前端非常流行框架之中的可取之处,然后推动其成为标准,并在浏览器中原生实现这些功能,最经典的莫过于 jQuery 的 $()
选择器。
“都 21 世纪了,还提 jQuery?”
尽管这几年风生水起的 Vue 和 React 加剧了 jQuery 的没落,但全世界仍有超过 6600 万个网站在使用 jQuery,同时 jQuery 也给业界留下了产生深远影响的遗产,W3C 就仿照$()
函数实现了 querySelector()
和 querySelectorAll()
方法。
而讽刺的是,也正是这两个原生方法的出现,大大加快了 jQuery 的没落,因为它们取代了 jQuery 最常用的功能之一:快捷的选择 DOM 元素。
那么浏览器原生支持的组件化会取代现在所流行的库或框架么?
那么事实真的是这样么,其实不然。
Web Components 与如今非常流行的 MVVM 框架是一种共存的关系,而不是一种互斥的关系,就像 Sass 变量和 CSS 变量,两者可以非常完美的互补,而不是说用了 CSS 变量就不能用 Sass 变量。
再者来说,我们用那些 MVVM 框架也并不仅仅只是为了它们的组件化功能,虽然组件化是其中非常重要的一项功能,但是还有页面路由、数据绑定、模块化、CSS 预处理器、虚拟 DOM、Diff 算法,以及各种庞大的生态等功能。
Web Components 要解决的仅仅只是组件化的这么一项功能。
React 和 Web Components 为了解决不同的问题而生。Web Components 为可复用组件提供了强大的封装,而 React 则提供了声明式的解决方案,使 DOM 与数据保持同步。两者旨在互补。作为开发人员,可以自由选择在 Web Components 中使用 React,或者在 React 中使用 Web Components,或者两者共存。
—— 《Web Components – React》
我们认为 Vue 和 Web Components 主要是互补的技术。Vue 为使用和创建定制元素提供了出色的支持。无论你是将自定义元素集成到现有的 Vue 应用程序中,还是使用 Vue 来构建和分发自定义元素都很方便。
—— 《Vue 与 Web Components | Vue.js》
Web Components 是一个浏览器原生支持的组件化方案,允许你创建新的自定义、可封装、可重用的HTML 标记。不用加载任何外部模块,直接就可以在浏览器中跑。
它的出现原因?因为早期组件生态很乱,有各种各样的框架和库,都有各自的规范,导致一个团队切换框架很困难 。为了解决这种分化的形式,让 Web 组件模型统一化,所以才有了Web Components规范的出现。目标是提供一种权威的、浏览器能理解的方式来创建组件。
为什么要学习它?用一句话总结就是回顾历史展望未来。
2011年提出Web Components概念,React诞生
2013年 Chrome 和 Opera 又联合提出了推出的 V0 版本的 Web Components 规范,React开源
2014年Vue诞生
2016年Web Components 推进到了 V1 版本
由于浏览器兼容性、和主流框架开发效率等等问题导致现在几乎使用不到它,但我们可以学习它的思想,也许未来就会变的有用?
可以创建一个自定义标签。根据规范,自定义元素的名称必须包含连词线”-“,用与区别原生的 HTML 元素。
class UserButton extends HTMLButtonElement {constructor() {super();}
}customElements.define('user-button', UserButton, { extends: "button" });
使用生命周期回调函数
在custom element的构造函数中,可以指定多个不同的回调函数,它们将会在元素的不同生命时期被调用:
connectedCallback
:当 custom element首次被插入文档DOM时,被调用。disconnectedCallback
:当 custom element从文档DOM中删除时,被调用。adoptedCallback
:当 custom element被移动到新的文档时,被调用。attributeChangedCallback
: 当 custom element增加、删除、修改自身属性时,被调用。经常写video,audio等html元素在带的控制条或者模块,但是这这些模块哪里来的用什么实现的
隐藏有点深刻,难以发现。
那什么是影子DOM
创建影子树
通过createShadowRoot创建影子树root节点
影子dom
再给影子树节点添加css时不能用过class或者元素选择来添加,否则无效果
影子dom
通过class选择dom时需要将style也放入影子节点里
不能直接获得影子DOM
通过js常规方法不能直接获取到dom节点
var $box = document.getElementById('box');var shadowRoot = $box.createShadowRoot(); // 获得root//var showRoot = $box.webkitGetShadowRoot() // webkit 支持var children = document.createElement('div');children.setAttribute('class', 'children');children.setAttribute('id', 'children');shadowRoot.appendChild(children);// 获得影子dom// 通过idvar getShadowRootById = document.getElementById('children');console.log(getShadowRootById)// 通过节点选择console.log('---------------')var getShadowRootByDomBox = document.body.firstChild.nextSibling; // 获得到box//var getShadowRootByDom = getShadowRootByDomBox.firstChildvar getShadowRootByDom = getShadowRootByDomBox.firstElementChild;console.log(getShadowRootByDom)
影子dom事件绑定
在createElement时拿到的元素,添加addEventListener事件
var $box = document.getElementById('box');var shadowRoot = $box.createShadowRoot(); // 获得root//var showRoot = $box.webkitGetShadowRoot() // webkit 支持var children = document.createElement('div');children.setAttribute('class', 'children')shadowRoot.innerHTML += '';shadowRoot.appendChild(children);children.addEventListener('click', function(e) {console.log(e)})
利用content元素select属性将目标内容匹配到template中指定位置,并且目标内容只能在影子元素里
影子dom
我接着测试测试

CSS 选择器:
:host, :host(), :host-context()
:host
测试

:host()选择器,选择影子dom宿主元素
我接着测试测试

:host-context()与后代选择器表达式一起使用,以仅选择特定祖先内部的自定义元素的实例
影子dom
213测试

templates and slots
可以编写不在呈现页面中显示的标记模板。然后它们可以作为自定义元素结构的基础被多次重用。
- 被使用前不会被渲染。
- 被使用前对页面其他部分没有影响,脚本不会运行,图像不会加载,音频不会播放。
会影响外部样式
My paragraph

slot的使用:
会影响外部样式
My paragraph
My default text slot text

下面挑选了市面上既好玩,颜值又高的组件库来体验以下 Web Components:
- css-doodle:直译 - css 涂鸦
- fancy-components:直译 - 花式组件库,可惜没有官方文档
二、css-doodle
css-doodle
:doodle {@grid: 20 / 100vmax;background: #12152f;}::after {content: '\@hex(@rand(0x2500, 0x257f))';font-size: 5vmax;color: hsla(@rand(360), 70%, 70%, @rand(.9))}

css-doodle
:doodle {@grid: 1x1x100 / 100vmin;animation: r 23s linear infinite}@size: 100% 50%;position: absolute;top: 25%;transform: rotate(@r(360deg));perspective: @r(100px, 200px);::after {content: '';position: absolute;@size: @r(.5vmin, 5vmin);color: @p(#fdfffc, #2ec4b6, #e71d36, #ff9f1c);background: currentColor;box-shadow: @m2(0 0 1.2vmin currentColor);animation: cycle @r(2s) linear infinite;--trans: scaleX(@r(1, 5)) translateZ(@r(10vmin, 20vmin));transform: rotateY(0) @var(--trans)}:empty::after { display: none }@keyframes cycle {to {transform: rotateY(@p(-1turn, 1turn)) @var(--trans)}}@keyframes r {to { transform: rotate(1turn) }}

css-doodle
:doodle {@grid: 80x1 / 100vw 100vh;@min-size: 100px;filter: url(#filter);animation: r 23s linear infinite}@size: 100% 50%;position: absolute;top: 25%;transform: rotate(@r(360deg));perspective: 130px;::after {content: '';position: absolute;@size: @r(10px);background: #fff;box-shadow: @m3(0 0 calc(.5vmin + 5px) #fff);animation: cycle @r(2s, 8s) linear infinite;animation-delay: -@r(100s);--trans: scaleX(@r(.1, 5)) translateZ(105px);transform: rotateY(0) @var(--trans)}@keyframes cycle {to {transform: rotateY(@p(-1turn, 1turn)) @var(--trans)}}@keyframes r {to { transform: rotate(@p(-1turn, 1turn)) }}

三、fancy-components
https://github.com/fancy-components/fancy-components
fancy-components
fancy-components
撒花

四、在脚手架中使用 Web Components 组件库
Vue 2 中使用
npm i -g @vue/cli
vue create vue2-app
cd vue2-app
npm i fancy-components
npm run serve
// src\main.js
import Vue from 'vue'
import App from './App.vue'
import { FcBubbles } from 'fancy-components'// 禁用 no-new 校验规则
/* eslint-disable no-new */
new FcBubbles()Vue.config.productionTip = falsenew Vue({render: h => h(App)
}).$mount('#app')
![]()
...
Web Components 原生组件的地位和 HTML 标签的地位是相同的,大写的驼峰命名组件会被当做 Vue 组件,原生组件要和 HTML 标签一样,不要写成驼峰命名。React 框架中也一样。
Vue CLI 旧版本中使用 Web Components 控制台可能会发出警告,原因是 Vue 将 原生组件当作 Vue 组件去判断,警告组件没有注册,解决办法就是配置 ignoredElements
让 Vue 忽略原生组件:
Vue.config.ignoredElements = [// 正则匹配/^fc-/,// 或字符串'css-coodle'
]
Vue 3 中使用
vue create vue3-app
cd vue3-app
npm i fancy-components
npm run serve
// src\main.js
import { createApp } from 'vue'
import App from './App.vue'
import { FcBubbles } from 'fancy-components'/* eslint-disable no-new */
new FcBubbles()createApp(App).mount('#app')
![]()
使用上如 Vue2 一样,但实际上会报错:
[Vue warn]: Failed to resolve component: fc-bubbles
If this is a native custom element, make sure to exclude it from component resolution via compilerOptions.isCustomElement.
原因与 Vue CLI 旧版本创建的 Vue2 应用一样,解决办法依然是配置忽略原生组件(自定义元素),参考:Vue 与 Web Components
// vue.config.js
module.exports = {chainWebpack: config => {config.module.rule('vue').use('vue-loader').tap(options => ({...options,compilerOptions: {// 将所有 fc- 开头的标签名都视为自定义元素isCustomElement: tag => tag.startsWith('fc-')}}))}
}
重启应用,警告已经不见了,但是点击仍然没有生效,打开 Element 面板发现组件的 click 属性并没有添加上,而其他属性如 click1 可以添加,这可能是因为 Vue3 认为 click 是一个不能直接添加的关键字,测试发现只需将 click 改成大写 Click 即可添加上。
![]()
在 Vite 中使用 Web Components 组件库
# npm 6.x
npm create vite@latest vite-vue-app
√ Select a framework: » vue
√ Select a variant: » vuecd vite-vue-app
npm install
npm i fancy-components
npm run dev
// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import { FcBubbles } from 'fancy-components'new FcBubbles()createApp(App).mount('#app')
![]()
还要配置忽略的自定义元素:
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'// https://vitejs.dev/config/
export default defineConfig({plugins: [vue({template: {compilerOptions: {// 将所有 fc- 开头的标签名都视为自定义元素isCustomElement: tag => tag.startsWith('fc-')}}})]
})
不需要重启即可生效。
相关内容
热门资讯
喜欢穿一身黑的男生性格(喜欢穿...
今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
发春是什么意思(思春和发春是什...
本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
网络用语zl是什么意思(zl是...
今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
为什么酷狗音乐自己唱的歌不能下...
本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
华为下载未安装的文件去哪找(华...
今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
怎么往应用助手里添加应用(应用...
今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
家里可以做假山养金鱼吗(假山能...
今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
一帆风顺二龙腾飞三阳开泰祝福语...
本篇文章极速百科给大家谈谈一帆风顺二龙腾飞三阳开泰祝福语,以及一帆风顺二龙腾飞三阳开泰祝福语结婚对应...
四分五裂是什么生肖什么动物(四...
本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...
美团联名卡审核成功待激活(美团...
今天百科达人给各位分享美团联名卡审核成功待激活的知识,其中也会对美团联名卡审核未通过进行解释,如果能...