**宣告:未經允許,不得轉載。** Web Components 現世很久了,所以你可能聽說過,甚至學習過,非常瞭解了。但是沒關係,可以再重溫一下,溫故知新。 ## 瀏覽器原生能力越來越強。 ### js 曾經的 `JQuery`,是前端入門必學的技能,是前端專案必用的一個庫。它的強大之處在於簡化了 `dom 操作`(強大的選擇器) 和 `ajax`(非同步) 操作。 現在原生 api `querySelector()`、`querySelectorAll()`、`classList` 等的出現已經大大的弱化了 dom 操作, `fetch`、基於 `promise` 的 `axios` 已經完全替代了 `ajax`, 甚至更好用了,`async-await` 是真的好用。 [You-Dont-Need-jQuery](https://github.com/nefe/You-Dont-Need-jQuery/blob/master/README.zh-CN.md#css--style) ### css css 前處理器(如 `scss`、`less`) 是專案工程化處理 css 的不二選擇。它的強大之處是支援**變數**、**樣式規則巢狀**、**函式**。 現在 css 已經支援變數`(--var)`了, 樣式規則巢狀也在計劃之中,函式嘛 `calc()` 也非常強大,還支援 `attr()` 的使用,還有 `css-module` 模組化。 [不用預編譯,CSS直接寫巢狀的日子就要到了](https://github.com/75team/w3c/blob/master/articles/20180712_anjia_%E4%B8%8D%E7%94%A8%E9%A2%84%E7%BC%96%E8%AF%91%EF%BC%8CCSS%E7%9B%B4%E6%8E%A5%E5%86%99%E5%B5%8C%E5%A5%97%E7%9A%84%E6%97%A5%E5%AD%90%E5%B0%B1%E8%A6%81%E5%88%B0%E4%BA%86.md) [w3c樣式規則巢狀 css-nesting-module](https://drafts.csswg.org/css-nesting/) 以前要製作酷炫複雜的 css 樣式及動畫,必須藉助 css 前處理器的變數、函式或者js才行,現在用 (`css-doodle`)[https://css-doodle.com/] 技術,實現的更酷、更炫。 [css-doodle作品集](https://codepen.io/yuanchuan/collections/popular/) ### web components 元件化 `Web Components` 可以建立可複用的元件,未來的某一天拋棄現在所謂的框架和庫,直接使用原生 API 或者是使用基於 Web Components 標準的框架和庫進行開發,你覺得可能嗎?我覺得是可能的。 #### vue-lit [vue-lit](https://github.com/yyx990803/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 中,然後直接在瀏覽中開啟預覽效果的。 ```html
```
[原始碼解讀](https://github.com/yyx990803/vue-lit/blob/master/index.js) ```js // lit-html 模板,提供 html 模板(簡單js表示式及事件繫結)、render 渲染能力 import { render } from 'https://unpkg.com/lit-html?module' // reactivity 是vue3.0的核心,shallowReactive 淺響應,effect 可以理解為 watch,提供屬性響應及部分生命週期處理 import { shallowReactive, effect } from 'https://unpkg.com/@vue/reactivity/dist/reactivity.esm-browser.js' let currentInstance export function defineComponent(name, propDefs, factory) { if (typeof propDefs === 'function') { factory = propDefs propDefs = [] } // 自定義元素 custom element,原生 API customElements.define( name, class extends HTMLElement { // 設定需要監聽的屬性 static get observedAttributes() { return propDefs } constructor() { super() // 屬性接入 vue 的響應式 const props = (this._props = shallowReactive({})) currentInstance = this // lit-html 的 html 生成的模板 const template = factory.call(this, props) currentInstance = null // bm onBeforeMount this._bm && this._bm.forEach((cb) => cb()) // shadowRoot,closed 表示不可以直接通過 js 獲取到定義的 customElement 操作 shadowRoot const root = this.attachShadow({ mode: 'closed' }) let isMounted = false effect(() => { if (isMounted) { // _bu, onBeforeUpdate this._bu && this._bu.forEach((cb) => cb()) } // 將 template 內容掛載到 shadowRoot 上 render(template(), root) if (isMounted) { // _u,onUpdated this._u && this._u.forEach((cb) => cb()) } else { isMounted = true } }) } // 首次掛載到 dom 上後的回撥,onMounted connectedCallback() { this._m && this._m.forEach((cb) => cb()) } // 解除安裝, onUnmounted disconnectedCallback() { this._um && this._um.forEach((cb) => cb()) } // 屬性監聽 attributeChangedCallback(name, oldValue, newValue) { this._props[name] = newValue } } ) } function createLifecycleMethod(name) { return (cb) => { if (currentInstance) { ;(currentInstance[name] || (currentInstance[name] = [])).push(cb) } } } export const onBeforeMount = createLifecycleMethod('_bm') export const onMounted = createLifecycleMethod('_m') export const onBeforeUpdate = createLifecycleMethod('_bu') export const onUpdated = createLifecycleMethod('_u') export const onUnmounted = createLifecycleMethod('_um') export * from 'https://unpkg.com/lit-html?module' export * from 'https://unpkg.com/@vue/reactivity/dist/reactivity.esm-browser.js' ``` [shallowReactive 原始碼](https://github.com/vuejs/vue-next/blob/master/packages/reactivity/src/reactive.ts),函式註釋已經表達的很清楚了,only the root level properties are reactive。物件只有根屬性響應,換言之即,淺響應,和淺拷貝類似。 ```js /** * Return a shallowly-reactive copy of the original object, where only the root * level properties are reactive. It also does not auto-unwrap refs (even at the * root level). */ export function shallowReactive
(target: T): T { return createReactiveObject( target, false, shallowReactiveHandlers, shallowCollectionHandlers ) } ``` [effect 原始碼](https://github.com/vuejs/vue-next/blob/master/packages/reactivity/src/effect.ts),粗略的可以看到裡面有 dep 依賴,還有 oldValue、newValue 處理。 通過分析,vue-lit 應該是將 vue3.0 的響應式和 web components 做的一個嘗試。用 `lit-html` 的原因時因為支援模板支援簡單js表示式及事件繫結(原生template目前只有slot插槽) #### css-doodle 實際上,前面介紹的 css-doodle 也是一個 web component。是瀏覽器原生就支援的。 示例:[藝術背景圖](https://codepen.io/yuanchuan/pen/YRvMwK)。 ```
:doodle { @grid: 1x300 / 100vw 40vmin; overflow: hidden; background: linear-gradient(rgba(63, 81, 181, .11), #673AB7); } align-self: flex-end; --h: @r(10, 80, .1); @random(.1) { --h: @r(85, 102, .1) } @size: 1px calc(var(--h) * 1%); background: linear-gradient(transparent, rgba(255, 255, 255, .4), transparent); background-size: .5px 100%; transform-origin: center 100%; transform: translate(@r(-2vmin, 2vmin, .01), 10%) rotate(@r(-2deg, 2deg, .01)); :after { content: ''; position: absolute; top: 0; @size: calc(2px * var(--h)); transform: translateY(-50%) scale(.14); background: radial-gradient(@p(#ff03929e, #673ab752, #fffa) @r(40%), transparent 50%) 50% 50% / @r(100%) @lr() no-repeat; }
``` 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模板)**: `
` 和 `
` 元素使您可以編寫不在呈現頁面中顯示的標記模板。然後它們可以作為自定義元素結構的基礎被多次重用。 ### Custom elements 使用者可以使用 `customElements.define` 自定義 html 元素。 ``` customElements.define(elementName, class[, extendElement]); ``` - elementName: 名稱不能是單個單詞,必須用**短橫線**分隔。 - class: 用以定義元素行為的類,包含生命週期。 - extendElement: 可選引數,一個包含 `extends` 屬性的配置物件,指定建立元素繼承哪個內建 HTML 元素 根據定義,得出有兩種 custom element: - Autonomous custom elements: 獨立元素,不繼承內建的HTML元素。和 html 元素一樣使用,例如`
` - Customized built-in elements: 繼承內建的HTML元素。使用先寫出內建html元素便籤,通過 **is** 屬性指定 custom element 名稱,例如`
` 還有生命週期: - connectedCallback:當 custom element首次被插入文件DOM時,被呼叫。 - disconnectedCallback:當 custom element從文件DOM中刪除時,被呼叫。 - adoptedCallback:當 custom element被移動到新的文件時,被呼叫。 - attributeChangedCallback: 當 custom element增加、刪除、修改自身屬性時,被呼叫。 示例:獨立元素。 ```html
``` 示例:繼承元素 ```html
``` 更多,請參考:[Custom elements](https://developer.mozilla.org/zh-CN/docs/Web/Web_Components/Using_custom_elements) ### Shadow DOM Web components 的重要功能是封裝——可以將標記結構、樣式和行為隱藏起來,並與頁面上的其他程式碼相隔離,保證不同的部分不會混在一起,使程式碼更加乾淨、整潔。Shadow DOM 介面是關鍵所在,它可以將一個隱藏的、獨立的 DOM 附加到一個元素上。 附加到哪個元素上,和定義 custom element 時有關,如果是獨立元素,附加到 document body 上;如果是繼承元素,則附加到繼承元素上。
可以和操作普通 DOM 一樣,利用 API 操作 Shoadow DOM。 ```js let shadow = elementRef.attachShadow({mode: 'open'}); let shadow = elementRef.attachShadow({mode: 'closed'}); ``` `open` 表示可以通過頁面內的 JavaScript 方法來獲取 Shadow DOM,如'document.querySelector('custom-info').shadowRoot'。反之,獲取不到。 更多,請參考:[Shadow DOM](https://developer.mozilla.org/zh-CN/docs/Web/Web_Components/Using_shadow_DOM) ### HTML templates template 和 slot 元素可以創建出非常靈活的 shadow DOM 模板,來填充 custom element。 對於重複使用的 html 結構,可以起到簡化作用,非常有意義。 示例 ```html
template info
default text
``` 更多,請參考:[HTML templates and slots](https://developer.mozilla.org/zh-CN/docs/Web/Web_Components/Using_templates_and_slots) ## web components 示例 [web component todolist](https://wc-todo.firebaseapp.com/native/)
[其他庫 todolist 大比拼](https://wc-todo.firebaseapp.com/)
看圖,結果不言而喻。 ## 總結 瀏覽器原生能力正在變得很強大。web component 值得擁抱一下。雖然 template 還不是很完善(不支援表示式),但這也只是白板上的一個黑點。 參考: 1. [尤大 3 天前發在 GitHub 上的 vue-lit 是啥?](https://github.com/axuebin/articles/issues/41) 2. [Web Components](https://developer.mozilla.org/zh-CN/docs/Web/Web_Components) 3. [web-components-todo](https://github.com/shprink/web-component