【js进阶10】Web Components

JavaScript | 2020-09-29 20:28:52 721次 0次

一、组件定义

定义组件,样式全局可控。

...
    <component-head>
        <style type="text/css">
	    .head{
	        background: #00739C;
		color: #fff;
            }
	</style>
	<div class="head">head</div>
    </component-head>
...
<script type="text/javascript">
    class Head extends HTMLElement {
        constructor() {
	    super();
        }
    }
    // 定义组件名称
    window.customElements.define('component-head', Head);
</script>

直接在 html 中使用自定义的组件,或者通过 js 注入的方式也可以:

...
const el = customElements.get('component-head');
const myElement = new el();
document.body.appendChild(myElement);


二、template

...
<component-head></component-head>
<template id="temp-head">
    <style type="text/css">
        .head{
	    background: #00739C;
	    color: #fff;
	}
    </style>
    <div class="head">head</div>
</template>
...

<script type="text/javascript">
    class Head extends HTMLElement {
        constructor() {
            super();
            // 获取模板中内容
            var templateElem = document.getElementById('temp-head').content;
            this.appendChild(templateElem);
        }
    }
    window.customElements.define('component-head', Head);
</script>

如上可以简单的使用一个模板,在自定义组件中展示,比如实现一个列表,将 template 代码快单独抽离,并且可以在组件中传入数据参数:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
        <link id="temp-head" rel="import" href="./temp.html">
    </head>
    <body>
        <component-head 
            name = "11111"
        ></component-head>
        
        <component-head
            name = "222222"
        ></component-head>
    </body>
</html>

<script type="text/javascript">
    // 加载模板对象
    function importTemp(linkId){
        return document.querySelector("#"+linkId).import.querySelector("#"+linkId).content.cloneNode(true);
    }

    class Head extends HTMLElement {
        constructor() {
            super();
            this.init();
        }
        init(){
            var templateElem = importTemp('temp-head');
            // 模板赋值
            templateElem.querySelector('.temp-name').innerText = this.getAttribute('name');
            this.appendChild(templateElem);
        }
    }
    window.customElements.define('component-head', Head);
</script>

上面通过 link 标签引入模板文件,temp.html 定义如下 :

<template id="temp-head">
  <style type="text/css">
	.temp-name{
		background: #00739C;
		color: #fff;
		margin-bottom: 5px;
	}
  </style>
  <div class="temp-name"></div>
</template>


三、slot

组件中创建插槽:
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
        <link id="temp-head" rel="import" href="./temp1.html">
    </head>
    <body>
        <component-head 
            name = "苏哲"
        >
            <span slot="my-text">components1 is great</span>
        </component-head>
        <component-head
            name = "111"
        >
            <span slot="my-text">no no no</span>
        </component-head>
    </body>
</html>

<script type="text/javascript">

    ...

    class Head extends HTMLElement {
        constructor() {
        super();
            this.init();
        }
        init(){
            var templateElem = importTemp('temp-head');
            
            // 需要使用 Shadow DOM
            const shadowRoot = this.attachShadow({mode: 'open'})
                    .appendChild(templateElem);
        }
    }
    window.customElements.define('component-head', Head);

</script>

模板中定义插槽:

<div class="temp-name">
    <slot name="my-text">NEED NAME</slot>
</div>


四、Shadow DOM

这个方法目前被使用的最为广泛,比如实现微前端等,Shadow DOM 允许将隐藏的 DOM 树附加到常规的 DOM 树中——它以 shadow root 节点为起始根节点,在这个根节点的下方,可以是任意元素,和普通的 DOM 元素一样。

Shadow host:一个常规 DOM节点,Shadow DOM 会被附加到这个节点上。

Shadow tree:Shadow DOM内部的DOM树。

Shadow boundary:Shadow DOM结束的地方,也是常规 DOM开始的地方。

Shadow root: Shadow tree的根节点。


样式隔离:

