React
组件根据层级关系可以划分成父组件(Parent
)、子组件(Child
)、子组件的兄弟组件(Brother
)以及孙组件(Grandson
),抛开三方数据流管理库(如 Redux
和 Mobx
等)本文将从 React
自身探讨和总结以上四种组件之间的数据通信方式。

父子组件数据通信
1. Parent
=> Child
Parent
组件想把数据传给子组件,最直接简单的方式就是通过组件 props
把数据传递过去。
1 2 3 4 5 6 7 8
| class Parent extends React.Component { state = {data: 1} render() { return <Child data={this.state.data} /> } }
|
第二种方法就是通过将数据或获取数据的方法挂载到 context
上面来供子组件使用,但是相比起直接传给子组件 props
,这种方式在实现起来会麻烦很多,故不推荐。
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 27 28 29 30 31 32
| class Parent extends React.Component { state = {data: 1} static childContextTypes = { data: PropTypes.number } getChildContext() { return { data: this.state.data } } render() { return <Child /> } }
// 子组件 class Child extends React.Component { // 声明自己需要父组件的 context 对象属性 static contextTypes = { data: PropTypes.number } render() { return <input value={this.context.data} /> } }
|
上述方法的使用的是 React16.3
以下的版本 context api
,而在 React16.3
之后的版本中,提供了新的 context api
。
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 27 28
| const Context = React.createContext() class Parent extends React.Component { state = {data: 1} render() { return ( <Context.Provider value={{data: this.state.data}}> <Child /> </Context.Provider> ) } }
// 子组件 class Parent extends React.Component { render() { return ( <Context.Consumer> { context => <input value={context.data} /> } </Context.Consumer> ) } }
|
2. Parent
<= Child
Parent
组件需要获取 Child
组件内部数据有三种方法:
2.1 通过 props
传入回调函数
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 27 28 29 30
| class Child extends React.Component { constructor(props){ this.state = {data: 2} props.getData(this.state.data) } onChange = e => { this.setState({data: e.target.value}) this.props.getData(e.target.value) } render() { return <input onChange={this.onChange value={this.state.data} /> } }
// 父组件 class Parent extends React.Component { state = {data: 1} // 获取子组件 data getData = data => { this.state.data = data } render() { return <Child getData={this.getData} /> } }
|
这种方式优点是简单,缺点是如果想保持 Parent
随时可以同步 Child
数据的话,需要在每个改变 Child
数据的地方执行 getData
。
2.2 通过 context
传入回调函数
与 props
传入回调函数类似,该方法将回调函数挂载到 context
对象上,子组件可以通过 context
中的回调,将自己的数据传给父组件,这种方式比起将回调挂载到 props
对象上实现起来更为繁琐,不推荐。
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 27 28 29 30 31 32 33 34 35 36 37 38 39
| const Context = React.createContext() class Parent extends React.Component { state = {data: 1} getData = data => { this.state.data = data} render() { return ( <Context.Provider value={{getData: this.getData}}> <Child /> </Context.Provider> ) } }
// 子组件 class Child extends React.Component { state = {data: 2} render() { return ( <Context.Consumer> { context => <input value={this.state.data} onChange={e => { this.setState({data: e.target,value}) context.getData(e.target.value) }} /> } </Context.Consumer> ) } }
|
2.3 通过 ref
调用 Child
内部方法
通常,每个 class
类型的 React.Component
都会有一个 ref
属性供它的外层组件使用,外层组件可以通过 ref
来拿到组件实例内部的方法和数据,利用这一点我们可以让父组件获取到子组件的数据。
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 27 28
| class Parent extends React.Component { state = {data: 1} getData = data => { this.state.data = this.refs.child.getCurData() } render() { return <Child ref='child' /> } }
// 子组件 class Child extends React.Component { state = {data: 2} // 输入值发生变化 onChange = e => { this.setState({data: e.target.value}) } // 返回当前最新的 data getCurData = () => this.state.data render() { return <input onChange={this.onChange} value={this.state.data} /> } }
|
这种方式灵活性比较高,子组件不需要在每次 data
发生改变的时候执行回调同步 data
给 Parent
,对于 Child
而言,只要保证 getCurData
返回的是最新的 data
即可,而 Parent
只要在需要 Child
数据的时候调用 getData
即可。
爷孙组件数据通信
从文章开始的图可以看出,爷孙组件之间的通信可以通过 props
依次下传的方式,也可以通过 context
对象直接进行数据通信,通过上面提及的父子组件的通信方式可以看出,采用 props
传递数据类似于瀑布流依次向下,而 context
对象则像一块共享空间,父组件和它的子孙组件都可以通过这个空间对数据予取予求,对于嵌套较深的子孙组件,如果采用 props
往下传的方式通信数据,会形成一条很长的 props
依赖链,这会造成以下问题:
很多不直接使用通信数据的组件,会被迫引入这个通信数据,然后接着往它的子组件传。
一旦父组件的数据格式发生变化,那么引入了该数据格式的子孙组件都需要进行相应修改。
而如果采用 context
对象让父组件和子孙组件跨层级共享数据可以解决 props
下传带来的两个问题,但同时,使用 context
对象也会带来副作用:
- 如果组件树比较庞大,父组件和子孙组件间的数据依赖关系可能不够明显,不利于后期代码的维护
- 对于
React16.3
版本以下的 context api
,容易被 shouldComponentUpdate
函数截断,导致依赖 context
数据的组件无法在 context
数据变化时重新渲染
兄弟组件数据通信
通常兄弟组件之间如果需要数据通信,常见的做法是把需要通信的数据放置到父组件中进行管理,然后兄弟组件通过 props
或者 context
对象来读写数据。
任意组件数据通信
任意组件,或者说是不区分层次关系的组件,要想进行数据通信可以采用三方对象管理来管理要共享的数据,而需要这些数据的组件只需通过订阅这些数据的变化即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const event = new EventEmitter() class A extends React.Component { constructor(){ event.sub('dataUpdate', data => console.log(data)) } } class B extends React.Component { constructor(){ setTimeout(() => event.pub('dataUpdate', 1), 200) } }
|