Web Components详解-组件通信

这篇具有很好参考价值的文章主要介绍了Web Components详解-组件通信。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

我们常说到程序的运行和代码的实现遵循高内聚和低耦合,理解一下这句话,模块中的功能在逻辑上是有关联的,模块之间依赖关系较弱。前端的组件同样遵循这套原则,单个组件的功能逻辑是完整的,组件与组件之间也没有强关联,那么如何保证组件之间的联系呢?在Vue和React中一般使用props响应式通信、bus事件总线、Pinia,Vuex,Mobx全局状态等等方式进行数据传递,类似的本篇文章也将介绍Web组件的通信方式

插槽(Slots)

插槽的使用在之前的文章介绍过,通过自定义标签中其他标签的slot属性与影子DOM的slot标签绑定达到传递组件的效果,本文就不做介绍

属性(Attributes)

在介绍创建自定义标签时,我们曾经接触过属性监听器函数attributeChangedCallback,通过在静态方法observedAttributes中声明属性的列表,来监听某个或者某些属性的变化,在标签使用setAttribute函数时会触发属性监听回调。借助这个特点,我们结合代理(proxy)或者存取器(set,get)将element.attrs = value的操作也代理到标签中,思考下面的代码

// 组件工厂
const createCustomElement = (config) => {
  const {
    name,
    attrs = [],
    mode = "open",
    temp,
    parent = document.body,
    BaseClass = HTMLElement,
  } = config;
  let elem = document.createElement(name);
  customElements.define(
    name,
    // @ts-ignore
    class extends BaseClass {
      constructor() {
        super();
        this.initProxy();
        this.attachShadow({ mode });
        this.shadowRoot?.appendChild(temp.content);
      }
      initProxy() {
        // 监听设置标签属性操作
        elem = new Proxy(elem, {
          set: (target, property, value) => {
            target.setAttribute(property, value);
            return Reflect.set(target, property, value);
          },
          get: (target, property) => {
            const obj = Reflect.get(target, property);
            // 事件函数优化,取this指向问题
            if (typeof obj === "function") return obj.bind(this);
            return obj;
          },
        });
      }
      attributeChangedCallback(attrName, oldValue, newValue) {
        console.log(`${attrName}属性的旧值:${oldValue},新值:${newValue}`);
      }
      static observedAttributes = attrs;
    }
  );
  parent.appendChild(elem);
  return elem;
};

我们实现一个组件的批量创建的函数,无需每次都重新使用自定义标签创建组件,只需要调用createCustomElement函数就可以创建一个组件。创建组件操作如下

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Attributes</title>
</head>

<body>
    <template id="temp1">
        <div>
            123
        </div>
    </template>
    <template id="temp2">
        <div>
            456
        </div>
    </template>
    <script src="./helpers.js"></script>
    <script>
        const attrs = ["attrs"]
        const elemName1 = "custom-element1"
        const elemName2 = "custom-element2"
        const elem1 = createCustomElement({
            name: elemName1,
            attrs,
            temp: temp1
        })
        const elem2 = createCustomElement({
            name: elemName2,
            attrs,
            temp: temp2
        })
        elem1.attrs = 111

    </script>
</body>

</html>

当我们使用elem1.attrs = 111给elem1的属性赋值时,会调用自定义标签中的attributeChangedCallback函数,达到组件的信息传递的效果

事件(Events)

讲完了基础的属性传递,我们来试试通过标签自定义事件(CustomEvent)的方式给组件添加信息传递的途径。

自定义事件有三个知识点,分别是CustomEvent类,dispatchEvent函数以及addEventListener函数,后两者不难理解,它们是element上的方法,用于触发和监听标签的事件。而CustomEvent类的作用则是承载数据的传递及事件的配置。

CustomEvent构造函数接收两个参数,第一个是string类型的type,表示事件类型;第二个是eventInitDict,表示事件配置,其中包含bubbles(允许冒泡),cancelable(允许取消,通过event.preventDefault()可以取消该方法调用),composed(允许穿越Shadow DOM的边界),detail(自定义事件传递的额外数据)。

其中composed在实现自定义组件时尤为重要,将其设置为true时才能在外界和影子DOM中传递数据,参考下面的一段关于自定义事件的代码

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Events</title>
</head>

