首页 > 其他分享 >WebComponent原生的组件化闲谈

WebComponent原生的组件化闲谈

时间:2024-07-02 11:54:44浏览次数:1  
标签:WebComponent 自定义 DOM 元素 组件 shadow 闲谈 Shadow

一、web component是啥?

web componentw3c的一套使得开发者可以将HTML页面的功能封装成自定义标签(custom elements)的标准,可以类比目前流行的ReactVue等前端框架的组件化思想,不过web component是前端标准提供的原生的组件化思想,其实和现有框架的组件化思想有异曲同工之妙,不同的是可能写法上面略有不同,再者原生者,不需要第三方的库、工具或者库进行支持。所以,我们能够将日常开发中比较常见的组件,或者将来可能需要在不同框架的应用场景中实现的功能性组件s进行抽象分离并将其原生组件化(再不影响项目开发时间效率的时候去制作),在平时的工作中有余力的去丰富开发小组中的原生组件,前提是我们工作的过程中自我体会和判断哪些模块封装成组件我们觉得是有意义的。 MDN文档:https://developer.mozilla.org/zh-CN/docs/Web/API/Web_components

目前的web component是由三项主要技术组成:

  • Custom element(自定义元素)
  • Shadow DOM(影子DOM)
  • HTML template(HTML模板)

二、custom elements的使用

自定义元素,这个元素,可以理解成HTML标签元素,比如divspanvideo等标签元素,而web component标准允许开发者使用custom elements进行自定义标签元素,下面会通过使用原生的方式去封装一个模型查看器对web component的整体使用流程进行介绍。

  • 基本介绍

    CustomElementRegistry这个接口的实例是用来处理custom element的,允许开发者进行自定义组件的注册并返回自定义标签元素;

    customElementRegistry.define(domstring,class,extends可选)方法用来注册一个自定义元素,该方法的参数有三个,分别表示:

    • DOMString 所创建的自定义元素的字符串名称,标准为了进行原生标签元素的区分,提议自定义标签元素必须使用-短横线链接表示;
    • class代表的是自定义标签元素的类对象,就是组件标签的样式模板和逻辑的封装类;
    • extends 可选参数是一个对象,它可以指定自定义元素继承自哪一个内置的元素。
    customElements.define('k-model-view',KModelView);
    

上面的代码就表示,我们定义了一个元素标签,和vue原生的组件注册很像,<k-model-view></k-model-view>,指定的标签元素的类是KModelView,我们没有指定该元素继承自某个指定的元素,就会默认继承自HTMLElement;对于class类对象,我们可以使用JavaScript的类语法进行定义。

class KModelView extends HTMLElement {
  construtor(){
    super();
    // 组件的逻辑代码
  }
}

