一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第26天,点击查看活动详情。
react组件通信无非就是这几种情况:父子组件通信、爷孙/祖孙之间通信、兄弟组件通信、其他关系组件通信。
父子组件的通信
父传子
因为 React 的设计实际上就是传递 Props
即可。那么场景体现在容器组件与展示组件之间,通过 Props
传递 state
,让展示组件受控。
可以在子组件标签上定义属性将父组件的值传进去,在子组件可以使用props接收传进来的属性值。示例:
function Parent() {
const [data, setData] = useState(1)
const change = () => {
setData(data + 1)
}
return (
<div onClick={change}>
父传子
<Child data={data} />
</div>
)
}
function Child(props) {
return (
<div>
子组件接收到的值:{props.data}
</div>)
}
子传父
有两种方式可以支持子传父,可以使用回调函数与实例函数。
1、使用回调函数
在父组件内定义一个回调函数getChildVal
,将这个函数绑定在子组件上通过props传入子组件,当子组件中是事件或者什么时机需要向父组件传值时,在子组件内就可以调用这个函数并传入值,此时定义在父组件的函数被触发,并且可以拿到子组件的传值
function Parent() {
const [data, setData] = useState()
const getChildVal = (val) => {
console.log('从子组件获取的值',val)
setData(val)
}
return (
<div>
父组件接收到的值:{data}
<Child onchange={getChildVal} />
</div>
)
}
function Child(props) {
const [val, setVal] = useState(1)
const changeVal = () => {
setVal(val+1)
props.onchange(val+1)
}
return (
<div onClick={changeVal}>
子传父
</div>)
}
2、使用实例(ref)
以function组件为例,具体操作为:1. 使用useRef
初始化一个ref。2. 将这个ref设置在需要的元素或子组件上。3. 通过定义的ref.current
访问元素
import React, {useRef, useRef} from 'react';
function Parent() {
const nameRef = useRef(null)
const btnClick = () => {
console.log(nameRef.current, nameRef.current.value)
}
return (
<div>
<ChildRef ref={nameRef} />
<button onClick={btnClick}>提交</button>
</div>
)
}
// 如果ref是设置在自定义组件,需要React.forwardRef处理一下
const ChildRef = React.forwardRef(Child)
function Child(props, ref) {
return(
<div>
年龄:<input ref={ref}></input>
</div>
)
}
跨层级通信
多级通信有两种情况:
第一种是一个容器中包含了多层子组件,需要最底部的子组件与顶部组件进行通信。在这种情况下,如果不断透传 Props 或回调函数,不仅代码层级太深,后续也很不好维护。
第二种是两个组件不相关,在整个 React 的组件树的两侧,完全不相交。
那么基于多层级间跨级的通信一般有三个方案。
- 使用 React 的
Context API
,最常见的用途是做语言包国际化 - 使用全局变量与事件。
- 使用状态管理框架。
先说一个透传示例:爷传孙
function Parent() {
const [data, setData] = useState(1)
const change = () => {
setData(data + 1)
}
return (
<div onClick={change}>
爷传孙
<Current data={data} />
</div>
)
}
function Current(props) {
return <Child {...props} />
}
function Child(props) {
return (
<div>
孙组件接收到的值:{props.data}
</div>)
}
这种方法呢,要根据实际情况来,如果跨级比较多组件繁琐不建议使用,维护起来比较麻烦。
Context
使用方法:1. createContext创建一个context对象。2. 使用Provider设置value属性传递值
import React, { Component } from 'react';
// 1.创建context对象
const Context = React.createContext();
// 2.父辈组件使用Provider传值
class Demo extends Component{
render() {
return <Context.Provider value={10}>
<ClassDemo />
</Context.Provider>
}
}
// 3.子组件用contextType进行取值
class ClassDemo extends Component{
static contextType = Context
render() {
console.log(this.context)
return <div>0</div>
}
}
自定义全局变量及事件
比如可以类似与vue一样,我们在react中去做一个EventBus
去做不相关组件之间的通信。
先定义一个全局EventBus(例如在utils/eventbus.js中定义)
// Bus:事件派发、监听和回调管理
class Bus {
constructor(){
this.callbacks = {}
}
$on(name, fn){
this.callbacks[name] = this.callbacks[name] || []
this.callbacks[name].push(fn)
}
$emit(name, ...args){
if(this.callbacks[name]){
this.callbacks[name].forEach(cb => cb(...args))
}
}
$off(name) {
if (this.callbacks[name]) {
delete this.callbacks[name]
}
}
}
const EventBus = new Bus()
export default EventBus;
使用该EventBus通信:
注册的一个事件后在多个地方都是可以触发的。比如说A组件中需要去监听一个全局状态,然后在B组件、C组件都可以去修改这个状态,那么就可以在B组件和C组件需要的时机去触发这个事件,A组件都可以监听相应到。
// A组件
import EventBus from '@/utils/eventbus';
function ALayout(){
const [color, setColor] = useState('red')
useEffect(()=>{
EventBus.$on('change', (c, val, val2) => {
setColor(c)
// 还可以接收其他额外的值
console.log('取到的传值:', val, val2)
})
},[])
return (<div style={{color}}>A组件全局颜色</div>)
}
// B组件
import EventBus from '@/utils/eventbus';
function B(){
// 触发事件并传值
const emitEvent = () => {
EventBus.$emit('change', 'blue', '其他值', 2)
}
return (<div onClick={emitEvent}>B组件设置为蓝色</div>)
}
// C组件
import EventBus from '@/utils/eventbus';
function C(){
// 触发事件并传值
const emitEvent = () => {
EventBus.$emit('change', 'green')
}
return (<div onClick={emitEvent}>C组件设置为绿色</div>)
}
使用状态管理框架
比如 Flux、Redux 及 Mobx。
当组件绑定一个的model后,此model中的state数据改变时,相应其他组件中用到了这个model中的state也会同步改变。
这种方法的优点是由于引入了状态管理,使得项目的开发模式与代码结构得以约束,缺点是学习成本相对较高。