<!DOCTYPE>
<html>
    <head>
        <meta charset="utf-8"/>
        <title>webcomponent</title>
        <link id="temp-head" rel="import" href="./temp1.html">
        <style type="text/css">
            .p1{
                color: orange;
            }
        </style>
    </head>
    <body>
        <p class="p1">common dom</p>
        <my-element name="组件1">
            <!----卡槽---->
            <span slot="title">web components 组件1 卡槽</span>
        </my-element>
    </body>
</html> 
<script type="text/javascript">
    ...
    // 同 slot 的创建方式

</script>
模板文件修改如下:
<template id="temp-head">
  <style>
    #container {
      --bg:#932;
      color: var(--bg);
      border: 1px solid darkred;
    }
    .p1{
    	color: #fff;
    	background: var(--bg);
    }
  </style>
  <div id="container">
  	<!--卡槽-->
  	<slot name="title"></slot>
  	<p class="p1">shadow Dom</p>
  </div>
</template>

全局的样式和 shadow Dom 中定义的样式相互不冲突,效果如下:

微信截图_20200922195616.png


事件:

<!DOCTYPE>
<html>
    <head>
        <meta charset="utf-8"/>
        <title>webcomponent</title>
        <link id="temp-head" rel="import" href="./temp1.html">
    </head>
    <body>
        <my-element name="组件1">
            <!----卡槽---->
            <span slot="title">组件1 卡槽</span>
        </my-element>
        <my-element name="组件2">
            <!----卡槽---->
            <span slot="title">组件2 卡槽</span>
        </my-element>
    </body>
</html> 
<script type="text/javascript">
	// 加载模板对象
    function importTemp(linkId){
        return document.querySelector("#"+linkId).import.querySelector("#"+linkId).content.cloneNode(true);
    }
        
    class MyElement extends HTMLElement {
        constructor() {
            super();
            //自定义事件
            this.onclick = ()=>{
                let coun = this.getAttribute('bar') || 0
                this.setAttribute('bar', ++coun) 
            }
            
            const templateElem = importTemp('temp-head');
            const shadowRoot = this.attachShadow({mode: 'open'});
            shadowRoot.appendChild(templateElem);
            
            this.container = shadowRoot.querySelector('#container');
            this.shadow = shadowRoot
        }
        
        //只会监听修改这里定义的属性
        static get observedAttributes() {
            return ['foo', 'bar'];
        }

        //组件属性改变回调监听
        attributeChangedCallback(attr, oldVal, newVal) {
            let name = this.getAttribute('name')
            switch(attr) {
                case 'foo':
                console.log(name+',修改了foo属性,旧属性:'+oldVal+',新属性:'+newVal)
                this.container.style.background = '#808080';
                break;
                case 'bar':
                console.log(name+',修改了bar属性,旧属性:'+oldVal+',新属性:'+newVal)
                this.container.style.background = '#516588';
                break;
            }
        }
        
        connectedCallback() {
            console.log('被创建了')
            //添加需要的模板
            const content = this.shadow.querySelector('#view').content;
            this.container.appendChild(content);
        }
        
        outHandle(){
            console.log('我在外部被调用了')
        }
    }

    //等待被创建  异步
    customElements.whenDefined('my-element').then(() => {
        const element = document.querySelector('my-element');
        element.setAttribute('foo', 1) 
        element.outHandle()
        //外部调用事件方法  会覆盖类中的方法
        //	  element.onclick = ()=>{
        //	  	alert(11)
        //	  }
    })
    window.customElements.define('my-element', MyElement);
	
</script>
// 模板文件
<template id="temp-head">
  <style>
    #container {
      --bg:#932;
      color: var(--bg);     
    }
    .p1{
    	color: #fff;
    	background: var(--bg);
    }
  </style>
  <!--模板功能-->
  <template id="view">
    <p>这是模板后期添加的组件,我被显示了</p>
  </template>
  <div id="container">
  	<!--卡槽-->
  	<slot name="title"></slot>
  	<p class="p1"> 点我看打印的效果</p>
  </div>
</template>

0人赞

分享到: