1. 实现需求

  • 子组件是个弹框
  • 父组件通过props可以控制弹框出现
  • 弹框内有一个关闭按钮,可以令弹窗消失

2. 出现Bug

使用getDerivedStateFromProps 让组件在props变化时更新state。但实际上只要父组件重新渲染时,这个生命周期函数就会重新调用,不管 props 有没有变化。因此会出现下面的bug。

操作 props state
父组件点击显示弹窗 true true
子组件关闭弹窗后 true false
父组件执行render函数 ture true (此时,没有点击显示弹窗,弹窗会自动弹出)

如果父组件重新渲染,在子组件修改的所有state都会丢失。这时,visible不是一个单一来源的值,导致state没有正确渲染。直接将props直接复制到state是不安全的。任何数据,都应保证只有一个数据来源,而且避免直接复制它。官方示例

3. 解决方法

3.1 完全可受控组件

从组件里删除state,变量只受props控制,调用父组件方法修改值。

1
2
3
function EmailInput(props) {
return <Input onChange={props.onChange} value={props.email} />
}

3.2 有key的非可控组件

让组件自己存储临时的email state,但组件可以从props接收初始值,但更改之后的值就与props无关。

1
2
3
4
5
6
7
8
9
10
11
class EmailInput extends React.Component {
state = { email: this.props.defaultEmail }

handleChange = event => {
this.setState({ email: event.target.value })
}

render() {
return <input onChange={this.handleChange} value={this.state.email} />
}
}

当父组件需要改变state的值时,我们可以使用key这个特殊的React属性。当key变化时,React会创建一个新的而不是更新一个既有的组件。每次key变化,表单里的所有组件都会用新的初始值重新创建。大部分情况下,这是处理state的最好办法。

1
2
3
4
<EmailInput
defaultEmail={this.props.user.email}
key={this.props.user.id}
>

3.3 getDerivedStateFromProps

但有时组件初始化的开销太大,一个麻烦但可行的方案是在getDerivedStateFromPorps观察userID的变化。这样可以确认state的值,是通过父组件修改的,再做对应的处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class EmailInput extends React.Component {
state = {
email: this.props.defaultEmail,
prePropUserID: this.props.userID
}

static getDerivedStateFromProps(props, state) {
if (props.userID !== state.prevPropsUserID) {
return {
email: props.defaultEmail,
prevPropsUserID: props.userID
}
}
return null;
}
}

3.4 ref调用子组件方法

注意,不能ref高阶组件(connect到redux的组件)

参考文档