Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

初识web-components和todolist实现 #3

Open
flytam opened this issue Jan 10, 2019 · 0 comments
Open

初识web-components和todolist实现 #3

flytam opened this issue Jan 10, 2019 · 0 comments
Labels

Comments

@flytam
Copy link
Owner

flytam commented Jan 10, 2019

在当下,前端三巨头vue react ng都是提倡组件化开发的,在原生领域,web-components也逐渐成为标准。近段时间大热的omi就是基于web-components实现的

web-components主要由3部分组成

  • custom-elements
  • shadow-dom
  • slot template

custom-elements

从字面意思可以知道这是自定义元素的意思。区别于原生html元素,我们可以自己定义它的行为。按照是否从原生html元素继承,可分下面两类

两类custom元素
  • Autonomous custom elements。完全自定义元素
  • Customized built-in elements .从常规html元素继承的
生命周期

custom-elements 比较赞的一点是具有以下的生命周期

  • connectedCallback 连接到dom后触发
    类似于react的componentDidMount,当自定义元素首次加载到dom会触发,如果我们想获取传入的attributes来选择展示内容的话,需要将逻辑放在这个周期内而不是constructor中,constructor是取不到attributes的值,还需要注意的是,受html限制,通过html传入的attributes值都是字符串

  • disconnectedCallback 当自定义元素从DOM树中脱离触发
    对于绑定元素的事件监听,可以在这里进行解绑,防止内存泄漏

  • adoptedCallback 当自定义元素移动到新的document触发

  • attributeChangedCallback 自定义元素属性值改变时触发。这个需要配合static get observedAttributes(){return ['需要监听的属性']}使用,表示哪些属性变化才会触发这个生命周期。对于动态attributes进行渲染,这个非常好用

一个Autonomous custom elements web-components通常使用方法如下

class App extends HTMLElement {
  static get observedAttributes() {
    return ['text'];
  }

  constructor() {
    super();
    // 在constructor中初始化
// 创建一个shadow元素,会css隔离的,一些原生html元素例如video等也是基于shadowdom实现的
    const shadow = this.attachShadow({mode: 'open'});

    const div = document.createElement('div');
    
    // web-components内的样式,外部不影响
    const style = document.createElement('style');
    
    shadow.appendChild(style);
    shadow.appendChild(div);
  }

  connectedCallback() {}

  disconnectedCallback() {}

  adoptedCallback() {}

  attributeChangedCallback(name, oldValue, newValue) {}
}

customElements.define('my-app', App);

如果是扩展原生元素的web-components则是类似

class CustomP extends HTMLParagraphElement {
 ...
}
customElements.define('custom-p', CustomP,{extend:'p'});

shadom-dom

shadom-dom操作和平常的dong操作差不多,对this.attachShadow({mode: 'open'});。shadow-dom最大的好处就是实现了dom隔离。例如css只会对内部的shadow-dom有效,并不影响外部的元素。这应该是css最完美的解决方案了,目前很多组件化css解决方案css modules、各种css in js都不太优雅

// this是custom-element
    const shadow = this.attachShadow({mode: 'open'});

    const div = document.createElement('div');
    
    const style = document.createElement('style');
    
    shadow.appendChild(style);
    shadow.appendChild(div);

template 和 slot

类似于vue的概念,用来实现html复用和插槽效果

template结合custom-elements用法
<template id="my-paragraph">
 <style>
   p {
     color: white;
     background-color: #666;
     padding: 5px;
   }
 </style>
 <p>My paragraph</p>
</template>
// mdn例子
customElements.define('my-paragraph',
 class extends HTMLElement {
   constructor() {
     super();
     let template = document.getElementById('my-paragraph');
     let templateContent = template.content;

     const shadowRoot = this.attachShadow({mode: 'open'})
       .appendChild(templateContent.cloneNode(true));
 }
})
slot用法则和vue的基本一致

使用

web-components的使用非常方便,有几种方法
1、直接html中使用自定义标签

<custom-element></custom-element>

2、通过js引入

const CustomElement = customElements.get('custom-element');
const customElement = new CustomElement();
// or
document.createElement('custom-elemen')

// append进dom

实际开发结合polymer体验更佳

最后写了个web-compoennts todolist

demo

代码如下

// TodoList.js
class TodoList extends HTMLElement {
  constructor() {
    super();
    this.shadowdom = this.attachShadow({ mode: "open" });
    this.handleRemove = this.handleRemove.bind(this);
  }

  get data() {
    const dataAttribute = this.getAttribute("data");
    if (dataAttribute) {
      return Array.isArray(dataAttribute)
        ? dataAttribute
        : JSON.parse(dataAttribute);
    } else {
      return [];
    }
  }

  set data(val) {
    this.setAttribute("data", JSON.stringify(val));
    this.render();
  }

  handleRemove(e) {
    this.remove(e.detail.index);
  }

  connectedCallback() {
    this.render();
    this.shadowdom.addEventListener("sub", this.handleRemove);
  }
  disconnectedCallback() {
    this.shadowdom.removeEventListener("sub", this.handleRemove);
  }
  //渲染内容
  render() {
    // 简便起见,每次渲染前先清空shadowdom的内容
    let last = null;
    while ((last = this.shadowdom.lastChild)) {
      this.shadowdom.removeChild(last);
    }
    this.data.forEach((item, index) => {
      const todoiterm = new (customElements.get("todo-iterm"))();
      todoiterm.innerHTML = `<span slot='text'>${item}</span>`;
      todoiterm.setAttribute("data-index", index);

      this.shadowdom.appendChild(todoiterm);
    });
  }

  addIterm(text) {
    this.data = [...this.data, text];
  }
  remove(deleteIndex) {
    this.data = this.data.filter((item, index) => index != deleteIndex);
  }
}
customElements.define("todo-list", TodoList);
// TodoIterm.js
class TodoIterm extends HTMLElement {
  constructor() {
    super();
    const template = document.getElementById("list-item");
    const templateContent = template.content;
    const shadowdom = this.attachShadow({ mode: "open" });

    shadowdom.appendChild(templateContent.cloneNode(true));
    shadowdom.getElementById("sub").onclick = e => {
      const event = new CustomEvent("sub", {
        bubbles: true,
        detail: { index: this.dataset.index},
      });
      this.dispatchEvent(event)
    };
  }
}
customElements.define("todo-iterm", TodoIterm);
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>web-components</title>
    <script src="./TodoList.js"></script>
    <script src="./TodoIterm.js"></script>
  </head>
  <body>
    <template id="list-item">
      <style>
        * {
          color: red;
        }
      </style>
      <li><slot name="text">nothing write</slot><button id="sub">-</button></li>
    </template>
    <!-- <todo-list></todo-list> -->
    <div>
        <input id='input'/>
        <button id='add'>+</button>
    </div>
    <script>
        // 加载web compoennts
        const List = customElements.get('todo-list');
        const todoList = new List()

        document.body.appendChild(todoList)
        document.getElementById('add').onclick = function(){
           const value = document.getElementById('input').value
           todoList.addIterm(value)
        }
     </script>
  </body>
</html>

一些需要注意的地方:
1、通过html传递属性值,由于是通过attributes传入,所以都是字符串
2、组件之间的通信传递需要通过自定义事件

@flytam flytam added the Go label Nov 16, 2019
@flytam flytam added 原生 and removed Go labels Jun 9, 2020
@flytam flytam changed the title 初识web-components & todolist实现 初识web-components和todolist实现 May 4, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant