发布订阅模式

113 阅读3分钟

在日常开发中,应该会经常遇到组件间通信的情况。而组件间的关系主要有:父子关系、祖孙关系、兄弟关系。

下面分情况讨论来引出pubsub-js库,进而引出发布订阅模式。

通信的几种情况

父给子传值

可以通过props进行传值,jsx代码如下

// 父子通信  父 --> 子
import { useState } from "react";
import "./index.css";
​
const Child = ({ count }) => {
  // const {count} = props
  return <div className="child">子 传值内容为{count}</div>;
};
​
const Test1 = () => {
  const [count, setCount] = useState(0);
  const handleClick = () => {
    setCount(count + 1);
  };
  return (
    <div className="test1">
      <h1>父给子传值</h1>
      父
      <br />
      <button onClick={handleClick}>修改count值</button>
      <Child count={count} />
    </div>
  );
};
​
export default Test1;

效果为:

父给子.png

父给子2.png

子给父传值

父先给子预留一个函数,子调用函数进行传值

// 父子通信  子 --> 父
import { useState } from "react";
import "./index.css";
​
const Child = ({ handleClick }) => {
  let value = 0;
  return (
    <div className="child">
      子<button onClick={handleClick(value)}>点击将子的值传给父</button>
    </div>
  );
};
​
const Test2 = () => {
  const [count, setCount] = useState(null);
  const handleClick = (childCount) => {
    return () => {
      setCount(childCount);
    };
  };
  return (
    <div className="test2">
      父
      <Child handleClick={handleClick} />
      子给父传的值为:{count}
    </div>
  );
};
​
export default Test2;

效果为:

子给父.png

子给父2.png

兄弟组件传值(通过父元素)

通过父组件作为中介,父组件保存状态和修改状态的方法

// 兄弟间通信
import { useState } from "react";
import "./index.css";
​
const Child1 = ({count}) => {
  return <div className="child1">
    child1
    <br />
    child2传递过来的信息为:{count}
    </div>;
};
const Child2 = ({handleClick}) => {
  return <div className="child2">
    child2
    <br />
    <button onClick={handleClick(1)}>点击把1传给child1</button>
    </div>;
};
​
const Test3 = () => {
  const [count, setCount] = useState(0);
  const handleClick = (props) => {
    return () => {
      setCount(props);
    };
  };
​
  return (
    <div className="test3">
      父
      <br />
      兄弟间通信(通过父亲作为中介)
      <br />
      <Child1 count={count} />
      <Child2 handleClick={handleClick} />
    </div>
  );
};
​
export default Test3;

效果为:

兄弟传值1.png

兄弟传值2.png

虽然效果实现了,但是一旦有多个兄弟组件都需要通信,则父组件会存放所有的状态和修改状态的方法,非常混乱和庞杂。

并且如果是孙组件和孙组件进行通信,则每一个父组件一直到祖组件都需要保存状态。

以上两种情况都不符合组件编写原则,即每一个组件内只保存和自己组件相关的状态和方法。

