美文网首页
web components

web components

作者: 林llgb | 来源:发表于2023-03-30 04:45 被阅读0次

    概念:

    web components是原生的组件化开发技术,它可以让我们创建自定义的html元素,并且功能和样式都会封装在组件内部,不会影响其他的元素。

    注意:web components与现有的react、vue等库不冲突,是相辅相成的。

    web components里面有三个概念:

    1. Custom elements(自定义元素)
    2. Shadow Dom(影子元素)
    3. HTML templates(HTML模版)

    Custom elements

    自定义元素是在js中通过继承HTMLElement或现有的HTML DOM对象来实现的。

    class CustomElement extends HTMLElement { 
      ...
    }
    
    生命周期:
    • connectedCallback(挂载时): 当自定义元素第一次被连接到文档DOM时被调用。
    • disconnectedCallback(卸载时): 当自定义元素与文档DOM断开连接时被调用。
    • adoptedCallback(移动时): 当自定义元素被移动到新文档时被调用。
    • attributeChangedCallback(属性变化时): 当自定义元素的一个属性被增加、移除或更改时被调用。

    Shadow Dom

    shadow dom与普通的document对象几乎一样,它是专门用来操作自定义的html元素,它也是一个树形结构,但是shadow dom完全独立于普通的dom,相当于是一个隔离区,需要把它挂载到一个普通的dom节点上。


    HTML templates

    html 模版是方便于编写自定义元素的html结构和css样式。它包括两个标签:template和slot。这里的slot与vue中的slot类似,用于指定一些占位的插槽,在外边可以用真实的元素替换掉。

    <template>
        <style>
        </style>
        <div>
            <h1></h1>
            <slot name="content"></slot>
        </div>
    </template>
    

    举例

    <blog-post>

    1. index.html定义模版
    <template id="blog-post-template">
        <div>
            <h1>博客标题</h1>
            <slot name="content"></slot>
            <button>查看全文</button>
        </div>
    </template>
    
    1. 在BlogPost.js中定义BlogPost类
    class BlogPost extends HTMLElement {
      constructor() {
        // 调用父类的构造函数才能初始化
        super() 
        const template = document.getElementById('blog-post-template')
        // attachShadow获取shadow dom的根元素,mode为open意思是允许通过shadow dom的api来操作和访问该自定义元素内部的dom树。使用appendChild来添加到根元素中。这里cloneNode ,这样子可以使得多次使用自定元素时,内容都是独立的。
        this.attachShadow({ mode: "open" }).appendChild(
          template.content.cloneNode(true)
        )
      }
    }
    // 将BlogPost注册到自定义元素注册表中,这里的名字必须带有中划线,目的是和原生的html元素区分开
    customElements.define("blog-post", BlogPost)
    
    1. 在index.html使用
    <body>
        <blog-post>
            <article slot="content">这是博客内容</article>
        </blog-post>
        
        <!-- type 必须为 module,否则变量名会冲突 -->
        <script src="BlogPost.js" type="module"></script>
    </body>
    
    1. 设置自定义的title属性

    index.html:

    <blog-post title="博客标题">
        <article slot="content">这是博客内容</articel>
    </blog-post>
    

    BlogPost.js

    class BlogPost extends HTMLElement {
      constructor() {
        // ...
        // this.shadowRoot是shadowDom中的根元素,它要在调用this.attachShadow之后才能使用。其中的api和document中的几乎一样。
        this.titleEle = this.shadowRoot.querySelector("h1")
      }
      
      static get observedAttributes() {
        return ["title"]
      }
    
      attributeChangedCallback(name, oldValue, newValue) {
        if (name === "title") {
          this.titleEle.textContent = newValue
        }
      }
    }
    
    
    1. 设置样式

    在template中使用style标签编写样式: 在template中写的样式,只会应用到template内部的元素中。

    <template>
        <style>
            div {}
            h1 {}
            button {}
        </style>
        <div>
            <h1></h1>
            <slot name="content"></slot>
            <button>查看全文</button>
        </div>
    </template>
    
    1. 在js中定义template
      在html中定义多个template会占用大量的空间和代码,不好维护。可以使用在js中定义template。
    const template = document.createElement("template")
    template.innerHTML = `
      <style>
        ...
      </style>
      <div>
        <h1></h1>
        <slot name="content"></slot>
        <button>查看全文</button>
      </div>
    `
    class BlogPost extends HTMLElement {
      constructor() {
        super()
        // const template = document.getElementById('blog-post-template')
        this.attachShadow({ mode: "open" }).appendChild(
          template.content.cloneNode(true)
        )
      }
    }
    
    

    <post-list>

    1. 定义模版、创建PostList类
    const template = document.createElement("template")
    template.innerHTML = `
      <style>
        div {
          ...
        }
        article {
          ...
        }
      </style>
      <div></div>
    `
    
    class PostList extends HTMLElement {
      constructor() {
        super()
        this.attachShadow({ mode: "open" }).appendChild(
          template.content.cloneNode(true)
        );
      }
    }
    
    customElements.define("post-list", PostList);
    
    
    1. 加载数据、创建博客列表
    class PostList extends HTMLElement {
      constructor() {
        ...
      }
    
      async connectedCallback() {
        const res = await fetch("https://jsonplaceholder.typicode.com/posts")
        const posts = await res.json()
        this.initPosts(posts)
      }
      
      // 创建博客列表
      initPosts(posts) {
        const div = this.shadowRoot.querySelector("div")
        posts.forEach(post => {
          const blogPostEle = div.appendChild(document.createElement("blog-post"))
    
          // 博客标题
          blogPostEle.setAttribute("title", post.title)
    
          // 博客文章
          const article = blogPostEle.appendChild(
            document.createElement("article")
          )
          article.slot = "content"
          article.innerHTML = post.body
        })
      }
    }
    
    customElements.define("post-list", PostList)
    
    
    1. 给添加事件
    class BlogPost extends HTMLElement {
      constructor() {
        // ...
        this.articleSlot = this.shadowRoot.querySelector("slot")
        this.content = ""
        this.article = null // 替换后的元素对象
      }
    
      slotChange() {
        // assignedElements获取真实的替换元素数组 
        const elements = this.articleSlot.assignedElements()
        const article = elements[0]
        // 真实的article元素保存到article属性中
        this.article = article
        // 保存博客全文到content属性中
        this.content = this.article.innerHTML
        // 把article中的博客全文改成摘要
        this.article.innerHTML = this.getExcept()
      }
    
      getExcept() {
        return this.content.slice(0, 60) + "..."
      }
    
      connectedCallback() {
        this.articleSlot.addEventListener("slotchange", this.slotChange.bind(this));
      }
    }
    
    1. 给按钮添加点击事件,维护显示/隐藏状态
    class BlogPost extends HTMLElement {
      constructor() {
        // ...
        this.buttonEle = this.shadowRoot.querySelector("button")
        this.showFullArticle = false
      }
    
      /**
       * 按钮点击事件,控制是否显示全文。
       */
      toggleFull() {
        this.showFullArticle = !this.showFullArticle
        if (this.showFullArticle) {
          this.article.innerHTML = this.content
          this.buttonEle.textContent = "隐藏全文"
        } else {
          this.article.innerHTML = this.getExcept()
          this.buttonEle.textContent = "查看全文"
        }
      }
    
      connectedCallback() {
        // ...
        this.buttonEle.addEventListener("click", this.toggleFull.bind(this))
      }
    
      disconnectedCallback() {
        // 卸载事件监听,在组件销毁的时候,释放内存
        this.buttonEle.removeEventListener("click", this.toggleFull())
        this.articleSlot.removeEventListener("slotchange", this.slotChange)
      }
    }
    

    总结

    创建自定义元素的步骤:

    1. 编写模版代码和样式
    2. 创建自定义元素class,继承HtmlElement
    3. 使用CustomeElement.define() 注册元素
    4. 在构造函数中使用super() 调用父类构造函数,并编写初始化逻辑
    5. 使用生命周期加载数据、注册监听和卸载监听

    React与Web Component互相调用

    Web Component可以在React中使用。但因为React有自己的模块化机制(Component),以及自己的事件系统(SyntheticEvent), 考虑到调用方式和事件系统的统一,官方推荐将web component包装为react component。

    class HelloMessage extends React.Component{
      render() {
        return <div>Hello <x-search>{this.props.name}</x-search>!</div>;
      }
    }
    

    React模块也可作为Web Component使用。只需在attachedCallback中调用ReactDOM.render。

    class XSearch extends HTMLElement {
      connectedCallback() {
        const mountPoint = document.createElement('span');
        this.attachShadow({ mode: 'open' }).appendChild(mountPoint);
    
        const name = this.getAttribute('name');
        const url = 'https://www.google.com/search?q=' + encodeURIComponent(name);
        ReactDOM.render(<a href={url}>{name}</a>, mountPoint);
      }
    }
    customElements.define('x-search', XSearch);
    

    文中有什么错误或者不足之处,欢迎大家指正...

    相关文章

      网友评论

          本文标题:web components

          本文链接:https://www.haomeiwen.com/subject/gosakdtx.html