<body>
    <template id="temp1">
        <button id="btn1">
            组件1
        </button>
        <script type="module">
            const root = elem1.shadowRoot
            root.querySelector("#btn1").addEventListener('click', () => {
                // 在按钮点击时触发自定义事件
                root.dispatchEvent(new CustomEvent('customClick', {
                    bubbles: true, // 允许事件冒泡
                    composed: true, // 允许事件穿越Shadow DOM边界
                    detail: { message: '点击' } // 传递的数据
                }));
            });
        </script>
    </template>
    <template id="temp2">
        <button>
            组件2
        </button>
    </template>
    <script src="./helpers.js"></script>
    <script>
        const elemName1 = "custom-element1"
        const elemName2 = "custom-element2"
        const elem1 = createCustomElement({
            name: elemName1,
            temp: temp1
        })
        const elem2 = createCustomElement({
            name: elemName2,
            temp: temp2,
            BaseClass: class extends HTMLElement {
                connectedCallback() {
                    this.addEventListener('customClick:elem1', (e) => {
                        // 监听、接收来自elem1的customClick:elem1事件消息
                        console.log("收到elem1的消息:", e.detail);
                    });
                }
            }
        })
        elem1.addEventListener('customClick', (e) => {
            // 监听、接收来自elem1的customClick事件消息,并发送customClick:elem1消息给elem2
            elem2.shadowRoot.dispatchEvent(new CustomEvent('customClick:elem1', e));
        });
    </script>
</body>

</html>

当点击组件1时,控制台会打印相关信息,达到组件通信的效果

消息中心(MessageCenter)

消息中心类似vue的$emit,它是一种发布订阅的写法,在异步,解耦的实践中发挥重要的作用,在早期的文章中,我曾经介绍过这种设计模式的实现:消息中心,发布者/订阅者模式。本文就不做详细的介绍,有兴趣的朋友可以看看链接文章,下面给出消息中心通信方式的使用示例:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Events</title>
</head>

<body>
    <template id="temp1">
        <button id="btn1">
            组件1
        </button>
        <script type="module">
            const root1 = elem1.shadowRoot
            bus.on(`${elemName2}:msg`, (e) => {
                console.log(e);
            })
            root1.querySelector("#btn1").addEventListener('click', () => {
                bus.emit(`${elemName1}:msg`, { msg: "hello this is elem1" })
            });
        </script>
    </template>
    <template id="temp2">
        <button id="btn2">
            组件2
        </button>
        <script type="module">
            const root2 = elem2.shadowRoot
            bus.on(`${elemName1}:msg`, (e) => {
                console.log(e);
            })
            root2.querySelector("#btn2").addEventListener('click', () => {
                bus.emit(`${elemName2}:msg`, { msg: "hello this is elem2" })
            });
        </script>
    </template>
    <script src="./helpers.js"></script>
    <script src="./node_modules/event-message-center/dist/umd/index.js"></script>
    <script>
        const elemName1 = "custom-element1"
        const elemName2 = "custom-element2"
        const bus = MessageCenter.messageCenter
        const elem1 = createCustomElement({
            name: elemName1,
            temp: temp1
        })
        const elem2 = createCustomElement({
            name: elemName2,
            temp: temp2
        })

    </script>
</body>

</html>

上述代码中,当我们点击组件1时,控制台打印hello this is elem1;点击组件2时提示hello this is elem2,由此达到组件的消息通信

全局状态(GloalState)

我要介绍的全局状态可能与框架中常用的全局状态管理工具不太一样,这里我使用代理实现了一个简单的状态管理类,以供组件可以访问

// 简单的响应式全局状态
class GlobalState {
  constructor(state, action) {
    this.state = this.initState(state);
    this.action = action;
  }
  initState(state) {
    return new Proxy(state, {
      set: (target, key, val) => {
        Reflect.set(target, key, val);
        this.action(target);
        return true;
      },
    });
  }
}

在组件中,通过修改state的状态触发钩子函数,达到响应的效果

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>GloalState</title>
</head>

<body>
    <template id="temp1">
        <button id="btn1">
            组件1
        </button>
        <script type="module">
            const root1 = elem1.shadowRoot
            root1.querySelector("#btn1").addEventListener('click', () => {
                state.name = "elem1"// 组件1中点击,name转换成elem1
            });
        </script>
    </template>
    <template id="temp2">
        <button id="btn2">
            组件2
        </button>
        <script type="module">
            const root2 = elem2.shadowRoot
            root2.querySelector("#btn2").addEventListener('click', () => {
                state.name = "elem2"// 组件2中点击,name转换成elem2
            });
        </script>
    </template>
    <script src="./helpers.js"></script>
    <script src="./node_modules/event-message-center/dist/umd/index.js"></script>
    <script>
        const elemName1 = "custom-element1"
        const elemName2 = "custom-element2"
        const { state } = new GlobalState({
            name: "elem"
        }, (state) => {
            console.log(state);
        })
        console.log(state);// 初始化name是elem
        const elem1 = createCustomElement({
            name: elemName1,
            temp: temp1
        })
        const elem2 = createCustomElement({
            name: elemName2,
            temp: temp2
        })

    </script>
</body>

</html>

总结

在前端开发中,遵循高内聚低耦合的原则,确保组件之间的功能逻辑关联性较强,同时组件之间的耦合性较低,是设计和构建可维护性高的应用的重要指导原则。

Web 组件作为可重用的组件化技术,也需要有效的通信机制来实现组件之间的数据传递和交互。文章中的每种通信方式都有其适用的场景,根据项目需求和设计原则来选择合适的通信方式是非常重要的。

