React 组件之间的通信是基于 props 的单向数据流,即父组件通过 props 向子组件传值,亦或是子组件执行传入的函数来更新父组件的state,这都满足了我们大部分的使用场景。
那 React 在兄弟组件之间如何通信呢?


一般地,对于兄弟组件之间的通信,是通过它们共同的祖先组件进行的,即 Lifting State Up,官方文档也有介绍。组件一通过事件将状态变更通知它们共同的祖先组件,祖先组再将状态同步到组件二。

但是,如果组件之间嵌套的比较深,即使提升状态到共同父组件,再同步状态到相应的组件还是需要一层一层的传递下去,可能会比较繁琐。

React 官方文档中介绍了 context 的用法, 在对应的场景中,context 就可以缩短父组件到子组件的属性传递路径。具体看例子:

Container.jsx

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
import Parent from './Parent'
import ChildOne from '../components/ChildOne'
import ChildTwo from '../components/ChildTwo'

export default class Container extends React.Component {
constructor(props) {
super(props);
this.state = { value: '' }
}

changeValue = value => {
this.setState({ value })
}

getChildContext() {
return {
value: this.state.value,
changeValue: this.changeValue
}
}

render() {
return (
<div>
<Parent>
<ChildOne />
</Parent>
<Parent>
<ChildTwo />
</Parent>
</div>
)
}
}

Container.childContextTypes = {
value: PropTypes.string,
changeValue: PropTypes.func
}

Parent.jsx

1
2
3
4
5
6
7
import React from "react"

const Parent = (props) => (
<div {...props} />
)

export default Parent

ChildOne.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
export default class ChildOne extends React.Component {

handleChange = (e) => {
const { changeValue } = this.context
changeValue(e.target.value)
}

render() {
return (
<div>
子组件一
<input onChange={this.handleChange} />
</div>
)
}
}

ChildOne.contextTypes = {
changeValue: PropTypes.func
}

ChildTwo.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export default class ChildTwo extends React.Component {
render() {
return (
<div>
子组件二
<p>{this.context.value}</p>
</div>
)
}
}

ChildTwo.contextTypes = {
value: PropTypes.string
}

运行效果

Container.childContextTypes 中进行接口的声明,通过 getChildContext 返回更新后的state,在 Child.contextTypes 中声明要获取的接口,这样在子组件内部就能通过 this.context 获取到。通过 Context 这样一个中间对象,ChildOne 和 ChildTwo 就可以相互通信了。

注意,React Context 也有一些局限性

  1. React Context 目前在 React 在是一个 experimental feature,在未来的版本中会有变化说不定,这点官方文档有说明。
  2. 在组件树中,如果中间某一个组件 ShouldComponentUpdate returning false 了,会阻碍 context 的正常传值,导致子组件无法获取更新。
  3. 组件本身 extends React.PureComponent 也会阻碍 context 的更新。

解决 ShouldComponentUpdate 与 Context 之间冲突的方案也是有的,例如使用 Redux 或者 Mobx 等全局单一状态管理。
这里抛砖引玉,介绍一种简单的解决 Context 不起作用的方法,它必须满足两个条件:

  1. Context 应该是唯一不可变的
  2. 组件只在初始化的时候去获取 Context

具体看代码:

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// Theme stores the state of the current theme, and allows components 
class Theme {
constructor(color) {
this.color = color
this.subscriptions = []
}

setColor(color) {
this.color = color
this.subscriptions.forEach(f => f())
}

subscribe(f) {
this.subscriptions.push(f)
}

unsubscribe(f) {
this.subscriptions = this.subscriptions.filter(m => m !== f)
}
}

export default class ThemeProvider extends React.Component {
constructor(p, c) {
super(p, c)
this.theme = new Theme(this.props.color)
}

componentWillReceiveProps(next) {
this.theme.setColor(next.color)
}

getChildContext() {
return {theme: this.theme}
}

handleChange = (e) => {
const color = e.target.value
this.theme.setColor(color)
}

render() {
return (
<div>
<select name="colors" onChange={this.handleChange}>
<option value="red">red</option>
<option value="green">green</option>
<option value="blue">blue</option>
</select>
{this.props.children}
</div>
)
}
}

ThemeProvider.childContextTypes = {
theme: PropTypes.object
}
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
export default class ThemedText extends React.Component {
state = { color: '' }

componentDidMount() {
this.updateTheme()
this.context.theme.subscribe(this.updateTheme)
}

updateTheme = () => {
this.setState({color: this.context.theme.color })
}

componentWillUnmount() {
this.context.theme.unsubscribe(this.updateTheme)
}

render() {
return (
<div style={{color: this.state.color}}>
{this.props.children}
</div>
)
}
}

ThemedText.contextTypes = {
theme: PropTypes.object
}

示例:

1
2
3
4
5
<ThemeProvider color="red">
<ThemedText>
<p>xxxxxxxxxxxxxxxxxxxxxxx</p>
</ThemedText>
</ThemeProvider>

小结:
React Context 是嵌套层次较深的兄弟组件之间通信的一种便捷方式,在某些使用场景作用是很强大的,所以需要谨慎使用。