8

从原生web组件到框架组件源码(一)

 3 years ago
source link: http://www.cnblogs.com/fangdongdemao/p/13908840.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

温馨提醒,当你觉得看我写的很乱的时候,就对了,那是因为我查阅了大量的资料提取出来的,因为有点东西不太理解,所以你会感觉有的部分重复了,也不是重复,只是后面对前面的内容进行梳理了一些,需要耐心的看到最后

iMBBbeq.png!mobile

自定义元素

我们发现自定义元素总是有破折号的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>

RJfu6fV.png!mobile

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 可以不写因为默认调用的就是内部的方法

生命周期

ja2u2uu.png!mobile

特性 描述 constructor() 已创建一个特定的 自定义元素 ,该 元素 已在注册表中定义。 connectedCallback()自定义元素 已连接到HTML文档的DOM。 disconnectedCallback()自定义元素 已从HTML文档的DOM断开。 adoptedCallback()自定义元素 被移动到一个新文件(常见于 iframes )。 attributeChangedCallback() 自定义元素 的观察属性已被修改。

我们可以通过 document.createElementnew 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>

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK