不借助redux等状态管理工具的React组件间的通信解决方法
组件通信分类
React组件间通信分为2大类,3种情况:
- 1)有嵌套关系的
- 父->子
- 子->父
这里的父通指上级组件,不一定是直属上级,可跨级
- 2)无嵌套关系的
- 兄弟组件间
- 无共同上级组件的
父->子(props)
-
方法一:父组件向子组件传递 props,子组件得到 props 后进行相应的处理。(跨多层组件的时候只需要一层一层传下去就行)
image.png
// 父组件 Parent.js:
import React,{ PureComponent } from "react";
import Children from "./Children.js";
export default class Parent extends PureComponent{
state = {
title: '父组件',
}
outputDesc = () => {
console.log('父组件向子组件通信,通过props传递参数或者函数');
}
render(){
const { title } = this.state;
const childProps = {
title,
onOutputDesc: this.outputDesc,
}
return(
<Children {...childProps} />
)
}
}
// 子组件 Children.js:
// 界面上会显示出 【这是子组件获取到的父组件的title:父组件】
// 点击控制台会打印出 【父组件向子组件通信,通过props传递参数或者函数】
export default class Children extends PureComponent{
render(){
const { title, outputDesc } = this.props;
return(
<Fragment>
<p>这是子组件获取到的父组件的title:{title}</p>
<button onClick={onOutputDesc}>点击</button>
</Fragment>
)
}
}
- 方法二:通过context
使用 React提供的context API(Provider 和 Consumer)
对于方法一,如果要通信的组件间的层次结构很深,中间的每一层组件都要去传递 props,增加了复杂性,并且这些 props 并不是这些中间组件自己所需要的。
当组件层次在三层以上时建议使用context,context做的事情就是创建一个上下文对象,并且对外暴露提供者(通常在组件树中上层的位置)和消费者,在上下文之内的所有子组件,
都可以访问这个上下文环境之内的数据,并且不用通过props。可以理解为有一个集中管理state的对象,并限定了这个对象可访问的范围,在范围之内的子组件都能获取到它内部的值。
// context.js
// Provider和Consumer总是成对出现
import React from 'react'
const demoContext = React.createContext();
const demoProvider = demoContext.Provider;
const demoConsumer = demoContext.Consumer;
export demoProvider;
export demoConsumer;
// 父组件
import React from 'react'
import List from './List'
import { demoProvider } from './context'
export default class Todo extends React.PureComponent {
state = {
list: [],
}
task = React.createRef()
handleClick = () => {
const list = [...this.state.list, this.task.current.value];
this.setState({ list });
this.task.current.value = '';
}
deleteTask = (index) => {
const { list } = this.state;
list.splice(index, 1);
this.setState({ list });
}
render() {
return (
// 在父(祖先)级组件中把要传递内容放到value里面
<demoProvider value={{deleteTask: this.deleteTask}}>
<input type="text" ref={this.task}/>
<button onClick={this.handleClick}> 添加 </button>
<List list={this.state.list} deleteTask={this.deleteTask}/>
</demoProvider>
);
}
}
// 子组件
import React from 'react'
import {demoConsumer} from './context'
export default class List extends React.PureComponent{
render() {
const { list } = this.props
return (
<demoConsumer>
// 后代组件中的组件放在Consumer里面, 内部是一个函数, 这个函数接受一个对象作为参数, 参数是Provider里面提供的值
{
({ deleteTask }) => {
return list.map((item, index) => {
return (
<li key={item}>
{ item }
<button onClick={()=>{deleteTask(index)}}> 删除 </button>
</li>
)
})
}
}
</demoConsumer>
);
}
}
子->父(回调)
父组件将一个函数作为 props 传递给子组件,子组件调用该回调函数,向父组件通信。
// 父组件 Parent.js:
import React,{ PureComponent, Fragment } from "react";
import Children from "./Children.js";
export default class Parent extends PureComponent{
state = {
description: '',
}
handleChangeDesc = (props) => {
this.setState({description: '子组件向父组件通信,通过回调传递参数: 在子组件中点击按钮改变父组件state中的属性})
}
render(){
const { description } = this.state;
const childProps = {
onChangeDesc: this.handleChangeDesc,
}
return(
<Fragment>
<p>{description}</p>
<Children {...childProps} />
</Fragment>
)
}
}
// 子组件 Children.js:
// 界面上会显示出更新后的description值 【子组件向父组件通信,通过回调传递参数: 在子组件中点击按钮改变父组件state中的属性】
export default class Children extends PureComponent{
changeParentDesc = () => {
const { onChangeDesc } = this.props;
onChangeDesc({description: '子组件向父组件通信,通过回调传递参数: 在子组件中点击按钮改变父组件state中的属性'});
}
render(){
const { title, outputDesc } = this.props;
return(
<button onClick={this.changeParentDesc}>点击</button>
)
}
}
兄弟组件间
方法一:利用共同的上级组件作为中间人进行通信(子1->父->子2)
方法二:通过自定义发布-订阅模式的事件实现(以Node.js中events模块的EventEmitter 类为例,这个会在我的另一篇文章中细讲)
网友评论