從原生web組件到框架組件源碼(一)
- 2020 年 11 月 1 日
- 筆記
溫馨提醒,當你覺得看我寫的很亂的時候,就對了,那是因為我查閱了大量的資料提取出來的,因為有點東西不太理解,所以你會感覺有的部分重複了,也不是重複,只是後面對前面的內容進行梳理了一些,需要耐心的看到最後
自定義元素
我們發現自定義元素總是有破折號的Q,<my-component>
或 <bacon-cheese>
因為瀏覽器供應商已承諾不創建其名稱中包含短劃線的新內置元素,以防止衝突
<app-element></app-element>
<element></element>
const appElement = document.querySelector('app-element');
console.log(appElement.constructor.name);
// HTMLElement類型的
const element=document.querySelector('element')
console.log(element.constructor.name);
// HTMLUnknownElement
上面兩個自定義元素,我們通過constructor.name
知道HTML 元素類型
<app-element>
實際上是一個自定義元素, 他基於HTMLElement 上標記的基本數據類型<element>
數據類型HTMLUnknownElement
, 是一個無效的HTML元素,瀏覽器並不知道它是什麼元素
class MyComponent extends HTMLElement {
connectedCallback() {
this.innerHTML = `<h1>Hello world</h1>`;
}
}
customElements.define('my-component', MyComponent);
<my-component></my-component>
customElements
引用customElements
,將返回瀏覽器載入自定義元素的全局記錄,類似於註冊表
方法 | 描述 |
---|---|
customElement.define(``name,class(function)) |
在頁面上定義一個自定義元素。 |
customElement.get(``name) |
獲取已定義的自定義元素的類。 |
customElement.whenDefined(``name) |
帶回 定義自定義元素時。 |
customElement.upgrade(``node) |
允許您手動更新自定義元素。 |
我們通過customElements.define()
定義自定義元素
獲取自定義元素
class AppElement extends HTMLElement {
/* ... */
}
customElements.define("app-element", AppElement);
customElements.get("app-element") === AppElement; // true
.get()
獲得所請求的自定義元素的類
特定的操作
customElements.define('my-counter', MyCounter);
customElements.whenDefined('my-counter').then(()=>{
console.log('xxx');
})
簡單的理解,我們在自定義元素 初始化後,進行的一些操作
更新操作
customElements.upgrade
// 創建一個自定義元素
const element = document.createElement("app-element");
// 我們把這個自定義元素定義好
class AppElement extends HTMLElement { /* ... */ }
customElements.define("app-element", AppElement);
console.log(element.constructor === HTMLElement); // true
//我們更新下這個元素,他已經從 HTMLElement=>AppElement
customElements.upgrade(element)
ae.constructor === HTMLElement; // false
ae.constructor === AppElement; // true
我們在.createElement()
定義前,他是HTMLElment
類型,但是upgrade
更新後,他就是AppElement
,
所以有必要進行手動更新
自定義元素的生命周期
connectedCallback()
是從元素的分離constructor
出來的
connectedCallback
通過用於講內容添加到元素
影子DOM
影子dom的特點
<div class="element">
#shadow-root
<div class="inner-element">
...
</div>
</div>
shadowRootInit
element.attachShadow(shadowRootInit);
shadowRootInit設置
{mode:'open'}
element.shadowRoot // 返回一個ShadownRoot對象
root元素可以從js外部訪問根節點
{mode:'closed'}
element.shadowRoot // null
拒絕js外部返回關閉的shadow
<slot>
包含文檔內容的內容
<div id="example">我是本來的元素,</div>
<script>
let example = document.getElementById('example');
let shadowRoots = example.attachShadow({mode: 'open'});
shadowRoots.innerHTML = `<style>
button {
background: tomato;
color: white;
}
</style>
<button id="button"><slot></slot> 我是添加的內容</button>`;
</script>
HMTL模板
template
元素是HTML流中可以標記重複使用的程式碼模組,但是這些模組不能立即呈現
<template id="books">
<li><span class="title"></span> — <span class="author"></span></li>
</template>
<ul id="contents"></ul>
<script>
const books = [
{ title: 'The Great Gatsby', author: 'F. Scott Fitzgerald' },
{ title: 'A Farewell to Arms', author: 'Ernest Hemingway' },
{ title: 'Catch 22', author: 'Joseph Heller' }
];
const fragment=document.querySelector('#books')
const contents = document.querySelector('#contents');
books.forEach(book=>{
// 創建內容實例
const instance=document.importNode(fragment.content,true)
instance.querySelector('.title').innerText=book.title;
instance.querySelector('.author').innerText=book.author;
// 添加到dom上
contents.appendChild(instance)
})
</script>
我們發現我們使用模板的時候,我們需要把javascript
// 拿到 <template></template> 標籤
const template = document.querySelector('template');
const node = document.importNode(template.content, true);
document.body.appendChild(node);
使用document.importNode
允許我們在多個位置重用相同模板內容的實例
webComponent 在項目的使用
新建一個最小的基點
class AppElement extends HTMLElement {
constructor() {
super();
}
}
customElements.define("app-element", AppElement);
我們命令的時候要養成一個良好的習慣,文件通過與類命名AppElement.js
在文檔中載入組件javascript 文件
<script src="/components/AppElement.js"></script>
這樣我們就可以在html添加這個組件
<app-element></app-element>
或者我們用js的形式添加
const appElement = document.createElement("app-element");
document.body.appendChild(appElement);
或者我們放在一個根文件中
<script type="module" src="/js/index.js"></script>
這樣我們就可以在index.js
中使用
import "./components/AppElement.js";
組件屬性
我們可以在constructor
添加屬性或者成員
class AppElement extends HTMLElement {
#role='devel'
constructor() {
super();
this.name = "Manz";
this.life = 5;
this.#role='js Devel'
}
test(){
}
#provateTest(){
}
}
也可以添加私有屬性和方法
執行方法
上面我們在自定義元素內部寫了一些方法
<app-element onClick="this.test()"></app-element>
我們發現他會執行public公共
類型的方法,私有方法只能在類內部執行
對於自身而且創建就執行靜態方法,默認的情況的其實this
可以不寫因為默認調用的就是內部的方法
生命周期
特性 | 描述 |
---|---|
constructor() |
已創建一個特定的自定義元素,該元素已在註冊表中定義。 |
connectedCallback() |
在自定義元素已連接到HTML文檔的DOM。! |
disconnectedCallback() |
在自定義元素已從HTML文檔的DOM斷開。! |
adoptedCallback() |
該自定義元素被移動到一個新文件(常見於iframes )。 |
attributeChangedCallback() |
自定義元素的觀察屬性已被修改。 |
我們可以通過document.createElement
或new AppElement()
手動創建元素
不要忘記寫super()
,因為我要擴展到HTMLElement
// 調用dom時候執行
connectedCallback() {
this.textContent='ddd'
}
// 刪除dom時候執行
disconnectedCallback(){
console.log(333);
}
我們發現在操作dom的時候connectedCallback
一些方法
在刪除了dom的時候,會調用disconnectedCallback
adoptedCallback() 自定義元素移動一個新文件(這個我暫時不清楚),不太清楚現實的用意在哪
變更檢測
可以使用HTML
元素的屬性
方法 | 描述 | 返回值 |
---|---|---|
.hasAttributes() | 元素有屬性嗎? | boolean |
.getAttributeNames() | 返回一個array屬性的小寫屬性值 | Array |
.hasAttribute(name) | 查詢某個name是否存在 | boolean |
.getAttribute(name) | 返回name的屬性值,不存在返回null | string |
.removeAttribute(name) | 刪除屬性name | |
.setAttribute(name,value) | 將屬性設置name-value | |
.toggleAttribute(name,[boolean]) | 如果存在則刪除,不存在則添加 | boolean |
特性 | 描述 |
---|---|
static get observedAttributes() |
觀察屬性以通知更改。 |
attributeChangedCallback(``name,``old,``now) |
它會關閉,當他們改變。 |
class AppElement extends HTMLElement {
static get observedAttributes() {
return ["value", "isEnabled"];
}
attributeChangedCallback(name, old, now) {
console.log(` ${name} ------ ${old} ---- ${now}.`);
}
}
static getter observedAttributes()
返回我們要觀察的屬性名稱
每當我們的屬性修改的時候,都會調用attributeChangedCallback()
方法
屬性名稱name,之前的old
值和當前的值now
每當屬性的修改都會調用這個函數
寫一個類似vue的完整版實例
<div id="templates"></div>
<template id="templateOne">
<style>
.aaa{
color:red;
font-size: 12px;
}
</style>
<div class="aaa">12211212</div>
<button onClick="clickDown()">Click</button>
<script>
function clickDown(){
alert(1)
}
</script>
</template>
<script>
let template=document.querySelector('#templateOne')
let content=document.querySelector('#templates')
content.appendChild(
document.importNode(template.content,true)
)
</script>
自定義組件的完整樣例
<my-counter></my-counter>
<script>
const template = document.createElement('template');
template.innerHTML = `
<style>
* {
font-size: 200%;
}
span {
width: 4rem;
display: inline-block;
text-align: center;
}
button {
width: 4rem;
height: 4rem;
border: none;
border-radius: 10px;
background-color: seagreen;
color: white;
}
</style>
<button id="dec">-</button>
<span id="count"></span>
<button id="inc">+</button>`;
class MyCounter extends HTMLElement {
constructor() {
super();
this.count = 0;
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.shadowRoot.appendChild(template.content.cloneNode(true));
this.shadowRoot.getElementById('inc').onclick = () => this.inc();
this.shadowRoot.getElementById('dec').onclick = () => this.dec();
this.update(this.count);
}
inc() {
this.update(++this.count);
}
dec() {
this.update(--this.count);
}
update(count) {
this.shadowRoot.getElementById('count').innerHTML = count;
}
}
customElements.define('my-counter', MyCounter);
</script>