以上一个粗略的原生标签组件注册就完成了,总是感觉少了点什么,正常的一个组件很重要的一个部分就是生命周期,提供的生命周期才能让开发者进行组件逻辑的开发,才能完成一个完美的组件。是的custom elements也是具有生命周期的。

  • 生命周期

    自定义元素custom elements和目前主流前端框架一样,也是拥有生命周期的,在组件使用的不同的生命周期中调用不同的生命周期的回调函数,具体的生命周期的回调函数详见一下四个:

    • connectedCallback: 当自定义元素custom elements首次被插入到文档DOM时候被调用;

    • disconnectedCallback: 当自定义元素从文档DOM中移除时候被调用;

    • adoptedCallback: 当自定义元素被移动到新的文档的时候会被调用;

    • attributeChangedCallback: 当元素的属性被增加、修改和删除时候会被调用,但是如果想要在元素的属性发生变化时候该回调被触发,则需要在calss中使用静态方法:observedAttributes() get函数来进行监听;

      static get observedAttributes() {return ['需要进行监听变化的属性'];
      

三、shadow DOM的使用

web components使用最大的意义在于封装、复用、组件化,那么自定义的组件,最想让其属性还有样式隐藏和隔离起来,不至于影响DOM文档流,那么shadow dom(影子DOM)就提供了这样的一个接口供开发者使用;

Shadow DOM 允许将隐藏的 DOM 树附加到常规的 DOM 树中——它以 shadow root 节点为起始根节点,在这个根节点的下方,可以是任意元素,和普通的DOM元素一样,结构解释如下图:

图片.png

  • shadow DOM的一些特定名词解释如下:

    • Shadow host:一个常规 DOM 节点,Shadow DOM 会被附加到这个节点上;
    • Shadow treeShadow DOM 内部的 DOM 树;
    • Shadow boundaryShadow DOM 结束的地方,也是常规 DOM 开始的地方;
    • Shadow root: Shadow tree的根节点;
  • 基本使用:

    shadow Dom是可以独立的,其也不是新事物,浏览器内置元素已经有所应用,比如默认播放控制按钮的 <video> 元素为例。你所能看到的只是一个<video>标签,实际上,在它的 Shadow DOM 中,包含了一系列的按钮和其他控制器。Shadow DOM 标准允许你为你自己的元素(custom element)维护一组 Shadow DOM。而影子dom可以附加到任何一个元素上面,不仅仅是custom elements身上,但是,我们通常是将其附加到自定义元素上的。

    // 将shadow dom 添加到任意的元素身上
    let shadowRoot = elementRef.attachShadow({mode: 'open'});
    // 将shadow dom 附加到自定义元素身上
    let shadowRoot = this.attachShadow({mode: 'open'}); 
    /**
     * attachShadow()函数就是产生一个shadow dom 返回一个shadowRoot;也就是shadow tree的根节点,谁调用,
     * 就会附加相应的dom身上;
     * mode: 属性,表示shadow dom是否对外暴漏,意味着外部能否通过脚本进行对其操作、控制和访问。
     * shadow root身上就可以挂载我们需要的shadow tree了。
     * /
    

四、templateslots的使用

  • template模板

template就是类似于vue中模板的概念,可以利用它作为组件的内容,并将其添加到shadowRoot中去,成为shadow dom内置的部分;模板中相较于传统的内置dom元素,是可以添加style样式可以将样式独立封装到自定义元素中去形成样式隔离;

<template id="my-component">
  <style>
    p {
      color: white;
      background-color: #666;
      padding: 5px;
    }
  </style>
  <p>我的组件</p>
</template>
  • slots插槽

slot插槽的使用和vue中也是比较像的,很想vue中使用的具名插槽,就是允许组件在其内部预留位置,供组件使用的时候,用户可以在组件中添加自己的标签内容等等,加强了组件的使用的灵活性;

<!--组件内部定义一个name插槽-->
<p><slot name="my-name">My default text</slot></p>
<!--外部使用组件的使用可以利用组件暴漏的插槽的名字进行组件内部的填充-->
<my-component>
  <span slot="my-name">插槽内容</span>
</my-component>
<!--这样就将需要自定义的元素添加进自定义组件中去,灵活性仿佛不如vue这种框架-->

五、web component常用接口

  • customElementRegistry: 接口提供注册自定义元素和查询已注册元素的方法。要获取它的实例,请使用 window.customElements属性。

    • customElements.define(): 定义新的自定义元素;
    • customElements.get(): 返回指定的自定义元素的构造函数,如果未定义,则返回undefined;
    • customElements.upgrade(): 将更新节点子树中所有包含阴影的自定义元素,它们连接到主文档之前也是;
  • HTMLTemplateElement: 该接口来访问 HTML <template>元素的内容,该接口继承了HTMLElement的属性和方法;

  • ShadowRoot:该接口是一个shadow DOM 子树的根节点,它与文档的主 DOM 树分开渲染,可参考上图;

    • ShadowRoot.host: 附加的宿主DOM元素;
    • ShadowRoot.innerHTML: 内部的 DOM 树;
    • ShadowRoot.mode:ShadowRoot 的模式——可以是 open 或者 closed。这定义了shadow root的内部实现是否可被 JavaScript 访问及修改 — 也就是说,该实现是否公开,例如,<video> 标签内部实现无法被 JavaScript 访问及修改。

六、组件案例实现

  • 组件代码

    
    import { Tshare } from "../../utils/Tshare";
    import * as THREE from 'three';
    import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
    export default class KModelView extends HTMLElement {
    
        static get observedAttributes() { return ['width', 'height', 'model'] };
        constructor() {
            super();
            this.lights = null;
            const shadowRoot = this.initShadowDom({ mode: 'open' });
            this.T = new Tshare();
        }
    
        get model() {
            return this.getAttribute('model');
        }
    
        get width() {
            return this.getAttribute('width');
        }
        set width(value) {
            this.setAttribute('width', value);
        }
    
        get height() {
            return this.getAttribute('height');
        }
        set height(value) {
            this.setAttribute('height', value);
        }
        get lights() {
            return this.lights;
        }
        set lights(value) {
            console.log(value, 'kkjjj');
            this.initLights(value);
        }
        connectedCallback() {
            this.initWebGLEngine(this.shadowRoot.querySelector('#k-canvas'));
        }
    
        disconnectedCallback() {
            console.log('卸载');
        }
        adoptedCallback() {
            console.log('移动');
        }
        attributeChangedCallback(name, oldValue, newValue) {
            // console.log('属性变化', name, oldValue, newValue);
            switch (name) {
    
            }
        }
        initWebGLEngine(container) {
            this.T.initEnv(container).loadHdrToScene().initOrbitControls();
            this.animate();
            this.loadGLTFModel();
        }
        animate() {
            requestAnimationFrame(this.animate.bind(this)); // 改变this的指向
            this.T.renderer.render(this.T.scene, this.T.camera);
        }
        async loadGLTFModel() {
            this.loader = new GLTFLoader();
            let res = await this.loader.loadAsync(this.model);
            this.T.sceneGraph.add(res.scene);
        }
        initLights(lights) {
            if (!lights) return;
            for (let i = 0; i < lights.length; i++) {
                let light = new THREE[lights[i].type](new THREE.Color(lights[i].color));
                this.T.scene.add(light);
            }
        }
        initShadowDom(ops) {
            let shadowRoot = this.attachShadow(ops);
            shadowRoot.innerHTML = `
                <style>
                :host {
                    display: inline-block;
                    width: auto;
                    color: red;
                }
                .k-canvas {
                    // background-color: aquamarine;
                }
                </style>
                <section id="k-canvas" class="k-canvas"
                    style="width: ${this.width ? this.width : '100vw'};height: ${this.height ? this.height : "100vh"
                } ">
                </section>
        `;
            return shadowRoot;
        }
    }
    
    if (!customElements.get('k-model-view')) {
        customElements.define('k-model-view', KModelView);
    }
    
  • 组件使用

      <k-model-view id="k-model-view" width="600px" height="500px" model="./model/yz0.glb"></k-model-view>
    
  • property属性传值

    let kmodel = document.getElementById('k-model-view');
    kmodel.lights = [{ type: "AmbientLight", intensity: 1, color: "#ffffff" }];
    
  • 效果

图片.png

标签:WebComponent,自定义,DOM,元素,组件,shadow,闲谈,Shadow
From: https://www.cnblogs.com/zkh-blog/p/18279632

相关文章

  • DevExpress WinForms磁贴导航面板 & TileBar组件,让桌面应用触摸更友好!
    界面控件DevExpressWinFormsTileNavPane被设计为位于应用程序窗口的顶部(就像Ribbon一样),可以被认为是Windows桌面应用程序中传统导航元素的触摸友好版本。P.S:DevExpressWinForms拥有180+组件和UI库,能为WindowsForms平台创建具有影响力的业务解决方案。DevExpressWinForms能......
  • Vue3手写一个全局命令式loading组件
    实现效果:vue文件中,打开全局loading...2s后关闭全局命令式loading,效果展示完,直接咱就是上代码 注册:  <!--src/components/myLoading/index.vue--><template><!--添加name属性,以添加样式Transition主要做一个淡入淡出的--><Transitionname="zhLoadi......
  • 【鸿蒙学习笔记】基础组件Blank:空白填充组件
    Blank:空白填充组件Column({space:20}){Row(){Text('Bluetooth')Blank().color(Color.Yellow)Toggle({type:ToggleType.Switch}).margin({top:14,bottom:14,left:6,right:6})}.backgroundColor(Color.Pink).borderRadius(15).padd......
  • 【鸿蒙学习笔记】基础组件Progress:进度条组件
    官方文档:Progress目录标题作用最全属性迭代进度赋值风格样式作用进度条组件最全属性迭代Progress({value:20,total:100,type:ProgressType.Linear}).color(Color.Green)//颜色.width(200)//大小.height(50)//高度.value(50)//进度可更新,2......
  • springcloud-gateway 网关组件中文文档
      SpringCloud网关GreenwichSR5该项目提供了一个基于Spring生态系统的API网关,其中包括:Spring5,SpringBoot2和项目Reactor。SpringCloud网关的目的是提供一种简单而有效的方法来路由到API,并向它们提供跨领域的关注,例如:安全性,监视/度量和弹性。  如......
  • Vue组件化编程
    Vue组件化编程组件的定义:用来实现局部(特定)功能效果的代码集合(html/css/js/image)为什么使用组件:一个界面的功能很复杂作用:复用代码,简化项目编码,提高运行效率组件分为非单文件组件和单文件组件,在开发中一般使用单文件组件非单文件组件1.组件的基本用法组件的使用步骤有三步:定......
  • Vue Ant Design中a-tree组件支持点击父节点名称(title\label)所有子节点选中
    核心代码<a-treeref="treeRef"class="draggable-tree"v-if="treeData.length":tree-data="treeData"......
  • 编译—配置化TOML与编译组件
    硬件功能模块化,软件功能配置化(业务化)软件功能配置化软件系统模块化设计是实现可配置性的基础。通过将系统拆分为多个独立的模块,可以使得每个模块都拥有独立的配置选项引入配置文件,提供可视化配置界面,实现动态参数调整-运行时对部分参数进行调整-热插拔配置文件ini......
  • 界面组件DevExpress WPF v24.1 - 增强的可访问性 & UI自动化
    DevExpressWPF拥有120+个控件和库,将帮助您交付满足甚至超出企业需求的高性能业务应用程序。通过DevExpressWPF能创建有着强大互动功能的XAML基础应用程序,这些应用程序专注于当代客户的需求和构建未来新一代支持触摸的解决方案。DevExpressWPF控件日前正式发布了今年一个重大版......
  • 流量控制组件选型之 Sentinel vs Hystrix
    Sentinel:Sentinel是阿里中间件团队研发的面向分布式服务架构的轻量级高可用流量控制组件,于2018年7月正式开源。Sentinel主要以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度来帮助用户提升服务的稳定性。大家可能会问:Sentinel和之前经常用到的熔断降级库Ne......