• React中组件通信02——消息订阅与发布、取消订阅以及卸载组件时取消订阅


    1. 前言

    1.1 使用props通信

    • 上一篇介绍了使用props进行组件之间的通信,但是对于子传父和兄弟之间的通信使用props不是最好的选择,所以介绍一下消息订阅与发布。
    • 关于props,可以看下面的文章,如下:
      React中组件通信01——props.

    1.2 关于useEffect

    • 下面写订阅消息的时候会用到,所以这里简单介绍一下:
    • useEffect 的使用,可以相当于class组件中的生命周期,可以代替组件挂载完毕(componentDidMount)组件更新完毕(componentDidUpdate)组件将要卸载这三个钩子(componentWillUnmount)
      • 1⃣️useEffect 的第二个参数是空数组的情况,第一个参数里的函数就相当于是class组件中的组件挂载完毕钩子(componentDidMount)
        这时可以做定时器、订阅消息等。
      • 2⃣️useEffect 的第二个参数如果不传 或者 是非空数组,此时第一个参数相当于组件更新完毕的钩子(componentDidUpdate)
        • 不传:监测组件中state里的每个属性,只要有更新就会调用;
        • 非空数组:可以指定监测state里的某一个或某几个属性,只有监测的属性有更新才会调用。
      • 3⃣️useEffect 第一个参数可以没有返回值,但是如果有返回函数,则这个返回函数相当于class组件中的组件将要卸载的钩子(componentWillUnmount)
        一般用于取消定时器、取消订阅等。

    2. 安装 pubsub-js

    • 命令如下:
      npm install pubsub-js
      
      • 1

    3. 消息订阅与发布

    3.1 简单例子-1

    • 小需求设计:
      在这里插入图片描述
    • 代码设计实现
      • ChildA——发布消息
        核心代码就2行:
        import PubSub from 'pubsub-js'
        
        //PubSub.publish('MY TOPIC', 'hello world!');
        PubSub.publish('GamesNews','通知:在我校(ChildA)举办运动会的时间定于10月16日');
        
        • 1
        • 2
        • 3
        • 4
        在这里插入图片描述
      • ChildB——订阅消息(借助于 useEffect 钩子),核心代码如下:
        import PubSub from 'pubsub-js'
        
            //订阅运动会消息
        const subscriberGamesNew = function(msg, data){
            console.log('ChildB 订阅的消息--subscriberGamesNew---');
            console.log( msg, data );//msg-订阅的topic  data-消息内容
        }
        
        useEffect(()=>{
            //subscribe-订阅的方法   'GamesNews'-订阅的主题
            let token = PubSub.subscribe('GamesNews', subscriberGamesNew);
            console.log('token',token);
        },[])//这里第二个参数是空数组 [],这种情况相当于class组件中 "组件挂载完毕的钩子" 
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        在这里插入图片描述
    • 效果展示
      在这里插入图片描述

    3.2 简单例子-2(完善、优化)——订阅消息+使用消息

    • 小需求如下:
      在这里插入图片描述
    • 代码设计如下:
      • 发布方
        在这里插入图片描述
      • 订阅方
        在这里插入图片描述
        在这里插入图片描述
        在这里插入图片描述
    • 效果如下:
      在这里插入图片描述

    4. 取消订阅

    4.1 取消单个topic

    • 语法:

      const token1 = PubSub.subscribe('GamesNews', mySubscribers);//订阅 GamesNews
      
      //取消订阅
      PubSub.unsubscribe(token1);
      或
      PubSub.unsubscribe('GamesNews');
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    • 例子如下:
      在这里插入图片描述

    • 效果如下:
      在这里插入图片描述

    • 上面函数可以优化一下,根据主题取消,如下:

       const myUnsubscribe =(topic)=>{
           PubSub.unsubscribe(topic); //取消订阅的GamesNews
           console.log('取消订阅运动会消息---成功!!!');
       }
      
      <button onClick={()=>myUnsubscribe('GamesNews')}>取消订阅运动会消息</button>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

    4.2 取消多个或更多语法

    4.3 卸载组件时取消订阅

    4.3.1 卸载所有组件

    • 首先,给出卸载所有组件的代码,如下:
      在这里插入图片描述
      核心代码就这一丢丢,不说了,自己看
      import root from '../index';
      
          //卸载组件---卸载所有
          const unMountAll =()=>{
              //卸载组件 root
              root.unmount();
          }
      
      
       <button onClick={unMountAll}>卸载所有组件</button>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
    • 然后,再看效果:
      在这里插入图片描述
    • 最后,简单说明一下:
      • 卸载组件,在class组件的声明周期中,其实是调用了组件将要卸载的钩子,函数组件中可以在useEffect中体现,详细看上面1.2的介绍《1.2 关于useEffect》。
      • 其实在上面卸载时,如果有代码有开启定时器而又没有取消定时器的话,是有问题的,其实也可以这么说,如果上面只卸载了B组件,但是B组件有有订阅消息,那么如果卸载了B组件,但是订阅没有取消的话,是不合理的,这也可以理解是前端优化的部分。
      • 光说看不出效果,下面我门就介绍卸载指定组件B来观察观察。

    4.3.2 卸载指定组件——取消订阅

    • 1⃣️ 在父组件中,控制B组件展示(即:父组件中,设计一个卸载/渲染B组件的按钮)
      在这里插入图片描述
    • 2⃣️ 在B组件 useEffect 中第一参数的返回函数中,有所体现,看卸载B组件时,是否执行返回函数
      在这里插入图片描述
    • 3⃣️ 看效果
      在这里插入图片描述
    • 优化,看效果:
      所以,需要在卸载组件时,把订阅取消了,取消订阅的代码所放位置,如下:
      在这里插入图片描述
      在这里插入图片描述

    5. 附核心完整代码

    • 代码结构:
      在这里插入图片描述
    • 核心代码
      • App.js + index.js
        在这里插入图片描述
      • Parent.jsx
        import React from "react";
        import ChildA from "./ChildA";
        import ChildB from "./ChildB";
        import './index.css'
        import root from '../index';
        
        function Parent() {
        
            const [mountChildBFlag,setMountChildFlag] = React.useState(true);
        
            //卸载组件---卸载所有
            const unMountAll =()=>{
                //卸载组件 root
                root.unmount();
            }
            return(
                <div className="parent">
                    我是父组件!
        
                    <div className="childA">
                        <ChildA notice={'通知——今天放假!'}/>
                    </div>
        
                    {/* 
        */
        } {/* 这里根据 mountChildBFlag 控制B组件的状态 */} { mountChildBFlag ? <div className="childB"> <ChildB notice={'通知——今天放假!'} /> </div> : "" } <br /><br /> <button onClick={()=>setMountChildFlag(!mountChildBFlag)}>卸载B组件/渲染B组件</button> <br /><br /> <button onClick={unMountAll}>卸载所有组件</button> </div> ) } export default Parent;
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20
        • 21
        • 22
        • 23
        • 24
        • 25
        • 26
        • 27
        • 28
        • 29
        • 30
        • 31
        • 32
        • 33
        • 34
        • 35
        • 36
        • 37
        • 38
        • 39
        • 40
        • 41
        • 42
        • 43
        • 44
        • 45
        • 46
      • ChildA.jsx
        import React from "react";
        import PubSub from 'pubsub-js'
        
        function ChildA(props){
        
            const stuNameRef = React.useRef();
        
            //发布运动会消息 按钮触发
            function publishGamesNews(){
                // 发布运动会消息  topic-->GamesNews
                PubSub.publish('GamesNews','通知:在我校(ChildA)举办运动会的时间定于10月16日');
                console.log('-----ChildA 发布了 GamesNews 消息----');
            }
            // 发布学生消息  开除的学生
            function expelStuSubmit(event){
                event.preventDefault();//非受控组件  只取表单数据,但阻止表单提交,实现页面无刷新
        
                const stuName = stuNameRef.current.value;
                PubSub.publish('stusInfo',{stuName:stuName,schoolName:'ChildA-School',stuState:'被开除'});
            }
        
            return(
                <div>
                    我是子组件A!!!
                    <br /><br />
                    收到来自于父组件的数据:{props.notice}
        
                    <br /><br />
                    <button onClick={publishGamesNews}>发布运动会消息</button>
        
                    <br /><br />
                    <form onSubmit={expelStuSubmit}>
                        学生姓名:<input type="text" ref={stuNameRef} name="stuName"/>
                        <button>开除学生</button>
                    </form>
                </div>
            )
        }
        
        export default ChildA;
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20
        • 21
        • 22
        • 23
        • 24
        • 25
        • 26
        • 27
        • 28
        • 29
        • 30
        • 31
        • 32
        • 33
        • 34
        • 35
        • 36
        • 37
        • 38
        • 39
        • 40
      • ChildB.jsx
        import PubSub from 'pubsub-js'
        import { useEffect,useState } from 'react';
        
        function ChildB(props){
        
            const [gamesNews,setGamesNews] = useState('等通知……');
            const [stusInfo,setStuInfo] = useState(
                [
                    {stuName:'学生1',schoolName:'ChildA-School',stuState:'在校'},
                    {stuName:'学生2',schoolName:'ABC学校',stuState:'离校'},
                    {stuName:'张三',schoolName:'ChildA-School',stuState:'在校'},
                    {stuName:'李四',schoolName:'XXX附属中学',stuState:'托管'},
                    {stuName:'王五',schoolName:'ChildA-School',stuState:'在校'},
                ]
            );
        
            //我的订阅方法
            const mySubscribers = function(msg, data){
                console.log('ChildB 订阅的消息--mySubscribers---');
                // console.log( msg, data );//msg-订阅的topic  data-消息内容
                //将订阅到的新消息进行更新
                if('GamesNews'===msg){
                    console.log('订阅到运动会的消息是:',data);
                    setGamesNews(data);
                }else if('stusInfo'===msg){
                    console.log('订阅到开除的学生是:',data);
                    // const newStuInfo = [...stusInfo,data];//这个不去重,追加数据
                    //这个地方需要注意stusInfo 和 data的类型
                    const newStuInfo = stusInfo.map((stu)=>{
                        return data.stuName===stu.stuName ? data : stu;
                    });
                    setStuInfo(newStuInfo);
                }
            }
        
            useEffect(()=>{
                //subscribe-订阅的方法  
                const token1 = PubSub.subscribe('GamesNews', mySubscribers);//订阅 GamesNews
                const token2 = PubSub.subscribe('stusInfo', mySubscribers);// 订阅 stusInfo
                console.log('token1---',token1);
                console.log('token2---',token2);
        
                return ()=>{
                    //这个返回函数,相当于class中的“组件将要卸载的钩子”  在这里可以取消订阅
                    console.log('ChildB组件...被卸载了');
        
                    PubSub.unsubscribe(token1); //取消订阅
                    
                }
            },[])//这里第二个参数是空数组 [],这种情况相当于class组件中 "组件挂载完毕的钩子" 
        
            //取消订阅
            const myUnsubscribe =(topic)=>{
                PubSub.unsubscribe(topic); //取消订阅的GamesNews
                console.log('取消订阅运动会消息---成功!!!');
            }
        
            return(
                <div>
                    我是子组件B!!!
                    <br /><br />
                    收到来自于父组件的数据:{props.notice}
        
                    <br /><br /><br />
                    订阅1——运动会消息:{gamesNews}
                    <br />
                    <button onClick={()=>myUnsubscribe('GamesNews')}>取消订阅运动会消息</button>
        
                    <br /><br /><br />
                    订阅2——学生消息:
                    <table>
                        <thead>
                            <tr>
                                <th>学生姓名</th>
                                <th>所在学校</th>
                                <th>状态</th>
                            </tr>
                        </thead>
                        <tbody>
                            {stusInfo.map((stu,index)=>{
                                return <tr key={index}>
                                    <td>{stu.stuName}</td>
                                    <td>{stu.schoolName}</td>
                                    <td>{stu.stuState}</td>
                                </tr>
                            })}
                        </tbody>
                    </table>
        
        
                </div>
            )
        }
        
        export default ChildB;
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20
        • 21
        • 22
        • 23
        • 24
        • 25
        • 26
        • 27
        • 28
        • 29
        • 30
        • 31
        • 32
        • 33
        • 34
        • 35
        • 36
        • 37
        • 38
        • 39
        • 40
        • 41
        • 42
        • 43
        • 44
        • 45
        • 46
        • 47
        • 48
        • 49
        • 50
        • 51
        • 52
        • 53
        • 54
        • 55
        • 56
        • 57
        • 58
        • 59
        • 60
        • 61
        • 62
        • 63
        • 64
        • 65
        • 66
        • 67
        • 68
        • 69
        • 70
        • 71
        • 72
        • 73
        • 74
        • 75
        • 76
        • 77
        • 78
        • 79
        • 80
        • 81
        • 82
        • 83
        • 84
        • 85
        • 86
        • 87
        • 88
        • 89
        • 90
        • 91
        • 92
        • 93
        • 94
        • 95
      • component–>index.css
        .parent{
            background-color: blueviolet;
            border: 1px solid;
            height: 900px;
            width: 600px;
            text-align: center;
        }
        
        .childA{
            background-color: green;
            height: 170px;
            margin: 20px;
        }
        
        .childB{
            background-color: grey;
            height: 400px;
            margin: 20px;
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
  • 相关阅读:
    多线程与同步
    基于web在线餐饮网站的设计与实现——仿Coco线上订奶茶饮料6个页面(HTML+CSS+JavaScript)
    洛谷 模板汇总 算法基础 python解析
    day02_python快速入门
    Sam Altman的成功学|升维指南
    【java-Date】
    111. 二叉树的最小深度
    springboot启动的shell脚本与参数
    数字孪生技术在智能家居中的应用
    Python爬虫从入门到进阶
  • 原文地址:https://blog.csdn.net/suixinfeixiangfei/article/details/132976539