Context

Context

React.js中,某个组件往自己的context里面放了某些状态,这个组件之下的所有子组件都可以直接访问这个状态,而不需要通过中间组件的传递。一个组件的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
class App extends Component {
static childContextTypes = {
themeColor: PropTypes.string,
num: PropTypes.number
}
constructor() {
super()
this.state = {
themeColor: 'red',
num: 'helloworld'
}
}
getChildContext() {
return {
themeColor: this.state.themeColor,
num: 123
}
}
render() {
return (
<div className="App">
<Head />
<Main />
<Footer />
</div>
)
}
}

getChildContext方法是用来设置context,返回的对象就是context,所有的子组件都是可以访问到这个对象。childContextTypes是用来验证getChildContext返回的对象必须要写。因为context是一个危险的特性,React.js团队想把危险的东西搞得复杂一点,提高使用门槛。

1
2
3
4
5
6
7
8
9
10
11
class Footer extends Component {
static contextTypes = {
// num: PropTypes.number,
themeColor: PropTypes.string
}
render() {
return(
<div style={{color: this.context.themeColor}}>这是底部了</div>
)
}
}

子组件想要获取context里面的内容,就必须写contextTypes来声明和验证你需要获取的状态的类型,它也是必写的,否则无法获取context里面的状态。

一个组件中定义了contextTypes,那么在以下生命周期中,将会收到额外的参数,即 context对象

  • constructor(props, context)
  • componentWillReceiveProps(nextProps, nextContext)
  • shouldComponentUpdate(nextProps, nextState, nextContext)
  • componentWillUpdate(nextProps, nextState, nextContext)
  • componentDidUpdate(nextProps, nextState, nextContext)

安全更新Context

state或者props更新时getChildContext方法会被调用,为了更新context,使用this.setState来更新本地state,这将会生成一个新的context,所有子组件都会接收到更新。

但如果有一个中间的父组件的shouldComponentUpdate返回了false(或者PureComponent组件),那么他的子组件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
class Title extends Component {
static contextTypes = {
themeColor: PropTypes.string
}
render() {
return (
<div style={{color: this.context.themeColor}}>
这是标题
</div>
)
}
}
class Main extends Component {
shouldComponentUpdate() {
return false;
}
render() {
return(
<div>
<Title />
<h3>主体部分</h3>
</div>
)
}
}

为了解决这个问题,可以通过基于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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
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)
}
}
class ThemeProvider extends Component {
constructor(props){
super(props)
this.themeColor = new Theme(this.props.color)
}
static childContextTypes = {
themeColor: PropTypes.object
}
componentWillReceiveProps(nextProps){
this.themeColor.setColor(nextProps.color)
}
getChildContext(){
return {
themeColor: this.themeColor
}
}
render() {
return (
<div className="App">{ this.props.children }</div>
)
}
}
class App extends Component {
constructor() {
super()
this.makeBlue = this.makeBlue.bind(this);
this.state = {
color: 'red',
num: 'helloworld'
}
}
makeBlue() {
this.setState({
color: 'blue'
})
}
render() {
return (
<ThemeProvider color={ this.state.color }>
<button onClick={this.makeBlue}>makeBule</button>
<Head />
<Main />
<Footer />
</ThemeProvider>
)
}
}
// Main
class Title extends Component {
static contextTypes = {
themeColor: PropTypes.object
}
componentDidMount() {
this.context.themeColor.subscribe( () => this.forceUpdate() )
}
render() {
return (
<div style={{color: this.context.themeColor.color}}>
这是标题
</div>
)
}
}

调用forceUpdate()将会导致组件的render()方法被调用,并忽略shouldComponentUpdate()。这将会触发每一个子组件的生命周期方法,涵盖,每个子组件的shouldComponentUpdate()方法。

我们创建了一个Theme 对象来保存状态,Theme对象同时也是一个事件发射器,这可以让像Title一样的组件来订阅未来的变化,Theme对象通过ThemeProvider在组件树中传递。只有刚开始的时候传递了context,后面的更新都通过Theme自己来传播,并没有重新创建一个context