兄弟组件传值(通过pubsub-js

pubsub-js:依托于发布订阅模式实现的组件间通信库

// 兄弟间通信
import { useState } from "react";
import "./index.css";
import PubSub from "pubsub-js";
​
const PUBSUB_ID = "setCount";
​
const Child1 = () => {
  const [count, setCount] = useState(0);
  PubSub.subscribe(PUBSUB_ID, (id, value) => {
    setCount(value);
  });
  return (
    <div className="child1">
      child1
      <br />
      child2传递过来的信息为:{count}
    </div>
  );
};
const Child2 = () => {
  const handleClick = () => {
    PubSub.publish(PUBSUB_ID, 1);
  };
  return (
    <div className="child2">
      child2
      <br />
      <button onClick={handleClick}>点击把1传给child1</button>
    </div>
  );
};
​
const Test4 = () => {
  return (
    <div className="test4">
      父
      <br />
      兄弟间通信(通过pubsub)
      <br />
      <Child1 />
      <Child2 />
    </div>
  );
};
​
export default Test4;

pubsub-js

第一步:安装:npm i pubsub-js

第二步:在需要订阅或发布数据的组件中导入import PubSub from 'pubsub-js'

第三步:使用

// 订阅消息
// PUBSUB_ID: 唯一标识符
// id: 标识符
// value:拿到的状态
PubSub.subscribe(PUBSUB_ID, (id, value) => { })
​
// 发布消息
// PUBSUB_ID: 唯一标识符
// value:想要发布的状态
PubSub.publish(PUBSUB_ID, value)
​
// 清除某个订阅:
// PUBSUB_ID: 唯一标识符
// MyFunc: 想要清除的某一个函数
PubSub.unSubscribe(PUBSUB_ID, MyFunc)
​
// 清除所有订阅
PubSub.clearAllSubscriptions()
​
// 查看订阅的函数数量
PubSub.countSubscriptions(PUBSUB_ID);

源码的简单实现

function Pubsub() {
  all = new Map();
​
  return {
    subscribe(id, func) {
      const handlersArr = all.get(id);
      handlersArr ? handlersArr.push(func) : all.set(id, [func]);
    },
​
    publish(id, val) {
      const handlersArr = all.get(id);
      if (handlersArr) {
        handlersArr.slice().forEach((handler) => {
          handler(id, val);
        });
      }
    },
​
    unsubscribe(id, func) {
      const handlersArr = all.get(id);
      if (handlersArr) {
        if (func) {
          // 要把所有函数都删除
          let index = handlersArr.indexOf(func);
          while (index >= 0) {
            handlersArr.splice(index, 1);
            index = handlersArr.indexOf(func);
          }
        } else {
          all.set(id, []);
        }
      }
    },
  };
}
​
const MyPubsub = Pubsub();

测试代码:

const PUBSUB_ID_ONE = "pubsub_one";
const PUBSUB_ID_TWO = "pubsub_two";
const PUBSUB_ID_THREE = "pubsub_three";
​
const funcOne = (id, val) => {
  console.log(`funcOne, id是: ${id}, 传过来的值是: ${val}`);
};
const funcTwo = (id, val) => {
  console.log(`funcTwo, id是: ${id}, 传过来的值是: ${val}`);
};
const funcThree = (id, val) => {
  console.log(`funcThree, id是: ${id}, 传过来的值是: ${val}`);
};
​
MyPubsub.subscribe(PUBSUB_ID_ONE, funcOne);
MyPubsub.subscribe(PUBSUB_ID_ONE, funcOne);
MyPubsub.subscribe(PUBSUB_ID_ONE, funcTwo);
MyPubsub.subscribe(PUBSUB_ID_ONE, funcThree);
MyPubsub.subscribe(PUBSUB_ID_TWO, funcTwo);
MyPubsub.subscribe(PUBSUB_ID_TWO, funcThree);
MyPubsub.subscribe(PUBSUB_ID_THREE, funcOne);
MyPubsub.subscribe(PUBSUB_ID_THREE, funcThree);
​
MyPubsub.publish(PUBSUB_ID_ONE, "1");
MyPubsub.publish(PUBSUB_ID_TWO, "2");
MyPubsub.publish(PUBSUB_ID_THREE, "3");

输出为:

funcOne, id是: pubsub_one, 传过来的值是: 1
funcOne, id是: pubsub_one, 传过来的值是: 1
funcTwo, id是: pubsub_one, 传过来的值是: 1
funcThree, id是: pubsub_one, 传过来的值是: 1
funcTwo, id是: pubsub_two, 传过来的值是: 2
funcThree, id是: pubsub_two, 传过来的值是: 2
funcOne, id是: pubsub_three, 传过来的值是: 3
funcThree, id是: pubsub_three, 传过来的值是: 3

如果移除所有的PUBSUB_ID_TWO的id:

MyPubsub.unsubscribe(PUBSUB_ID_TWO);
MyPubsub.publish(PUBSUB_ID_ONE, "1");
MyPubsub.publish(PUBSUB_ID_TWO, "2");
MyPubsub.publish(PUBSUB_ID_THREE, "3");

结果为:

funcOne, id是: pubsub_one, 传过来的值是: 1
funcOne, id是: pubsub_one, 传过来的值是: 1
funcTwo, id是: pubsub_one, 传过来的值是: 1
funcThree, id是: pubsub_one, 传过来的值是: 1
funcOne, id是: pubsub_three, 传过来的值是: 3
funcThree, id是: pubsub_three, 传过来的值是: 3