前言:
在 React 16.3 中, 新的生命周期函数被引入了, 即静态方法 getDerivedStateFromProps
.
componentWillReceiveProps
, componentWillMount
, componentWillUpdate
这三个生命周期函数都被添加了 UNSAFE_ 不安全标记. React 提示我们不应该继续使用这些生命周期函数.
getDerivedStateFromProps
是一个口直体嫌的 API, 它的用法和 componentWillReceiveProps
几乎完全不一致. 它的名字揭示了它的用途, 可是它的使用体验却并不令人愉悦. 我们有更好的办法替代它.
为什么要使用 componentWillReceiveProps / getDerivedStateFromProps ?
在初始化组件数据时, 我们有时需要将组件接收的参数 props 中的数据添加到它的 state 中, 期望组件响应 props 的变化.
然而组件接收的 props 数据是只读的, 不可变的, 禁止修改的. 当组件接收了新的 props 时, constructor 函数中数据初始化行为并不会再次发生. 于是我们想要在 componentWillReceiveProps
/ getDerivedStateFromProps
生命周期函数中获取新的 props 并且调用 setState() 来更新数据.
实际场景
在如下图所示的界面中, 当用户点击了左侧面板中的编辑按钮后, 右侧的表单组件要能够根据用户点击的类目而变化. 同时表单输入域上的任何变化都会实时改变这个组件的数据(这个表单组件内部需要维护 state, 不是一个只接受 props 的完全受控组件) .
static getDerivedStateFromProps 和 componentWillReceiveProps 的显著区别
尽管 static getDerivedStateFromProps
看起来像是 UNSAFE_componentWillReceiveProps
的替代 API, 但是二者的触发阶段, 参数和可访问的数据都有很大的差异.
getDerivedStateFromProps
并不是 componentWillReceiveProps
的替代品.
触发机制:
-
UNSAFE_componentWillReceiveProps(nextProps)
在组件接收到新的参数时被触发.
当父组件导致子组件更新的时候, 即使接收的 props 并没有变化, 这个函数也会被调用.
UNSAFE_componentWillReceiveProps()is invoked before a mounted component receives new props.
Note that if a parent component causes your component to re-render, this method will be called even if props have not changed.
-
getDerivedStateFromProps(props, state)
会在每次组件渲染前被调用
getDerivedStateFromProps is invoked right before calling the render method, both on the initial mount and on subsequent updates.
getDerivedStateFromProps 会在每次组件被重新渲染前被调用, 这意味着无论是父组件的更新, props 的变化, 或是组件内部执行了 setState(), 它都会被调用.
工作方式
-
UNSAFE_componentWillReceiveProps(nextProps)
:
参数是组件接收到的新的 props , 用于比对新的 props 和原有的 props, 用户需要在函数体中调用 setState() 来更新组件的数据.
If you need to update the state in response to prop changes (for example, to reset it), you may compare this.props and nextProps and perform state transitions using this.setState() in this method.
-
static getDerivedStateFromProps(nextProps, currentState)
:
参数是组件接收到的新的 props 和组件当前的数据. 用户需要在这个函数中返回一个对象, 它将作为 setState() 中的 Updater 更新组件.
It should return an object to update the state, or null to update nothing.
为什么不应该使用 getDerivedStateFromProps
Deriving state leads to verbose code and makes your components difficult to think about.
充满了限制, 很难用
getDerivedStateFromProps
是一个静态方法, 是一个和组件自身"不相关"的角色. 在这个静态方法中, 除了两个默认的位置参数 nextProps 和 currentState 以外, 你无法访问任何组件上的数据.
相比起在 UNSAFE_componentWillReceiveProps(nextProps)
的函数体中直接比较 this.props 和 nextProps 上的字段. 你需要"绕一个弯" 去比较 nextProps 和 currentState.
示例代码:
UNSAFE_componentWillReceiveProps(nextProps){
if (this.props.currentExercise.id !== nextProps.currentExercise.id){
this.setState({...nextProps.currentExercise})
}
}
static getDerivedStateFromProps(nextProps, prevState) {
if (prevState.id !== nextProps.currentExercise.id) {
return {...nextProps.currentExercise}
}
return null;
}
会被频繁地触发
无论是组件调用了 setState(), 接收的 props 发生了变化, 或是父组件的更新都会导致子组件上的 getDerivedStateFromProps
被触发.
使用的时候必须非常小心
由于 getDerivedStateFromProps
会在 setState() 后被调用, 并且它的返回值会被用于更新数据. 这意味着你会在 setState() 之后触发 getDerivedStateFromProps
, 然后可能意外地再次 "setState()".
getDerivedStateFromProps(nextProps)
函数中的第一个位置参数未必是 "新" 的 props. 在组件内调用了 setState() 时, getDerivedStateFromProps
会被调用. 但是此时的组件其实并没有获得 "新" 的 props, 是的, 这个 nextProps 的值和原来的 props 是一样的.
这就导致了我们在使用 getDerivedStateFromProps
时, 必须添加很多逻辑判断语句来处理 props 上的更新和 state 上的更新, 避免意外地返回了一个 Updater 再次更新数据, 导致数据异常.
更优雅的做法
React 官方博客中提供了以下几种方案:
- 让表单控件变成完全受控组件, 不论是 onChange 处理函数还是 value 都由父组件控制, 这样用户无需再考虑这个组件 props 的变化和 state 的更新.
function EmailInput(props) {
return <input onChange={props.onChange} value={props.email} />;
}
- 让表单控件变成完全不受控组件, 但是具有 key 属性.
仍然用自身的数据来控制 value. 但是接收 props 中的某个字段作为 key 属性的值, 以此响应 props 的更新: 当 key 的值变化时 React 会替换 (reset)组件, 从而重新生成初始化数据.
When a key changes, React will create a new component instance rather than update the current one.
示例代码:
//组件内的代码
class EmailInput extends Component {
state = { email: this.props.defaultEmail };
handleChange = event => {
this.setState({ email: event.target.value });
};
render() {
return <input onChange={this.handleChange} value={this.state.email} />;
}
}
// 在父组件中接收 props 中的数据作为 key
<EmailInput
defaultEmail={this.props.user.email}
key={this.props.user.id}
/>
- 其他方法请参考 这里
参考
生命周期函数: https://reactjs.org/docs/react-component.html
You Probably Don't Need Derived State: https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html