前言
近年前端技术层出不穷,Facebook 推出的 React 也是程序员间讨论最多的 Javascript 框架之一。借这个机会开始学习 React,并把笔记作为 React 基础教程系列(参考 React 官网)。
因为属于入门,不太适合对 React 有经验的同学,但希望到此的同学多少都有所收获。
为了更好的阅读体验,可以在文章底下的“了解更多”中查看原文。
派生 state
我语文可能是体育老师教的,对于派生这个词语不知道是什么意思,先解释下什么是"派生":
派生(derived):本指江河的源头产生出支流。引申为从一个主要事物的发展中分化出来。
那么放在 Javascript 中,对于"派生 state"就好理解了,无非就是 state 状态对象的值由其他变量赋值得到,而不是我们对 state 进行初始化创建的。
那么就对于这样的 state ,在如下两个场景就会有"副作用",使得程序出现非预期的 bug 问题。
避免将 props 的值复制给 state
场景说明:
父组件 EmailReset 中有个定时器,页面渲染后将触发定时器,不断刷新 state 状态对象;
子组件 EmailInput 有个 Input 输入框,接受 props.email 属性,并将其作为 Input 输入框的 value 显示。
代码示例:
页面 HTML 展示:
父组件 EmailReset:
注意:
在 componentDidMount 生命周期中,定义并执行定时器 timer;
并对子组件 EmailInput 设置属性 email(此 email 和 state 并没有关系,此处还是个"死值")。
子组件 EmailInput:
一个典型 React 受控组件例子,Input 输出后触发事件方法,更新 state 对象重新渲染后回显到页面上。
问题:
看似没有问题,但当用户对 Input 输入数据后,会被初始化成原始值。
页面 HTML 效果:
结合浏览器控制台调试信息,和页面 HTML Input 输入框所示,我们看到三种现象:
1. 每次父组件定时器执行,更新 state 对象,触发 render 方法
2. 之后,每次还会触发子组件的生命周期方法:static getDerivedStateFromProps 和 componentWillReceiveProps(现在不建议使用)
3. 页面 HTML 的 Input 输入框值被还原
那怎么解决?
代码示例:
尝试更改逻辑设计,增加如上判断。这样每次父组件 state 状态的更新,虽然还会触发子组件的生命周期 componentWillReceiveProps 方法,但由于这样的判断,就不会重置 email 数据状态。
好像展示解决的这个 bug,但稍有疏忽还会引发其他问题。
在 props 变化后修改 state
场景说明:
针对上面那个解决方案,再引申举个反例。
比如,一个页面 HTML 中有多个 tab 标签,每个 tab 对应不同的 Input 输入框初始数据。
代码示例:
父组件 EmailFormTab:
定义父组件的 tab 栏,以及点击事件 onChooseTab:
逻辑这样:点击不同 tab,触发对应事件,获取点击 tab 索引,更新对应 state 状态;从而每次渲染后,currentEmail 为 emailList 对应的 email 值。
子组件同上。
问题:
看似也没什么问题,但注意前两个 tab 对应的 email 数据相同时,每当在 tab1 下,对 input 输入新值,再点击 tab2 ,会使得 input 变为前面输入的值。
这肯定不是我们设计上的原意(虽然公用一个 input,逻辑上每次切换 tab 时,希望他们保持独立)。
页面 HTML 效果:
原因就是 static getDerivedStateFromProps or componentWillReceiveProp 根本就是有问题的。
注意,props.email (每次点击 tab 时,传入的值) 和 this.props.email (初始化时的值)的判断永远进入不了,导致 state 一直更改不了。
input 的 value 一直沿用之前 currentEmail.email (tab1 输入时的值)。
解决方案
使用"完全"可控的组件
由于子组件 EmailInput 对于 Input 上事件触发,都是通过 state 状态自身管理。所以需要将 onChange 移出子组件,以及 value 对应的 state 取值,都放到父组件中控制,再通过数据属性 props 传递给子组件。
这样子组件就是个完全可控的组件了。
子组件,将 state 替换为 props:
父组件:
重新定义初始化的 state 状态对象:
当父组件触发响应方法后,更新 state :
通过属性定义,传入子组件:
页面 HTML 效果:
使用有 key 描述的可控组件
把代码恢复到之前的样子,只添加 key 属性,让每次渲染子组件时,都根据 key 新创建独立的子组件。
代码示例:
总结
根据两个不恰当的例子说明,我们基本了解对派生 state 的使用要及其注意。
对 static getDerivedStateFromProps 和 componentWillReceiveProps 两个生命周期中添加的逻辑要慎重!因为说不定会像上例中出现"重置的效果",也不要试图用自认为没问题的逻辑判断解决派生 state 相关问题。
另外区分好受控和非受控组件的含义。有必要时,不要怕麻烦,在父组件中定义好事件函数传给子组件;
也可以定义 key 属性,或者人为逻辑中增加独一的 ID 设计以区分。
閱讀更多 前端雨爸 的文章