You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
classFormextendsComponent{handleSubmitClick=()=>{constname=this._name.value;// do something with `name`}render(){return(<div><inputtype="text"defaultValue="Bob"ref={input=>this._name=input}/><buttononClick={this.handleSubmitClick}>Sign up</button></div>);}}
该文章包含如下内容
前言
在HTML中,表单元素(
<input>
/<textarea>
/<select>
),通常自己会维护state,并根据用户的输入进行更新在这个HTML中,我们可以在input中随意的输入值,如果我们需要获取到当前input所输入的内容,应该怎么做呢?
受控与非受控组件
非受控组件(uncontrolled component)
使用非受控组件,不是为每个状态更新编写数据处理函数,而是将表单数据交给DOM节点来处理,可以使用Ref来获取数据
在非受控组件中,希望能够赋予表单一个初始值,但是不去控制后续的更新。可以采用
defaultValue
指定一个默认值受控组件(controlled component)
在React中,可变状态(mutable state)通常保存在组件的state属性中,并且只能够通过
setState
来更新在上述的代码中,在input设置了value属性值,因此显示的值始终为
this.state.value
,这使得state成为了唯一的数据源。如果我们在上面的示例中写入
handleChange
方法,那么每次按键都会执行该方法并且更新React的state,因此表单的值将随着用户的输入而改变React组件控制着用户输入过程中表单发生的操作并且state还是唯一数据源,被React以这种方式控制取值的表单输入元素叫做受控组件
受控和非受控组件边界
非受控组件
Input组件只接收一个
defaultValue
默认值,调用Input组件的时候,只需要通过props传递一个defaultValue
即可受控组件
数值的展示和变更需要由
state
和setState
,组件内部控制state,并实现自己的onChange方法请问这时Input组件是受控还是非受控?如果我们采用之前的写法更改这个组件以及其调用
此时的Input组件本身是一个受控组件,它是由唯一的state数据驱动的。但是对于Demo来说,我们并没有Input组件的一个数据变更权利,那么对于Demo组件来说,Input组件就是一个非受控组件。(
‼️以非受控组件的方式去调用受控组件是一种反模式
)如何修改当前的Input和Demo组件代码,才能够使得Input组件本身也是一个受控组件,并且对于Demo组件来说它也是受控的讷?
反模式-以非受控组件的方式去调用受控组件
虽然受控和非受控通常用来指向表单的inputs,也能用来描述数据频繁更新的组件。
通过上一节受控与非受控组件的边界划分,我们可以简单的分类为:
简单来说,如果一个组件的state中的某个数据来自外部,就将该数据称之为派生状态。
大部分使用派生state导致的问题,不外乎两个原因:
直接复制prop到state
getDerivedStateFromProps
和componentWillReceiveProps
的执行时期在父级重新渲染时,不管props是否有变化,这两个生命周期都会执行
所以在两个方法里面直接复制props到state是不安全的,会导致state没有正确渲染
点击查看示例
给input设置props传来的初始值,在input输入时它会修改state。但是如果父组件重新渲染时,输入框input的值就会丢失,变成props的默认值
即使我们在重置前比较
nextProps.email!==this.state.email
仍然会导致更新针对于目前这个小demo来说,可以使用
shouldComponentUpdate
来比较props中的email是否修改再来决定是否需要重新渲染。但是对于实际应用来说,这种处理方式并不可行,一个组件会接收多个prop,任何一个prop的改变都会导致重新渲染和不正确的状态重置。加上行内函数和对象 prop,创建一个完全可靠的shouldComponentUpdate
会变得越来越难。shouldComponentUpdate
这个生命周期更多是用于性能优化,而不是处理派生state。截止这里,讲清为什么不能直接复制prop到state。思考另一个问题,如果只使用props中的email属性更新组件讷?
在props变化后修改state
接着上述示例,只使用
props.email
来更新组件,这样可以防止修改state导致的bug通过这个改造,组件只有在props.email改变时才会重新给state赋值,那这样改造会有问题吗?
在下列场景中,对拥有两个相同email的账号进行切换的时,这个输入框不会重置,因为父组件传来的prop值没有任何变化
点击查看示例
这个场景是构建出来的,可能设计奇怪,但是这样子的错误很常见。对于这种反模式来说,有两种方案可以解决这些问题。关键在于,任何数据,都要保证只有一个数据来源,而且避免直接复制它。
解决方案
完全可控的组件
从EmailInput组件中删除state,直接使用props来获取值,将受控组件的控制权交给父组件。
如果想要保存临时的值,需要父组件手动执行保存。
有key的非受控组件
让组件存储临时的email state,email的初始值仍然是通过prop来接受的,但是更改之后的值就和prop没有关系了
在之前的切换账号的示例中,为了在不同页面切换不同的值,可以使用
key
这个React特殊属性。当key变化时,React会创建一个新的组件而不是简单的更新存在的组件(获取更多)。我们经常使用在渲染动态列表时使用key值,这里也可以使用。点击查看示例
每次id改变的时候,都会重新创建
EmailInput
,并将其状态重置为最近email值。可选方案
使用key属性来做,会使组件整个组件的state都重置。可以在
getDerivedStateFromProps
和componentWillReceiveProps
来观察id的变化,麻烦但是可行点击查看示例
使用实例方法重置非受控组件
刚刚两种方式,均是再有唯一标识值的情况下。如果在没有合适的
key
值时,也想要重新创建组件。第一种方案就是生成随机值或者递增的值当作key
值,另一种就是使用示例方法强制重置内部状态父组件使用ref调用这个方法,点击查看示例
那我们如何选?
在我们的业务开发中,尽量选择受控组件,减少使用派生state,过量的使用
componentWillReceiveProps
可能导致props判断不够完善,倒是重复渲染死循环问题。在组件库开发中,例如antd,将受控与非受控的调用方式都开放给用户,让用户自主选择对应的调用方式。比如Form组件,我们常使用
getFieldDecorator
和initialValue
来定义表单项,但是我们根本不关心中间的输入过程,在最后提交的时候通过getFieldsValue
或者validateFields
拿到所有的表单值,这就是非受控的调用方式。或者是,我们在只有一个Input的时候,我们可以直接绑定value和onChange事件,这也就是受控的方式调用。总结
在本文中,首先介绍了非受控组件和受控组件的概念。对于受控组件来说,组件控制用户输入的过程以及state是受控组件唯一的数据来源。
接着介绍了组件的调用问题,对于组件调用方而言,组件提供方是否为受控组件。对于调用方而言,组件受控以及非受控的边界划分取决于当前组件对于子组件值的变更是否拥有控制权。
接着介绍了以非受控组件的方式调用受控组件这种反模式用法,以及相关示例。不要直接复制props到state,而是使用受控组件。对于不受控的组件,当你想在prop变化时重置state的话,可以选择以下几种方式:
key
属性,重置内部所有的初始stateref
调用实例方法最后总结了一下,应当如何选择受控组件还是非受控组件。
The text was updated successfully, but these errors were encountered: