React 组件通信方式
文章目录
一.前言
React应用是由组件堆积的形式而成的,组件之间相互独立,但是可以相互通信的,
React组件的数据是单向数据流的
二.组件通信
1.父组件通信子组件
父组件要通信子组件是很简单的,使用Props进行属性传值即可!
export default class Parent extends React.Component {
render() {
return (
<div>
父组件
<Son name="liuqiao"></Son>
</div>
);
}
}
class Son extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
{this.props.name}
</div>
);
}
}
2.子组件通信父组件
React虽然是单向数据流的,但是不影响子组件向父组件通信.通过父组件可以向子组件传递函数这个特性,利用回调函数来实现子组件通信父组件
export default class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
name: '我是父组件'
};
}
handleClick = text => {
this.setState({
name: text
});
}
render() {
return (
<div>
{this.state.name}
<Son handleClick={this.handleClick}></Son>
</div>
);
}
}
class Son extends React.Component {
constructor(props) {
super(props);
this.state = {
msg: '我要向父组件传递数据'
};
}
render() {
return (
<div>
<button onClick={
() => {
this.props.handleClick(this.state.msg);
}
}>按钮</button>
</div>
);
}
}
上述代码中,我在Son组件中要想向父组件Parent传递数据,使用了一个从父组件传递过来的回调函数,此回调函数在子组件中触发,传递相应的参数,也就是要传递的数据.然后在父组件中接收并处理.从而达到子组件通信父组件.
PS:一般情况下,回调函数会与setState成对出现
3.context跨级组件通信(生产-消费者模式)
context的设计目的是为了共享对于一个组件树而言是
全局性
的数据.可以尽量减少逐层传递,但是不建议用context
,因为当结构复杂时,这种全局变量不易追溯到源头,不知道他从哪里传递过来的,会导致应用混乱,难以维护.context适用的场景最好是全局性的信息,且不可变.如
用户信息
,界面颜色
和主题定制
等
// 状态树跨级通信
import React from 'react';
//定义一个全局的context
const GlobalContext = React.createContext();
export default class Context extends React.Component {
constructor(props) {
super(props);
this.state = {
msg: "来自老祖宗的问候",
isCreated: false
};
}
render() {
return (
//提供一个生产者
<GlobalContext.Provider value={
{
//传递给消费者的数据
msg: this.state.msg,
//传递给消费者,一个函数,提供改变生产者内部的一个参数
onChangeShow: (text) => {
this.setState({
msg: text
});
},
}
}>
<div>
<h1>我是父组件,我提供数据给子孙吗:{this.state.msg}</h1>
<Son></Son>
</div>
</GlobalContext.Provider>
);
}
}
class Son extends React.Component {
render() {
return (
<GlobalContext.Consumer>
{
context => (
<div>
<h1>我是儿子组件,我要消费父亲的数据,{context.msg}</h1>
<GrandSon></GrandSon>
</div>
)
}
</GlobalContext.Consumer>
);
}
}
class GrandSon extends React.Component {
render() {
return (
<GlobalContext.Consumer>
{
context => (
<div>
<h1>我是孙子组件,我要消费祖宗的数据,{context.msg}</h1>
<button onClick={() => {
context.onChangeShow('我要改变我祖宗的规矩');
}
}>我要改变我祖宗的规矩</button>
</div>
)
}
</GlobalContext.Consumer>
);
}
}
context中有个两个需要理解的概念:
-
生产者
Provider
用在组件树中更外层的位置。它接受一个名为 value 的 prop,其值可以是任何 JavaScript 中的数据类型
-
消费者
consumer
可以在 Provider 组件内部的任何一层使用。它接收一个名为 children 值为一个函数的 prop。这个函数的参数是 Provider 组件接收的那个 value prop 的值,返回值是一个 React 元素(一段 JSX 代码)。
通常消费者是一个或多个子节点,所以context的设计模式是生产-消费者模式
PS:GlobalContext.Consumer
内必须是回调函数,通过context方法改变根组件状态
context优缺点:
-
优点:跨组件访问数据
-
缺点:react组件树种某个上级组件shouldComponetUpdate 返回false,当context更新时,不会引起下级组件更新
4.非父子组件通信(发布订阅模式)
发布订阅模式优点:
- 1.耦合度低,发布者和订阅者互不干扰,他们能够独立运行,不用担心开发过程中两者之间的关系
- 2.易于扩展,让系统无论什么时候都可以扩展,该订阅的订阅,该发布的发布
- 3.灵活性,只要遵守一份协议,不需要担心不同的组件是如何组合在一起的
类似于观察者模式,实现发布订阅模式需要3个重要核心
- 存储消息
- 订阅消息
- 发布消息
// 发布订阅模式实现
const Event = {
//存储消息
cacheList: {},
/**
* 订阅消息
* @param {消息类型} type
* @param {回调函数} callback
*/
subscribe(type, callback) {
//如果存在此类型的消息,就push
if (this.cacheList[type]) {
this.cacheList[type].push(callback);
}
else {
// 无此类消息,直接赋值回调函数
this.cacheList[type] = [callback];
}
},
//发布消息
dispatch(type, data) {
// 发布消息时,如果没有此类型的消息,直接退出
if (!this.cacheList[type]) {
return;
}
else {
//循环执行回调函数
var currentCache = this.cacheList[type];
currentCache.map((ele,index)=>{
currentCache[index](data);
});
}
}
};
以上代码,定义了一个发布订阅模式的核心,传递消息者,需要发布消息,接收消息者,需要订阅组件,订阅时,可以在React的生命周期函数中执行!
export default class Person extends React.Component {
constructor(props) {
super(props);
this.state = {
msg: "来自老祖宗的问候",
};
}
//组件订阅消息,此时涉及到一个生命周期,当组件加载完成时执行的钩子函数
render() {
return (
<div>
<button onClick={() => {
Event.dispatch('taggle', this.state.msg);
}}>我是祖宗组件,我要发布消息</button>
<Son></Son>
</div>
);
}
}
class Son extends React.Component {
constructor(props) {
super(props);
this.state = {
msg: "来自儿子的问候",
};
}
//当组件加载完成时,直接订阅消息
componentDidMount() {
Event.subscribe('taggle', (data) => {
this.setState({
msg: data
});
});
}
render() {
return (
<div>
<h1>我是儿子组件,{this.state.msg}</h1>
<GrandSon></GrandSon>
</div>
);
}
}
class GrandSon extends React.Component {
constructor(props) {
super(props);
this.state = {
msg: "来自孙子的问候",
};
}
//当组件加载完成时,直接订阅消息
componentDidMount() {
Event.subscribe('taggle', (data) => {
this.setState({
msg: data
});
});
}
render() {
return (
<div>
<h1>我是孙子组件,{this.state.msg}</h1>
</div>
);
}
}
5.状态提升(中间人模式)
React中的状态提升概括来说,就是将多个组件需要共享的状态提升到它们最近 的父组件上.在父组件上改变这个状态然后通过props分发给子组件.
其实状态提升,说白了,就是找他们共同的爹
//状态提升
import React from 'react';
export default class State extends React.Component {
constructor(props) {
super(props);
this.state = {
msg: '我是父亲,共享数据'
};
}
ChangeMsg = (text) => {
this.setState({
msg: text
});
}
changMe = () => {
this.setState({
msg: '都消停点,不要传了'
});
}
render() {
return (
<div>
{/* 自定义事件,回调函数 */}
<ChildOne role={this.state.msg} onChangeMsg={this.ChangeMsg}></ChildOne>
<ChildTwo role={this.state.msg} onChangeMsg={this.ChangeMsg}></ChildTwo>
<button onClick={this.changMe}>问候一下</button>
</div>
);
}
}
class ChildOne extends React.Component {
change = () => {
this.props.onChangeMsg('我把数据传给弟弟');
}
render() {
return (
<div>
<h1>我是大儿子 {this.props.role}</h1>
<button onClick={this.change}>改变</button>
</div>
);
}
}
class ChildTwo extends React.Component {
change = () => {
this.props.onChangeMsg('我把数据传给哥哥');
}
render() {
return (
<div>
<h1>我是小儿子 {this.props.role}</h1>
<button onClick={this.change}>改变</button>
</div>
);
}
}
在 React 中,将多个组件中需要共享的 state 向上移动到它们的最近共同父组件中,便可实现共享 state。这就是所谓的状态提升
6.ref标记实现通信
一般不建议使用ref,因为ref这种方式,权限太大了,可以直接获取整个子组件 的对象
export default class App extends Component {
render() {
return (
<div>
<button onClick={() => {
console.log(this.refs.mychild.state.myname);
this.refs.mychild.handleEvent("父组件的问候");
}}>子组件对象</button>
{/* ref属性加载组件上,此时调用它的组件会获取到整个子组件的对象 */}
<Child ref='mychild' />
</div>
)
}
}
class Child extends Component {
state = {
myname: 'liuqiao'
}
handleEvent = (text) => {
console.log("父组件来了" + text);
this.setState({
myname: text
});
}
render() {
return (
<div>
我的名字:{this.state.myname}
</div>
)
}
}
三.总结
1.父通子,子通父
父通子不用说,props传递,子通父,也简单,回调函数通信
2.非父子组件通信,跨级深
非嵌套组件的通信,这类组件的通信可以考虑通过发布订阅模式或者采用context实现
如果采用context,就是利用组件的共同父组件的context对象进行通信
3.兄弟通信
中间人模式,即状态提升!也就是利用父组件实现中转传递,但是会增加子组件和父组件之间的耦合度,如果嵌套深的话,不易找到父组件
所以,我认为非父子通信,跨级比较深的,最好的方式是 发布订阅模式!