React组件通信

组件通信

爷孙组件通信

爷孙组件通信主要有 3 种方式:

  1. 将孙子组件的 props 封装在一个固定字段中
  2. 通过 children 透传
  3. 通过 context 传递

假设有个三层组件,爷爷分别给儿子和孙子发红包

先看青铜解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function Grandpa() {
const [someMoneyForMe] = useState(100);
const [someMoneyForDaddy] = useState(101);
return <Daddy money={someMoneyForDaddy} moneyForSon={someMoneyForMe} />;
}
function Daddy(props: { money: number; moneyForSon: number }) {
const { money, moneyForSon } = props;
return (
<div className="daddy">
<h2>This is Daddy, received ${money}</h2>
<Me money={moneyForSon} />
</div>
);
}
function Me(props: { money: number }) {
const { money } = props;
return (
<div className="son">
<h3>This is Me, received ${money}</h3>
</div>
);
}

Daddy 组件会透传爷爷给孙子的组件给 Me。这种方案的缺点很明显,以后爷爷要给 Daddy 和 Me 发糖果的时候,Daddy 还得加字段。

将孙子组件的 props 封装在一个固定字段中

按照 1 的方案,我们可以固定给 Daddy 添加一个 sonProps 的字段,然后将 Grandpa 需要传给孙子的状态全部通过 sonProps 传递

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function Grandpa() {
const [someMoneyForMe] = useState(100);
const [someMoneyForDaddy] = useState(101);
return <Daddy money={someMoneyForDaddy} sonProps={{money: someMoneyForMe}} />;
}
function Daddy(props: { money: number; sonProps: Parameters<typeof Me>[0]; }) {
const { money, sonProps } = props;
return (
<div className="daddy">
<h2>This is Daddy, received ${money}</h2>
<Me {...sonProps}/>
</div>
);
}
function Me(props: { money: number }) {
const { money } = props;
return (
<div className="son">
<h3>This is Me, received ${money}</h3>
</div>
);
}

这样以后要给 Me 加字段,就不用改 Daddy 了。但要测试 Daddy 时还得 mock Me 组件的数据,Daddy 和 Son 耦合。

通过 children 透传

children 类似于 vue 中的 slot,可以完成一些嵌套组件通信的功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function Grandpa() {
const [someMoneyForMe] = useState(100);
const [someMoneyForDaddy] = useState(101);
return (
<Daddy money={someMoneyForDaddy}>
<Me money={someMoneyForMe} />
</Daddy>
);
}
function Daddy(props: { money: number; children?: React.ChildNode }) {
const { money, children } = props;
return (
<div className="daddy">
<h2>This is Daddy, received ${money}</h2>
{children}
</div>
);
}
function Me(props: { money: number }) {
const { money } = props;
return (
<div className="son">
<h3>This is Me, received ${money}</h3>
</div>
);
}

将 Daddy 的嵌套部分用 children 替代后,解耦了子组件和孙子组件的依赖关系,Daddy 组件更加独立。

作为替代,也可以传递一个组件实例:

三种方案的决策

  1. 第一种方案一般用于固定结构和跨组件有互相依赖的场景,多见于 UI 框架中的复合组件与原子组件的设计中
  2. 第二种常用在嵌套层级不深的业务代码中,比如表单场景。优点是顶层 Grandpa 的业务收敛度很高,一眼能看清 UI 结构及状态绑定关系,相当于拍平了 React 组件树
  3. 第三种比较通用,适合复杂嵌套透传场景。缺点是范式代码较多,且会造成 react dev tools 层级过多;Context 无法在父组件看出依赖关系,必须到子组件文件中才能知道数据来源