04. State & 生命周期

State 与 props 类似,但是 state 是私有的,并且完全受控于当前组件.

State类似于vue中的data选项

将函数组件转换成 class 组件

在没有useState钩子函数之前,是通过class组件管理State ?

1
2
3
4
5
6
7
8
9
10
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world</h1>
<h2>It is {this.props.date.toLocalTimeString()}</h2>
</div>
)
}
}

每次组件更新时 render 方法都会被调用,但只要在相同的 DOM 节点中渲染 <Clock /> ,就仅有一个 Clock 组件的 class 实例被创建使用。这就使得我们可以使用如 state 或生命周期方法等很多其他特性。

单例模式?

向 class 组件中添加局部的 state

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Clock extends React.Component {
// 第二步:添加构造函数,并给 this.state 赋初始值
constructor(props) {
super(props) // 通过super将props传递到父类构造函数中
this.state = {date: new Date()}
}
render() {
// 第一步:render 方法内,将 this.props 替换成 this.state
return (
<div>
<h1>Hello, world</h1>
<h2>It is {this.state.date.toLocalTimeString()}</h2>
</div>
)
}
}


ReactDOM.render(
<Clock />,
document.getElementById('root')
);

将生命周期方法添加到 Class 中

在具有许多组件的应用程序中,当组件被销毁时释放所占用的资源是非常重要的。

Clock 组件第一次被渲染到 DOM 中的时候,就为其设置一个计时器。这在 React 中被称为“挂载(mount)”。

同时,当 DOM 中 Clock 组件被删除的时候,应该清除计时器。这在 React 中被称为“卸载(unmount)”。

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
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}

// 挂载 - 挂载完成后执行
componentDidMount() {
// 组件挂载后开启定时器
this.timerID = serInterval( // 可以向this添加任意属性字段
() => this.tick(), 1000
)
}

// 卸载
componentWillUnmount() {
// 组件卸载后清除定时器,释放内存
clearInterval(this.timerID)
}

tick() {
this.setState({
date: new Date()
})
}

render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}

ReactDOM.render(
<Clock />,
document.getElementById('root')
);

正确地使用 State

关于 setState() 你应该了解三件事:

不要直接修改 State

1
2
3
4
5
6

// Wrong 不要直接修改state
this.state.comment = 'Hello'

// Correct 应使用 setState() 方法
this.setState({comment: 'Hello'})

构造函数是唯一可以给 this.state 直接赋值的地方

setState是React内部方法,使state更新具有响应式?

State 的更新可能是异步的

出于性能考虑,React 可能会把多个 setState() 调用合并成一个调用。

因为 this.propsthis.state 可能会异步更新,所以你不要依赖他们的值来更新下一个状态

解决这个问题,可以让 setState() 接收一个函数而不是一个对象。这个函数用上一个 state 作为第一个参数,将此次更新被应用时的 props 做为第二个参数:

1
2
3
4
// Correct
this.setState((state, props) => ({
counter: state.counter + props.increment
}));

State 的更新会被合并

当你调用 setState() 的时候,React 会把你提供的对象合并到当前的 state

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  constructor(props) {
super(props);
this.state = {
posts: [],
comments: []
};
}

// 分别调用setState单独更新state下的属性
componentDidMount() {
fetchPosts().then(response => {
this.setState({
posts: response.posts
});
});

fetchComments().then(response => {
this.setState({
comments: response.comments
});
});
}

这里的合并是浅合并,所以 this.setState({comments}) 完整保留了 this.state.posts, 但是完全替换了 this.state.comments

数据是向下流动的(单向数据流)

不管是父组件或是子组件都无法知道某个组件是有状态的还是无状态的,并且它们也并不关心它是函数组件还是 class 组件。

这就是为什么称 state 为局部的或是封装的的原因。除了拥有并设置了它的组件,其他组件都无法访问

组件可以选择把它的 state 作为 props 向下传递到它的子组件中:

1
<FormattedDate date={this.state.date} />

FormattedDate 组件会在其 props 中接收参数 date,但是组件本身无法知道它是来自于 Clock 的 state,或是 Clock 的 props,还是手动输入的

这通常会被叫做“自上而下”或是“单向”的数据流。任何的 state 总是所属于特定的组件,而且从该 state 派生的任何数据或 UI 只能影响树中“低于”它们的组件。

如果你把一个以组件构成的树想象成一个 props 的数据瀑布的话,那么每一个组件的 state 就像是在任意一点上给瀑布增加额外的水源,但是它只能向下流动。

每个组件都是真正独立的。