概述
在 React 中,组件之间的通讯是必不可少的一环,我们在处理业务的过程中,可能要使用很多组件,并在它们之间进行传值通讯,比如,父组件与子组件之间如何通讯,子组件与父组件又如何通讯,两个不相邻的组件如何进行跨组件之间的通讯,接下来将会介绍在 React 中常用的组件之间通讯的方法。
组件之间通讯的方法
父子通讯
父组件与子组件之间的通讯可以通过属性传值进行通讯,React 会把父组件传递的所有数据放到 props
对象中传递给子组件。
jsx
import React, { Component } from 'react'
// 父组件
class App extends Component {
constructor() {
super()
this.state = {
title: [
{
id: 1,
name: '标题一'
},
{
id: 2,
name: '标题二'
},
{
id: 3,
name: '标题三'
}
]
}
}
render() {
return (
<div>
<Nav title={title} />
</div>
)
}
}
// 类式 子组件
class Nav extends Component {
constructor(props) {
super(props)
}
render() {
const { title } = this.props
return (
<div>
<ul>
{
title.map(item => {
return <li key={item.id}>{item.name}</li>
})
}
</ul>
</div>
)
}
}
// 函数式 子组件
function Nav(props) {
const { title } = props
return (
<div>
<ul>
{
title.map(item => {
return <li key={item.id}>{item.name}</li>
})
}
</ul>
</div>
)
}
子父通讯
子组件与父组件之间的通讯可以通过父组件传给子组件一个回调函数,然后子组件可以调用这个函数并传参给父组件。
jsx
import React, { Component } from 'react'
// 父组件
export class App extends Component {
constructor() {
super()
this.state = {
count: 0
}
}
changeCount(count) {
this.setState({ count: this.state.count + count })
}
render() {
const { count } = this.state
return (
<div>
<AddCount addClick={(count) => this.changeCount(count)}/>
</div>
)
}
}
// 子组件
class AddCount extends Component {
addCount(count) {
this.props.addClick(count)
}
render() {
const { title } = this.props
return (
<div>
<button onClick={e => this.addCount(1)}>+1</button>
</div>
)
}
}
跨组件通讯
Context
我们可以先明确几个概念:
Provider:生产者容器组件, 专门用于负责生产数据
Consumer:消费者容器组件, 专门用于消费生产者容器组件生产的数据的
容器组件:专门用于包裹其它组件的组件, 我们就称之为容器组件
在 React 中如果组件之间传递数据层次太深, 一层一层的传递比较麻烦, React 提供了 Context 上下文这个方法来传递数据。
此时我们可以通过调用 React.createContext({})
这个创建上下文的方法,然后就会返回两个容器组件,即 Provider
和 Consumer
,利用这两个容器组件从祖先组件传递数据给到所有的后代组件,其中 Provider
接收一个 value
属性,传递给消费组件。
一个 Provider
可以和多个消费组件有对应关系,多个 Provider
也可以嵌套使用,里层的会覆盖外层的数据,当 Provider
的 value
值发生变化时,它内部的所有消费组件都会重新渲染。
而 Context
提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props
。它设计目的是为了共享那些对于一个组件树而言是全局的数据,例如当前认证的用户、主题或首选语言。
jsx
import React, { Component } from 'react'
// 1. 创建一个上下文对象
const AppContext = React.createContext({})
// 2. 从上下文对象中获取容器组件
const { Provider, Consumer } = AppContext
// 定义子组件
class Childern extends Component{
render(){
return (
<Consumer>
{
(value)=>{
return (
<div>
<p>{value.name}</p>
<p>{value.age}</p>
</div>
)
}
}
</Consumer>
)
}
}
// 定义父组件
class Teacher extends Component{
render(){
return (
<div>
<Childern></Childern>
</div>
)
}
}
class App extends Component{
render(){
return (
/* 3. 此时我们可以在生产者容器组件中通过 value 来生产数据 */
<Provider value={{name: '编程老师', age: 28}}>
<Teacher></Teacher>
</Provider>
)
}
}
还可以在创建 Context
到时候指定默认值,也可以通过给组件设置 contextType
从上下文中获取数据,挂载在 class 上的 contextType
属性会被重赋值为一个由 React.createContext()
创建的 Context
对象,然后就可以使用 this.context
来消费最近 Context
上的那个值。
jsx
import React, { Component } from 'react'
// 1. 创建一个上下文对象
const AppContext = React.createContext({
name: '编程老师',
age: 28
})
class Childern extends Component{
render(){
return (
<div>
{/* 从当前组件的上下文中消费数据 */}
<p>{this.context.name}</p>
<p>{this.context.age}</p>
</div>
)
}
}
// 2. 指定当前组件的上下文
Childern.contextType = AppContext
class Teacher extends Component{
render(){
return (
<div>
{/* 从当前组件的上下文中消费数据 */}
<p>{this.context.name}</p>
<p>{this.context.age}</p>
<Childern></Childern>
</div>
)
}
}
Teacher.contextType = AppContext
class App extends Component{
render(){
return (
// 3. 使用生产者生产数据
<div>
<Teacher></Teacher>
</div>
)
}
}
export default App
但是,如果存在多个生产者,就不能使用 contextType
这种形式接收数据,只能使用 Consumer
这种方式接受数据。
jsx
import React, { Component } from 'react'
const AppContext1 = React.createContext({})
const AppContext2 = React.createContext({})
class Childern extends React.Component {
render() {
return (
<AppContext1.Consumer>
{(value) => {
return (
<AppContext2.Consumer>
{(value2) => {
return (
<div>
<p>{value.name}</p>
<p>{value.age}</p>
<p>{value2.course}</p>
</div>
)
}}
</AppContext2.Consumer>
)
}}
</AppContext1.Consumer>
)
}
}
class Teacher extends React.Component {
render() {
return (
<div>
<Childern></Childern>
</div>
)
}
}
class App extends React.Component {
render() {
return (
<AppContext1.Provider value={{ name: '编程老师', age: 28 }}>
<AppContext2.Provider value={{ course: '前端' }}>
<Teacher></Teacher>
</AppContext2.Provider>
</AppContext1.Provider>
)
}
}
export default App
事件总线
虽然通过 Context
我们已经能够实现跨组件通讯,但是 Context
只能实现从上往下传递,不能实现从下往上传递或者同级之间传递,我们可以借助第三方库 events 来实现跨组件之间的通讯。
jsx
import React, { Component } from 'react'
import { EventEmitter } from 'events'
// 1. 创建一个全局的事件管理器对象
const eventBus = new EventEmitter()
class Childern extends Component{
componentDidMount() {
// 2. 监听事件
eventBus.addListener('sayHello', this.handle)
}
// 注意点:最好在组件卸载的时候移除对应的事件
componentWillUnmount() {
eventBus.removeListener('sayHello', this.handle)
}
handle = (name, age) => {
console.log(name, age)
}
render(){
return (
<div>Childern</div>
)
}
}
class Teacher extends Component{
render(){
return (
<div>
<p>Teacher</p>
<button onClick={()=>{this.btnClick()}}>按钮</button>
</div>
)
}
btnClick(){
// 3. 发出事件
eventBus.emit('sayHello', '编程老师', 28)
}
}
总结
可见,React 组件之间的通讯有很多种方式,你可以根据具体的业务来使用具体的通讯方式,但在平时开发时,使用的最多的还是 props
和 调用回调函数的方式。其实还有一种全局状态管理的解决方案,那就是 React Redux,类似于 Vue 全家桶中的 Pinia,而 React Redux 是 Redux 的官方 React UI 绑定库,由 Redux 官方团队维护,并 与 Redux 和 React 最新的 API 保持同步,关于 React Redux 的技术文章学习,后续我再和掘友们分享。