以上就是文章全部内容了,如果觉得文章不错的话,还望三连支持一下,感谢!

相关代码

myCode: 基于js的一些小案例或者项目 - Gitee.com

MessageCenter: 基于发布订阅模式实现的一个事件消息中心

参考文章

Shadow DOM 和事件(events)文章来源地址https://www.toymoban.com/news/detail-733808.html

到了这里,关于Web Components详解-组件通信的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Web Components详解-Shadow DOM基础

    目录 引言 概念 基本用法 attachShadow函数 mode(模式) delegatesFocus(委托聚焦) Custom Elements+Shadow DOM 基本用法 样式及属性隔离 写在最后 相关代码 参考文章 上篇文章的自定义标签中,我们使用customElements对象对原生标签进行拓展,达到组件的拓展性与复用性的效果,那么如何

    2024年02月07日
    浏览(33)
  • Web Components详解-Shadow DOM样式控制

    本文继续Web Components系列文章,介绍一下Shadow DOM的样式及选择器。 Shadow DOM的样式与外界是隔离的,即自定义元素的样式只会影响到Shadow DOM内部,不会影响到外部的页面元素,这点在之前有说到过。那么有什么办法可以在Shadow DOM中使用全局样式?样式选择器又有什么异同呢?

    2024年02月08日
    浏览(34)
  • 面试题-React(七):React组件通信

    在React开发中,组件通信是一个核心概念,它使得不同组件能够协同工作,实现更复杂的交互和数据传递。常见的组件通信方式:父传子和子传父 一、父传子通信方式 父组件向子组件传递数据是React中最常见的一种通信方式。这种方式适用于将数据从一个上层组件传递到其直

    2024年02月11日
    浏览(45)
  • web前端Javascript—7道关于前端的面试题

    本文主要是web前端Javascript—的面试题,附上相关问题以及解决答案,希望对大家web前端Javascript闭包的学习有所帮助。 每个JavaScript 程序员都必须知道闭包是什么。在 JavaScript 面试中,你很可能会被问到的问题 以下是 7 个有关 JavaScript的面试题,比较有挑战性。不要查看答案

    2024年02月03日
    浏览(101)
  • Web前端 ---- 【Vue】(组件)父子组件之间的通信一文带你了解

    目录 前言 父组件传子组件 ---- props 给要传递数据的子组件绑定要传过去的属性及属性值 在子组件中使用props配置项接收 props配置项 子组件传父组件 ---- 组件的自定义事件 子组件向父组件传递数据 通过代码来绑定自定义事件 本文将介绍在Vue中父子组件如何进行通信 这里先介

    2024年02月05日
    浏览(117)
  • React: 组件介绍 Components

    A  Component  is one of the core building blocks of React. In other words, we can say that every application you will develop in React will be made up of pieces called components. Components make the task of building UIs much easier. “Components let you split the UI into independent, reusable pieces, and think about each piece in isolation.”   Here we

    2024年02月10日
    浏览(40)
  • 【面试题】详解JavaScript中的Map()

     前端面试题库 ( 面试必备)              推荐:★★★★★ 地址:前端面试题库 JavaScript是一种动态、解释性的编程语言,用于开发web上的动态页面和交互式应用程序。与其他编程语言相比,JavaScript拥有更加灵活的内置数据类型,并且拥有更高级别的调试和错误处理工

    2024年02月11日
    浏览(45)
  • Kendo UI,一个加速Web应用界面开发的JavaScript组件库!

    Kendo UI是什么? 首先,Kendo UI是一个由四个JavaScript UI库组成的包,这些库是专为jQuery、Angular、React和Vue原生构建的,每一个都是用一致的API和主题构建的。所以无论开发者怎么选择,所开发的Web应用始终保持了现代的应用界面,响应迅速、可访问且速度快! 为什么要选择Ke

    2024年02月15日
    浏览(57)
  • vue给components动态添加组件

    在Vue中,可以使用 v-bind 指令来动态地将组件添加到其他组件上。 首先,需要定义一个包含所有可能的子组件的数组或对象。然后,通过计算属性(computed property)根据条件选择要显示的组件。最后,使用 component 元素并结合 is 特性来动态地切换不同的组件。 这样就可以根据

    2024年01月24日
    浏览(42)
  • VUE 父子组件、兄弟组件 之间通信 最强详解

    目录 1. 父组件 调用 子组件 内参数/方法 1.1 通过 ref 调用/获取 子组件内参数/方法 2. 子组件 调用 父组件 内参数/方法 2.1 通过 emit 调用 父组件方法 2.2 通过 props 调用 父组件方法/参数 2.3 通过 this.$parent 调用 父组件方法/参数 3. 兄弟组件 通信 3.1 通过 bus 进行 兄弟组件 通信

    2024年02月05日
    浏览(61)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包