React 基础:派生 state 的“错误使用”示例


React 基础:派生 state 的“错误使用”示例


前言

近年前端技术层出不穷,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 展示:

React 基础:派生 state 的“错误使用”示例


父组件 EmailReset:

React 基础:派生 state 的“错误使用”示例


注意:

在 componentDidMount 生命周期中,定义并执行定时器 timer;

并对子组件 EmailInput 设置属性 email(此 email 和 state 并没有关系,此处还是个"死值")。


子组件 EmailInput:

React 基础:派生 state 的“错误使用”示例


一个典型 React 受控组件例子,Input 输出后触发事件方法,更新 state 对象重新渲染后回显到页面上。


问题:

看似没有问题,但当用户对 Input 输入数据后,会被初始化成原始值。


页面 HTML 效果:

React 基础:派生 state 的“错误使用”示例


结合浏览器控制台调试信息,和页面 HTML Input 输入框所示,我们看到三种现象:

1. 每次父组件定时器执行,更新 state 对象,触发 render 方法

2. 之后,每次还会触发子组件的生命周期方法:static getDerivedStateFromProps componentWillReceiveProps(现在不建议使用)

3. 页面 HTML 的 Input 输入框值被还原


那怎么解决?

代码示例:

React 基础:派生 state 的“错误使用”示例


尝试更改逻辑设计,增加如上判断。这样每次父组件 state 状态的更新,虽然还会触发子组件的生命周期 componentWillReceiveProps 方法,但由于这样的判断,就不会重置 email 数据状态。


好像展示解决的这个 bug,但稍有疏忽还会引发其他问题。


在 props 变化后修改 state

场景说明:

针对上面那个解决方案,再引申举个反例。


比如,一个页面 HTML 中有多个 tab 标签,每个 tab 对应不同的 Input 输入框初始数据。


代码示例:

父组件 EmailFormTab:

React 基础:派生 state 的“错误使用”示例


定义父组件的 tab 栏,以及点击事件 onChooseTab:

React 基础:派生 state 的“错误使用”示例


逻辑这样:点击不同 tab,触发对应事件,获取点击 tab 索引,更新对应 state 状态;从而每次渲染后,currentEmail 为 emailList 对应的 email 值。


子组件同上。


问题:

看似也没什么问题,但注意前两个 tab 对应的 email 数据相同时,每当在 tab1 下,对 input 输入新值,再点击 tab2 ,会使得 input 变为前面输入的值。

这肯定不是我们设计上的原意(虽然公用一个 input,逻辑上每次切换 tab 时,希望他们保持独立)。


页面 HTML 效果:

React 基础:派生 state 的“错误使用”示例


原因就是 static getDerivedStateFromProps or componentWillReceiveProp 根本就是有问题的。


React 基础:派生 state 的“错误使用”示例


注意,props.email (每次点击 tab 时,传入的值) 和 this.props.email (初始化时的值)的判断永远进入不了,导致 state 一直更改不了。

input 的 value 一直沿用之前 currentEmail.email (tab1 输入时的值)。


解决方案

使用"完全"可控的组件

由于子组件 EmailInput 对于 Input 上事件触发,都是通过 state 状态自身管理。所以需要将 onChange 移出子组件,以及 value 对应的 state 取值,都放到父组件中控制,再通过数据属性 props 传递给子组件。

这样子组件就是个完全可控的组件了。

子组件,将 state 替换为 props:

React 基础:派生 state 的“错误使用”示例

父组件:

重新定义初始化的 state 状态对象:

React 基础:派生 state 的“错误使用”示例

当父组件触发响应方法后,更新 state :

React 基础:派生 state 的“错误使用”示例

通过属性定义,传入子组件:

React 基础:派生 state 的“错误使用”示例

页面 HTML 效果:

React 基础:派生 state 的“错误使用”示例


使用有 key 描述的可控组件

把代码恢复到之前的样子,只添加 key 属性,让每次渲染子组件时,都根据 key 新创建独立的子组件。

代码示例:

React 基础:派生 state 的“错误使用”示例


总结

根据两个不恰当的例子说明,我们基本了解对派生 state 的使用要及其注意。

static getDerivedStateFromProps componentWillReceiveProps 两个生命周期中添加的逻辑要慎重!因为说不定会像上例中出现"重置的效果",也不要试图用自认为没问题的逻辑判断解决派生 state 相关问题。

另外区分好受控和非受控组件的含义。有必要时,不要怕麻烦,在父组件中定义好事件函数传给子组件;

也可以定义 key 属性,或者人为逻辑中增加独一的 ID 设计以区分。


分享到:


相關文章: