声明:未经允许,不得转载。

Web Components 现世很久了,所以你可能听说过,甚至学习过,非常了解了。但是没关系,可以再重温一下,温故知新。

浏览器原生能力越来越强。

js

曾经的 JQuery,是前端入门必学的技能,是前端项目必用的一个库。它的强大之处在于简化了 dom 操作(强大的选择器) 和 ajax(异步) 操作。

现在原生 api querySelector()querySelectorAll()classList 等的出现已经大大的弱化了 dom 操作, fetch、基于 promiseaxios 已经完全替代了 ajax, 甚至更好用了,async-await 是真的好用。

You-Dont-Need-jQuery

css

css 预处理器(如 scssless) 是项目工程化处理 css 的不二选择。它的强大之处是支持变量样式规则嵌套函数

现在 css 已经支持变量(--var)了, 样式规则嵌套也在计划之中,函数嘛 calc() 也非常强大,还支持 attr() 的使用,还有 css-module 模块化。

不用预编译,CSS直接写嵌套的日子就要到了

w3c样式规则嵌套 css-nesting-module

以前要制作酷炫复杂的 css 样式及动画,必须借助 css 预处理器的变量、函数或者js才行,现在用 (css-doodle)[https://css-doodle.com/] 技术,实现的更酷、更炫。

css-doodle作品集

web components 组件化

Web Components 可以创建可复用的组件,未来的某一天抛弃现在所谓的框架和库,直接使用原生 API 或者是使用基于 Web Components 标准的框架和库进行开发,你觉得可能吗?我觉得是可能的。

vue-lit

vue-lit,描述如下:

Proof of concept mini custom elements framework powered by @vue/reactivity and lit-html.

描述用到了 custom elements,而且浏览器控制台 elements 的 DOM 结构中也含有 shadow-root。而 custom element 和 shadow DOM 是 web components 的重要组成。具体看下面 demo,

说明:本文文档示例,都是可以直接复杂到一个 html 文档的 body 中,然后直接在浏览中打开预览效果的。

  1. <my-component />
  2. <script type="module">
  3. import {
  4. defineComponent,
  5. reactive,
  6. html,
  7. onMounted
  8. } from 'https://unpkg.com/@vue/lit@0.0.2';
  9. defineComponent('my-component', () => {
  10. const state = reactive({
  11. text: 'Hello World',
  12. });
  13. function onClick() {
  14. alert('cliked!');
  15. }
  16. onMounted(() => {
  17. console.log('mounted');
  18. });
  19. return () => html`
  20. <p>
  21. <button @click=${onClick}>Click me</button>
  22. ${state.text}
  23. </p>
  24. `;
  25. })
  26. </script>

源码解读

  1. // lit-html 模板,提供 html 模板(简单js表达式及事件绑定)、render 渲染能力
  2. import { render } from 'https://unpkg.com/lit-html?module'
  3. // reactivity 是vue3.0的核心,shallowReactive 浅响应,effect 可以理解为 watch,提供属性响应及部分生命周期处理
  4. import {
  5. shallowReactive,
  6. effect
  7. } from 'https://unpkg.com/@vue/reactivity/dist/reactivity.esm-browser.js'
  8. let currentInstance
  9. export function defineComponent(name, propDefs, factory) {
  10. if (typeof propDefs === 'function') {
  11. factory = propDefs
  12. propDefs = []
  13. }
  14. // 自定义元素 custom element,原生 API
  15. customElements.define(
  16. name,
  17. class extends HTMLElement {
  18. // 设置需要监听的属性
  19. static get observedAttributes() {
  20. return propDefs
  21. }
  22. constructor() {
  23. super()
  24. // 属性接入 vue 的响应式
  25. const props = (this._props = shallowReactive({}))
  26. currentInstance = this
  27. // lit-html 的 html 生成的模板
  28. const template = factory.call(this, props)
  29. currentInstance = null
  30. // bm onBeforeMount
  31. this._bm && this._bm.forEach((cb) => cb())
  32. // shadowRoot,closed 表示不可以直接通过 js 获取到定义的 customElement 操作 shadowRoot
  33. const root = this.attachShadow({ mode: 'closed' })
  34. let isMounted = false
  35. effect(() => {
  36. if (isMounted) {
  37. // _bu, onBeforeUpdate
  38. this._bu && this._bu.forEach((cb) => cb())
  39. }
  40. // 将 template 内容挂载到 shadowRoot 上
  41. render(template(), root)
  42. if (isMounted) {
  43. // _u,onUpdated
  44. this._u && this._u.forEach((cb) => cb())
  45. } else {
  46. isMounted = true
  47. }
  48. })
  49. }
  50. // 首次挂载到 dom 上后的回调,onMounted
  51. connectedCallback() {
  52. this._m && this._m.forEach((cb) => cb())
  53. }
  54. // 卸载, onUnmounted
  55. disconnectedCallback() {
  56. this._um && this._um.forEach((cb) => cb())
  57. }
  58. // 属性监听
  59. attributeChangedCallback(name, oldValue, newValue) {
  60. this._props[name] = newValue
  61. }
  62. }
  63. )
  64. }
  65. function createLifecycleMethod(name) {
  66. return (cb) => {
  67. if (currentInstance) {
  68. ;(currentInstance[name] || (currentInstance[name] = [])).push(cb)
  69. }
  70. }
  71. }
  72. export const onBeforeMount = createLifecycleMethod('_bm')
  73. export const onMounted = createLifecycleMethod('_m')
  74. export const onBeforeUpdate = createLifecycleMethod('_bu')
  75. export const onUpdated = createLifecycleMethod('_u')
  76. export const onUnmounted = createLifecycleMethod('_um')
  77. export * from 'https://unpkg.com/lit-html?module'
  78. export * from 'https://unpkg.com/@vue/reactivity/dist/reactivity.esm-browser.js'

shallowReactive 源码,函数注释已经表达的很清楚了,only the root level properties are reactive。对象只有根属性响应,换言之即,浅响应,和浅拷贝类似。

  1. /**
  2. * Return a shallowly-reactive copy of the original object, where only the root
  3. * level properties are reactive. It also does not auto-unwrap refs (even at the
  4. * root level).
  5. */
  6. export function shallowReactive<T extends object>(target: T): T {
  7. return createReactiveObject(
  8. target,
  9. false,
  10. shallowReactiveHandlers,
  11. shallowCollectionHandlers
  12. )
  13. }

effect 源码,粗略的可以看到里面有 dep 依赖,还有 oldValue、newValue 处理。

通过分析,vue-lit 应该是将 vue3.0 的响应式和 web components 做的一个尝试。用 lit-html 的原因时因为支持模板支持简单js表达式及事件绑定(原生template目前只有slot插槽)

css-doodle

实际上,前面介绍的 css-doodle 也是一个 web component。是浏览器原生就支持的。

示例:艺术背景图

  1. <script src="https://unpkg.com/css-doodle@0.8.5/css-doodle.min.js"></script>
  2. <css-doodle>
  3. :doodle {
  4. @grid: 1x300 / 100vw 40vmin;
  5. overflow: hidden;
  6. background: linear-gradient(rgba(63, 81, 181, .11), #673AB7);
  7. }
  8. align-self: flex-end;
  9. --h: @r(10, 80, .1);
  10. @random(.1) { --h: @r(85, 102, .1) }
  11. @size: 1px calc(var(--h) * 1%);
  12. background: linear-gradient(transparent, rgba(255, 255, 255, .4), transparent);
  13. background-size: .5px 100%;
  14. transform-origin: center 100%;
  15. transform: translate(@r(-2vmin, 2vmin, .01), 10%) rotate(@r(-2deg, 2deg, .01));
  16. :after {
  17. content: '';
  18. position: absolute;
  19. top: 0;
  20. @size: calc(2px * var(--h));
  21. transform: translateY(-50%) scale(.14);
  22. background: radial-gradient(@p(#ff03929e, #673ab752, #fffa) @r(40%), transparent 50%) 50% 50% / @r(100%) @lr() no-repeat;
  23. }
  24. </css-doodle>

dom 结构:

input、select 等内建 html 元素

input、select 也是 web component。但是是内建的,默认看不到 shadowRoot 结构,需要打开浏览器控制台的设置,勾选Show user agent shadow DOM,才可以在控制台elements中看到其结构。

设置





dom 结构

web components 组件化由 3 部分组成。

  • Custom elements(自定义元素):一组JavaScript API,允许您定义custom elements及其行为,然后可以在您的用户界面中按照需要使用它们。
  • Shadow DOM(影子DOM):一组JavaScript API,用于将封装的“影子”DOM树附加到元素(与主文档DOM分开呈现)并控制其关联的功能。通过这种方式,您可以保持元素的功能私有,这样它们就可以被脚本化和样式化,而不用担心与文档的其他部分发生冲突。
  • HTML templates(HTML模板)<template><slot> 元素使您可以编写不在呈现页面中显示的标记模板。然后它们可以作为自定义元素结构的基础被多次重用。

Custom elements

用户可以使用 customElements.define 自定义 html 元素。

  1. customElements.define(elementName, class[, extendElement]);
  • elementName: 名称不能是单个单词,必须用短横线分隔。
  • class: 用以定义元素行为的类,包含生命周期。
  • extendElement: 可选参数,一个包含 extends 属性的配置对象,指定创建元素继承哪个内置 HTML 元素

根据定义,得出有两种 custom element:

  • Autonomous custom elements: 独立元素,不继承内建的HTML元素。和 html 元素一样使用,例如<custom-info></custom-info>
  • Customized built-in elements: 继承内建的HTML元素。使用先写出内建html元素便签,通过 is 属性指定 custom element 名称,例如<p is="custom-info"></p>

还有生命周期:

  • connectedCallback:当 custom element首次被插入文档DOM时,被调用。
  • disconnectedCallback:当 custom element从文档DOM中删除时,被调用。
  • adoptedCallback:当 custom element被移动到新的文档时,被调用。
  • attributeChangedCallback: 当 custom element增加、删除、修改自身属性时,被调用。

示例:独立元素。

  1. <button onclick="changeInfo()">更改内容</button>
  2. <custom-info text="hello world"></custom-info>
  3. <script>
  4. // Create a class for the element
  5. class CustomInfo extends HTMLElement {
  6. // 必须加这个属性监听,返回需要监听的属性,才能触发 attributeChangedCallback 回调
  7. static get observedAttributes() {
  8. return ['text'];
  9. }
  10. constructor() {
  11. // Always call super first in constructor
  12. super();
  13. // Create a shadow root
  14. const shadow = this.attachShadow({mode: 'open'});
  15. // Create p
  16. const info = document.createElement('p');
  17. info.setAttribute('class', 'info');
  18. // Create some CSS to apply to the shadow dom
  19. const style = document.createElement('style');
  20. console.log(style.isConnected);
  21. style.textContent = `
  22. .info {
  23. color: red;
  24. }
  25. `;
  26. // Attach the created elements to the shadow dom
  27. shadow.appendChild(style);
  28. console.log(style.isConnected);
  29. shadow.appendChild(info);
  30. }
  31. connectedCallback () {
  32. // 赋值
  33. this.shadowRoot.querySelector('.info').textContent = this.getAttribute('text')
  34. }
  35. attributeChangedCallback(name, oldValue, newValue) {
  36. // TODO
  37. console.log(name, oldValue, newValue)
  38. this.shadowRoot.querySelector('.info').textContent = newValue
  39. }
  40. }
  41. // Define the new element
  42. customElements.define('custom-info', CustomInfo);
  43. function changeInfo() {
  44. document.querySelector('custom-info').setAttribute('text', 'custom element')
  45. }
  46. </script>

示例:继承元素

  1. <p is="custom-info" text="hello world"></p>
  2. <script>
  3. // Create a class for the element,extend p element
  4. class CustomInfo extends HTMLParagraphElement {
  5. constructor() {
  6. super();
  7. const shadow = this.attachShadow({mode: 'open'});
  8. const info = document.createElement('span');
  9. info.setAttribute('class', 'info');
  10. const style = document.createElement('style');
  11. console.log(style.isConnected);
  12. style.textContent = `
  13. .info {
  14. color: red;
  15. }
  16. `;
  17. shadow.appendChild(style);
  18. console.log(style.isConnected);
  19. shadow.appendChild(info);
  20. }
  21. connectedCallback () {
  22. this.shadowRoot.querySelector('.info').textContent = this.getAttribute('text')
  23. }
  24. }
  25. // Define the new element, extend p element
  26. customElements.define('custom-info', CustomInfo, {extends: 'p'});
  27. </script>

更多,请参考:Custom elements

Shadow DOM

Web components 的重要功能是封装——可以将标记结构、样式和行为隐藏起来,并与页面上的其他代码相隔离,保证不同的部分不会混在一起,使代码更加干净、整洁。Shadow DOM 接口是关键所在,它可以将一个隐藏的、独立的 DOM 附加到一个元素上。

附加到哪个元素上,和定义 custom element 时有关,如果是独立元素,附加到 document body 上;如果是继承元素,则附加到继承元素上。

可以和操作普通 DOM 一样,利用 API 操作 Shoadow DOM。

  1. let shadow = elementRef.attachShadow({mode: 'open'});
  2. let shadow = elementRef.attachShadow({mode: 'closed'});

open 表示可以通过页面内的 JavaScript 方法来获取 Shadow DOM,如'document.querySelector('custom-info').shadowRoot'。反之,获取不到。

更多,请参考:Shadow DOM

HTML templates

template 和 slot 元素可以创建出非常灵活的 shadow DOM 模板,来填充 custom element。 对于重复使用的 html 结构,可以起到简化作用,非常有意义。

示例

  1. <!-- 显示 default text -->
  2. <custom-info></custom-info>
  3. <!-- 显示 template info -->
  4. <custom-info>
  5. <span slot="info">template info</span>
  6. </custom-info>
  7. <template id="custom-info">
  8. <style>
  9. p {
  10. color: red;
  11. }
  12. </style>
  13. <p><slot name="info">default text</slot></p>
  14. </template>
  15. <script>
  16. class CustomInfo extends HTMLElement {
  17. constructor() {
  18. super();
  19. const shadowRoot = this.attachShadow({mode: 'open'});
  20. const customInfoTpCon = document.querySelector('#custom-info').content;
  21. shadowRoot.appendChild(customInfoTpCon.cloneNode(true));
  22. }
  23. }
  24. customElements.define('custom-info', CustomInfo);
  25. </script>

更多,请参考:HTML templates and slots

web components 示例

web component todolist

其他库 todolist 大比拼

看图,结果不言而喻。

总结

浏览器原生能力正在变得很强大。web component 值得拥抱一下。虽然 template 还不是很完善(不支持表达式),但这也只是白板上的一个黑点。

参考:

  1. 尤大 3 天前发在 GitHub 上的 vue-lit 是啥?
  2. Web Components
  3. web-components-todo

前端未来趋势之原生API:Web Components的更多相关文章

  1. Web API之Web Components

    本文参考<你的前端框架要被web组件替代了>. 于2011年面世的Web Components是一套功能组件,让开发者可以使用 HTML.CSS 和 JavaScript 创建可复用的组件 ...

  2. 使用Node.js原生API写一个web服务器

    Node.js是JavaScript基础上发展起来的语言,所以前端开发者应该天生就会一点.一般我们会用它来做CLI工具或者Web服务器,做Web服务器也有很多成熟的框架,比如Express和Koa.但 ...

  3. 前端应该知道的Web Components

    前端组件化的痛点 在前端组件化横行的今天,确实极大的提升了开发效率.不过有一个问题不得不被重视,拟引入的这些html.css.js代码有可能对你的其他代码造成影响. 虽然我们可以通过命名空间.闭包等一 ...

  4. 腾讯发布新版前端组件框架 Omi,全面拥抱 Web Components

    Omi - 合一 下一代 Web 框架,去万物糟粕,合精华为一 → https://github.com/Tencent/omi 特性 4KB 的代码尺寸,比小更小 顺势而为,顺从浏览器的发展和 AP ...

  5. 前端组件化-Web Components【转】

    以下全部转自:http://www.cnblogs.com/pqjwyn/p/7401918.html 前端组件化的痛点在前端组件化横行的今天,确实极大的提升了开发效率.不过有一个问题不得不被重视,拟 ...

  6. ES7前端异步玩法:async/await理解 js原生API妙用(一)

    ES7前端异步玩法:async/await理解   在最新的ES7(ES2017)中提出的前端异步特性:async.await. 什么是async.await? async顾名思义是“异步”的意思,a ...

  7. 我的第一个原生Web Components——滑块(SingleSlider)

    写着写着,就会跑偏,没错又走上了一个岔道……就是不知道这条岔道以后会不会越来越宽,有的说他是未来,有的说…… 这里不知道,也不做什么评断.减少一些重复性的工作,提高开发效率这是最根本的.说白了就是偷懒 ...

  8. Fiori Fundamentals和SAP UI5 Web Components

    这周有位同事邀请我给团队讲一讲SAP技术的演进历史,所以我准备了下面几个主题来介绍. 其中SAP的技术回顾和演进,我的思路就是从前后台两方面分别介绍. 我画了一张非常简单的图: 去年5月我写过一篇文章 ...

  9. 一文读懂前端技术演进:盘点Web前端20年的技术变迁史

    本文原文由作者“司徒正美”发布于公众号“前端你别闹”,即时通讯网收录时有改动,感谢原作者的分享. 1.引言 1990 年,第一个Web浏览器的诞生:1991 年,WWW诞生,这标志着前端技术的开始. ...

随机推荐

  1. 免费开源工作流Smartflow-Sharp v2.0

    @font-face { font-family: 宋体 } @font-face { font-family: "Cambria Math" } @font-face { fon ...

  2. 【Python】数据结构

    列表的更多特性 list.append(x) 在列表的末尾添加一个元素.相当于 a[len(a):] = [x] . list.extend(iterable) 使用可迭代对象中的所有元素来扩展列表. ...

  3. 无所不能的embedding 3. word2vec->Doc2vec[PV-DM/PV-DBOW]

    这一节我们来聊聊不定长的文本向量,这里我们暂不考虑有监督模型,也就是任务相关的句子表征,只看通用文本向量,根据文本长短有叫sentence2vec, paragraph2vec也有叫doc2vec的. ...

  4. 引用类型之Object

    引用类型 引用类的值(对象)是引用类型的一个实例.在ECMAScript中,引用类型是一种数据结构,用于将数据和功能组织在一起. 对象是某个特定引用类型的实例.新对象是使用new操作符后跟一个构造函数 ...

  5. js日志输出还是只会console.log么,那你就out了

    几乎所有的javascript开发者最常使用的日志打印调试api都是console.log(),其实还有很多的选项供我们选择,笔者下面就为大家一一介绍. 一.console.table() conso ...

  6. 达梦产品技术支持培训-day2-DM8常用SQL

    (本文只作为随笔或个人笔记,非官方文档,请勿作他用,谢谢) DM8数据库的SQL兼容性很高,和Oracle差距不大,以下是个人认为比较关键的部分. 1.关键动词 create --新建 drop -- ...

  7. 使用Python学习win32库进行内存读写

    前言: 上一周,在52的精华帖中,看到有位大佬用Python制作了鬼泣5的修改器,看完才知道,原来Python也可以对内存进行操作,出于对技术的好奇,看完以后,决定自己也尝试一下. 要用到的工具: C ...

  8. 【C语言】这种求结构体成员大小的方法,你可能需要了解一下~

    在C语言编程中,有时候需要知道某结构体中某成员的大小,比如使用堆内存来存储结构体中的某成员时,需要知道该成员的大小,才好确定所需申请的空间大小.求某结构体中某成员的大小,你会怎么做? 例子: type ...

  9. 【原创】xenomai内核解析--xenomai与普通linux进程之间通讯XDDP(二)--实时与非实时关联(bind流程)

    版权声明:本文为本文为博主原创文章,转载请注明出处.如有问题,欢迎指正.博客地址:https://www.cnblogs.com/wsg1100/ 1.概述 上篇文章介绍了实时端socket创建和配置 ...

  10. spring boot:spring security+oauth2+sso+jwt实现单点登录(spring boot 2.3.3)

    一,sso的用途 ? 1,如果有多个应用系统,用户只需要登录一次就可以访问所有相互信任的应用系统. 不需要每次输入用户名称和用户密码, 也不需要创建并记忆多套用户名称和用户密码. 2,系统管理员只需维 ...