将父组件的props作为组件的初始state的几种方案
# 前言
在项目中有遇到一个需求,可能也是伪需求:如何将父组件的props作为组件的初始state状态。
官网用一个非常抽象的词来叙述这个需求:派生state
# 官网给的反面模式
直接复制 prop
到 state
class EmailInput extends Component {
// 直接赋值
state = { email: this.props.email };
render() {
return <input onChange={this.handleChange} value={this.state.email} />;
}
handleChange = event => {
this.setState({ email: event.target.value });
};
// 如果没有componentWillReceiveProps的话,还是正常的
// 如果不写,就监控不到父组件的变化
componentWillReceiveProps(nextProps) {
// 这会覆盖所有组件内的 state 更新!
// 不要这样做。
this.setState({ email: nextProps.email });
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
可以发现state维护了两个源: 键盘输入
+ 父组件
如果需要修改的,必须修改componentWillReceiveProps
class EmailInput extends Component {
state = {
email: this.props.email
};
componentWillReceiveProps(nextProps) {
// 只要 props.email 改变,就改变 state
if (nextProps.email !== this.props.email) {
this.setState({
email: nextProps.email
});
}
}
// ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
这样子看上去解决了:实际上还是存在问题,因为我们判断的是email
值是否改变了,见官网示例 (opens new window)
原因是:
const fakeAccounts = [
{
id: 1,
name: "One",
email: "fake.email@example.com",
password: "totally fake"
},
{
id: 2,
name: "Two",
email: "fake.email@example.com",
password: "also fake"
},
{
id: 3,
name: "Three",
email: "also.fake.email@example.com",
password: "definitely fake"
}
];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
可以发现第1个和第2个账号的 email
都是 fake.email@example.com
结果就是:虽然切换了账号,但是输入框内的值未发生改变(即,父组件切换到了fakeAccounts[1]
,但是由于value
没变化,所以子组件不觉得父组件已经变化了)
# 解决方案
# 1.建议:完全可控的组件
即所有的改变,全部由父组件维护。
# 2. 建议:有key的非控组件
<EmailInput
defaultEmail={this.props.user.email}
key={this.props.user.id}
/>
1
2
3
4
2
3
4
# 3. 建议:使用getDerivedStateFromProps
在某些情况下key
不起作用,使用getDerivedStateFromProps
观察id
的变化
可以通过绑定一个ID
来代替所有的变量。
new version
// 这里只列出需要变化的地方
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
// 增加一个 ID 来记录父组件的ID
userID: this.props.userID
counter: this.props.count
}
}
static getDerivedStateFromProps(props, state) {
// 把 value 比较变为 id 比较
// 比较 父组件的ID 和 当前state维护的ID ,如果不同,就重置初始的state
if (props.userID !== state.userID) {
return {
counter: props.counter, // 重置维护的state
userID: props.userID // 重新记录父组件的ID
}
}
return null
}
handleClick = () => {
this.setState({
counter: this.state.counter + 1
})
}
render() {
return (
<div>
<h1 onClick={this.handleClick}>Hello, world!{this.state.counter}</h1>
</div>
)
}
}
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
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
old version
| 掘金给出的方案
// 这里只列出需要变化的地方
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
// 增加一个 preCounter 来记录 value 的变化
preCounter: 0,
counter: 0
}
}
static getDerivedStateFromProps(props, state) {
// 跟 state.preCounter 进行比较
if (props.counter !== state.preCounter) {
return {
counter: props.counter,
preCounter: props.counter
}
}
return null
}
handleClick = () => {
// 子组件自己的维护的state
this.setState({
counter: this.state.counter + 1
})
}
render() {
return (
<div>
<h1 onClick={this.handleClick}>Hello, world!{this.state.counter}</h1>
</div>
)
}
}
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
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
# 总结
- 如果可以使用
componentWillReceiveProps
,并且父组件的props
修改子组件的state
一次,并且父组件改变后,也不会对子组件进行修改。 - 如果使用
getDerivedStateFromProps
时,则必须创建preCount
或者父组件的ID
来作为条件判断。
# 参考资料
编辑 (opens new window)
上次更新: 2022/04/06, 15:04:00