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
    

3. 消息订阅与发布

3.1 简单例子-1

  • 小需求设计:
    在这里插入图片描述
  • 代码设计实现
    • ChildA——发布消息
      核心代码就2行:
      import PubSub from 'pubsub-js'
      
      //PubSub.publish('MY TOPIC', 'hello world!');
      PubSub.publish('GamesNews','通知:在我校(ChildA)举办运动会的时间定于10月16日');
      
      在这里插入图片描述
    • 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组件中 "组件挂载完毕的钩子" 
      
      在这里插入图片描述
  • 效果展示
    在这里插入图片描述

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

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

4. 取消订阅

4.1 取消单个topic

  • 语法:

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

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

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

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

4.2 取消多个或更多语法

4.3 卸载组件时取消订阅

4.3.1 卸载所有组件

  • 首先,给出卸载所有组件的代码,如下:
    在这里插入图片描述
    核心代码就这一丢丢,不说了,自己看
    import root from '../index';
    
        //卸载组件---卸载所有
        const unMountAll =()=>{
          
          
            //卸载组件 root
            root.unmount();
        }
    
    
     <button onClick={
          
          unMountAll}>卸载所有组件</button>
    
  • 然后,再看效果:
    在这里插入图片描述
  • 最后,简单说明一下:
    • 卸载组件,在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>
      
                  {
              
              /* <div className="childB">
                      <ChildB 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;
      
    • 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;
      
    • 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;
      
    • 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;
      }
      

猜你喜欢

转载自blog.csdn.net/suixinfeixiangfei/article/details/132976539