本文首发于公众号【一个老码农】
react组件之间的通信,大致可以分为以下几类
- 父传子
- 子传父
- 兄弟组件之间的通信
- 任意组件之间的通信
- 数据全局共享
下面我就来正式聊一下react
组件之间有哪些通信方式,并且都是怎样进行通信的,,语言我们用react
+ typescript
1.父传子
- props
props
传递数据是我们react
开发过程中最常用的一种方式,可以把父组件的数据传递给子组件,下面我们就用代码演示一下:
父组件代码:
import React from 'react';
import ChildA from './ChildA';
function App() {
return (
<div>
<ChildA title='父传子数据'></ChildA>
</div>
)
}
export default App
子组件代码:
interface ChildAProps {
title: string
}
const ChildA: React.FC<ChildAProps> = ({title}) => {
return <div>{title}</div>
}
export default ChildA
- useContext
useContext
传递的数据,在Context.Provider
的所有子组件中都可以获取到,value
一旦更新,所有子组件也会同步更新
父组件代码:
import React from 'react';
import ChildB from './ChildB';
export const Context = React.createContext<String>('')
function App() {
return (
<Context.Provider value={'context传值'}>
<div>
<ChildB></ChildB>
</div>
</Context.Provider>
)
}
export default App
子组件代码:
import { useContext } from "react"
import { Context } from "./App"
const ChildB = () => {
const title = useContext(Context)
return <div>
这是ChildB {title}
</div>
}
export default ChildB
2. 子传父
-
Props
回调函数
向子组件Props
中传一个函数,在子组件需要向父组件传递数据时,调用此函数即可
子组件代码:
interface ChildAProps {
title?: string
call?: (param: string) => void
}
const ChildA: React.FC<ChildAProps> = ({title, call}) => {
return <div onClick={()=>{
call && call('回传参数')
}}>这是ChildA {title}</div>
}
export default ChildA
父组件代码:
import React from 'react';
import ChildA from './ChildA';
function App() {
return (
<div>
<ChildA call={(v)=>{
console.log(`子组件传过来的值 ${v}`)
}}></ChildA>
</div>
)
}
export default App
-
useRef
函数调用
ref
的方式可以为父组件提供一个可以调用子组件的函数,在子组件被调用的函数中可以返回相应的数据至父组件,父组件则需要在子组件的Props
中传入一个ref
子组件代码:
import { MutableRefObject, useImperativeHandle } from "react"
export interface ChildDCurrent {
refresh: () => string
}
interface ChildDProps {
cref: MutableRefObject<ChildDCurrent>
}
const ChildD: React.FC<ChildDProps> = ({cref}) => {
useImperativeHandle(cref, ()=>({
refresh: () => {
return '123'
}
}))
return <div></div>
}
export default ChildD
父组件代码:
import { MutableRefObject, useRef } from "react"
import ChildD, { ChildDCurrent } from "./ChildD"
const ChildC = () => {
const ref = useRef() as MutableRefObject<ChildDCurrent>
return <div>
<button onClick={()=> {
// ChildD传过来的数据
const title = ref.current.refresh()
console.log(`子组件传递过来的数据 ${title}`)
}}>
点击刷新
</button>
<ChildD cref={ref}></ChildD>
</div>
}
export default ChildC
3.兄弟组件之间的通信
兄弟组件之间的数据传递,可以利用组件的Props
以及Props
回调函数来进行,而这种使用方法通信的前提是:必须要有共同的父组件
子组件代码:
interface ChildFProps {
update: (title: string) => void
}
const ChildF: React.FC<ChildFProps> = ({update}) => {
return <div onClick={() => {
update('abcde')
}}>ChildF</div>
}
export default ChildF
interface ChildGProps {
title: string
}
const ChildG: React.FC<ChildGProps> = ({title}) => {
return <div>ChildG {title}</div>
}
export default ChildG
父组件代码:
import { useState } from "react"
import ChildF from "./ChildF"
import ChildG from "./ChildG"
const ComE = () => {
const [updateValue, setUpdateValue] = useState('')
return <div>
<ChildF update={(v) => {
setUpdateValue(v)
}}></ChildF>
<ChildG title={updateValue}></ChildG>
</div>
}
export default ComE
4.任意组件之间的通信
- 观察者
如果组件之间没有共同的父组件,那我们就可以用观察者进行组件之间的通信,这个时候我们就需要用到第三方的插件events
。下面我们讲下如何用events
实现组件间的通信。
首先我们需要执行以下命令安装event
:
yarn add events
代码:
安装成功后,我们新建一个event.js
文件,导出一个全局对象
import EventEmitter from "events";
export default new EventEmitter()
接收数据的组件代码:
import { useEffect, useState } from "react"
import event from "./class/event"
const ChildI = () => {
const [message, setMessage] = useState('')
useEffect(() => {
//监听消息
event.addListener('message', (message) => {
setMessage(message)
})
return () => {
event.removeListener('message', (message) => {
console.log(message)
})
}
})
return <div>{message}</div>
}
export default ChildI
发送数据的组件代码:
import event from "./class/event"
const ChildJ = () => {
return <div>
<button onClick={() => {
//发送消息
event.emit('message','这是我发的消息')
}}>发送消息</button>
</div>
}
export default ChildJ
5.各组件数据全局共享
-
window
挂载属性
全局数据的共享,如果比较简单的数据,我们可以在window
上挂载属性。但是在typeScript
中,因为Window
类中没有对应的属性,所以会提示报错,我们可以用以下办法解决:
在src
中创建一个@types
文件夹,在@types
文件夹下面新建一个index.d.ts
文件,代码如下:
interface Window {
customTitle: string
}
有了以上代码之后就可以在任意地方用以下代码赋值:
window.customTitle = 'xxx'
取值就用下面代码:
const customTitle = window.customTitle
console.log(customTitle)
- 自定义单例
上面的window
以及更上面的观察者其实都是一个单例,window
挂载属性虽然方便,但是违背了设计模式的单一原则,而且并不利于代码管理和维护。在开发中,个人其实还是建议自定义一个单例进行管理。
我们新建一个class
,并导出一个全局对象
export class Single {
title?: string
}
export default new Single()
使用时,我们这样写:
import single from './class/Single';
//赋值
single.title = 'abc'
//取值
const title = single.title
console.log(title)
当然,做为一个习惯其它强类型语言的程序员,我们更熟悉这种写法:
export class Single {
private static instance: Single
public title: string
private constructor(title: string) {
this.title = title
}
/**
* 获取单例对象
*/
public static getInstance() {
if (!this.instance) {
this.instance = new Single('')
}
return this.instance
}
}
使用时这样使用:
//赋值时这样写
Single.getInstance().title = 'xxxx'
//取值使用这样写
const title = Single.getInstance().title
console.log(title)