组件通信
爷孙组件通信
爷孙组件通信主要有 3 种方式:
- 将孙子组件的 props 封装在一个固定字段中
- 通过 children 透传
- 通过 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 组件更加独立。
作为替代,也可以传递一个组件实例:
三种方案的决策
- 第一种方案一般用于固定结构和跨组件有互相依赖的场景,多见于 UI 框架中的复合组件与原子组件的设计中
- 第二种常用在嵌套层级不深的业务代码中,比如表单场景。优点是顶层 Grandpa 的业务收敛度很高,一眼能看清 UI 结构及状态绑定关系,相当于拍平了 React 组件树
- 第三种比较通用,适合复杂嵌套透传场景。缺点是范式代码较多,且会造成 react dev tools 层级过多;Context 无法在父组件看出依赖关系,必须到子组件文件中才能知道数据来源