在日常开发中,应该会经常遇到组件间通信的情况。而组件间的关系主要有:父子关系、祖孙关系、兄弟关系。
下面分情况讨论来引出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;
效果为:
子给父传值
父先给子预留一个函数,子调用函数进行传值
// 父子通信 子 --> 父
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;
效果为:
兄弟组件传值(通过父元素)
通过父组件作为中介,父组件保存状态和修改状态的方法
// 兄弟间通信
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;
效果为:
虽然效果实现了,但是一旦有多个兄弟组件都需要通信,则父组件会存放所有的状态和修改状态的方法,非常混乱和庞杂。
并且如果是孙组件和孙组件进行通信,则每一个父组件一直到祖组件都需要保存状态。
以上两种情况都不符合组件编写原则,即每一个组件内只保存和自己组件相关的状态和方法。
兄弟组件传值(通过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