![React进阶之路](https://wfqqreader-1252317822.image.myqcloud.com/cover/752/26793752/b_26793752.jpg)
2.6 表单
在有交互的Web应用中,表单是必不可少的。但是,和其他元素相比,表单元素在React中的工作方式存在一些不同。像div、p、span等非表单元素只需根据组件的属性或状态进行渲染即可,但表单元素自身维护一些状态,而这些状态默认情况下是不受React控制的。例如,input元素会根据用户的输入自动改变显示的内容,而不是从组件的状态中获取显示的内容。我们称这类状态不受React控制的表单元素为非受控组件。在React中,状态的修改必须通过组件的state,非受控组件的行为显然有悖于这一原则。为了让表单元素状态的变更也能通过组件的state管理,React采用受控组件的技术达到这一目的。
2.6.1 受控组件
如果一个表单元素的值是由React来管理的,那么它就是一个受控组件。React组件渲染表单元素,并在用户和表单元素发生交互时控制表单元素的行为,从而保证组件的state成为界面上所有元素状态的唯一来源。对于不同的表单元素,React的控制方式略有不同,下面我们就来看一下三类常用表单元素的控制方式。
1.文本框
文本框包含类型为text的input元素和textarea元素。它们受控的主要原理是,通过表单元素的value属性设置表单元素的值,通过表单元素的onChange事件监听值的变化,并将变化同步到React组件的state中。下面是一个例子。
![](https://epubservercos.yuewen.com/700037/15253388305240606/epubprivate/OEBPS/Images/Figure-T57_3361.jpg?sign=1739375789-VOAUxGBoDgo7D2aBc1wVUnLOV7xYgCPb-0-4e18929c144ad4fcf75c7f5af4ee92f7)
用户名和密码两个表单元素的值是从组件的state中获取的,当用户更改表单元素的值时,onChange事件会被触发,对应的handleChange处理函数会把变化同步到组件的state,新的state又会触发表单元素重新渲染,从而实现对表单元素状态的控制。
这个例子还包含一个处理多个表单元素的技巧:通过为两个input元素分别指定name属性,使用同一个函数handleChange处理元素值的变化,在处理函数中根据元素的name属性区分事件的来源。这样的写法显然比为每一个input元素指定一个处理函数简洁得多。
textarea的使用方式和input几乎一致,这里不再赘述。
2.列表
列表select元素是最复杂的表单元素,它可以用来创建一个下拉列表:
![](https://epubservercos.yuewen.com/700037/15253388305240606/epubprivate/OEBPS/Images/Figure-T58_3368.jpg?sign=1739375789-oaBqXRd4OFMIRyRoPP8Ed5zUQhQHva72-0-3077775332fa8f3360e074b4a672a5da)
通过指定selected属性可以定义哪一个选项(option)处于选中状态,所以上面的例子中,Mobx这一选项是列表的初始值,处于选中状态。在React中,对select的处理方式有所不同,它通过在select上定义value属性来决定哪一个option元素处于选中状态。这样,对select的控制只需要在select这一个元素上修改即可,而不需要关注option元素。下面是一个例子:
![](https://epubservercos.yuewen.com/700037/15253388305240606/epubprivate/OEBPS/Images/Figure-T58_3370.jpg?sign=1739375789-7uldnWDgvJsvbyVkLYdImBNo7pQk2ocx-0-4bc27f6f749f4aa45a665340b76ee718)
3.复选框和单选框
复选框是类型为checkbox的input元素,单选框是类型为radio的input元素,它们的受控方式不同于类型为text的input元素。通常,复选框和单选框的值是不变的,需要改变的是它们的checked状态,因此React控制的属性不再是value属性,而是checked属性。例如:
![](https://epubservercos.yuewen.com/700037/15253388305240606/epubprivate/OEBPS/Images/Figure-T59_2690.jpg?sign=1739375789-wgqY3sBax1SgS2f4TgLRc796QMjyhfkc-0-6a620fe808a070347e5a39c955a59201)
![](https://epubservercos.yuewen.com/700037/15253388305240606/epubprivate/OEBPS/Images/Figure-T60_3373.jpg?sign=1739375789-WLeeeWoQem1mTJ16j8JppyJPmm6waq1y-0-bce00b266104811abf2a62e9819867d1)
上面的例子中,input的value是不变的,onChange事件改变的是input的checked属性。单选框的用法和复选框相似,读者可自行尝试使用。
下面为BBS项目添加表单元素,让每一个帖子的标题支持编辑功能。本节项目源代码的目录为/chapter-02/bbs-components-form。修改后的PostItem如下:
![](https://epubservercos.yuewen.com/700037/15253388305240606/epubprivate/OEBPS/Images/Figure-T61_3374.jpg?sign=1739375789-dflF7bolrdiVhhBFksqEo7tvC1Vq0dLW-0-8bb368f640ed81863b7716b15b500b51)
![](https://epubservercos.yuewen.com/700037/15253388305240606/epubprivate/OEBPS/Images/Figure-T62_3375.jpg?sign=1739375789-WixxpcDAuVI2otMzYyAWhYZOf1od1Kzg-0-4ccd45d301e7e32fdb5a2965c2282477)
![](https://epubservercos.yuewen.com/700037/15253388305240606/epubprivate/OEBPS/Images/Figure-T63_3376.jpg?sign=1739375789-B1B6HSHSrrF9r9i25ZsH77PljPTVysE1-0-36b483d8ad1e6fcbd15a92cf3bece536)
当点击编辑状态的button时,帖子的标题会使用textarea展示,此时标题处于可编辑状态,当再次点击button时,会执行保存操作,PostItem通过onSave属性调用父组件PostList的handleSave方法,将更新后的Post(标题和时间)保存到PostList的state中。PostList中的修改如下:
![](https://epubservercos.yuewen.com/700037/15253388305240606/epubprivate/OEBPS/Images/Figure-T63_3377.jpg?sign=1739375789-D3Wq6kktk1m39LLKdB9AHFyB6G5i3cA6-0-b9e23ad0245b6b3e58c945fbf48a5764)
2.6.2 非受控组件
使用受控组件虽然保证了表单元素的状态也由React统一管理,但需要为每个表单元素定义onChange事件的处理函数,然后把表单状态的更改同步到React组件的state,这一过程是比较烦琐的,一种可替代的解决方案是使用非受控组件。非受控组件指表单元素的状态依然由表单元素自己管理,而不是交给React组件管理。使用非受控组件需要有一种方式可以获取到表单元素的值,React中提供了一个特殊的属性ref,用来引用React组件或DOM元素的实例,因此我们可以通过为表单元素定义ref属性获取元素的值。例如:
![](https://epubservercos.yuewen.com/700037/15253388305240606/epubprivate/OEBPS/Images/Figure-T64_3378.jpg?sign=1739375789-aOT0WPM4gxKOsFMVdEHIip1Kl4hST3zj-0-ae8401129c7199fce7b5a923866a73c7)
ref的值是一个函数,这个函数会接收当前元素作为参数,即例子中的input参数指向的是当前元素。在函数中,我们把input赋值给了this.input,进而可以在组件的其他地方通过this.input获取这个元素。
在使用非受控组件时,我们常常需要为相应的表单元素设置默认值,但是无法通过表单元素的value属性设置,因为非受控组件中,React无法控制表单元素的value属性,这也就意味着一旦在非受控组件中定义了value属性的值,就很难保证后续表单元素的值的正确性。这种情况下,我们可以使用defaultValue属性指定默认值:
![](https://epubservercos.yuewen.com/700037/15253388305240606/epubprivate/OEBPS/Images/Figure-T65_3379.jpg?sign=1739375789-e9NxZxaSSU4LhwaaOi2iUw8i7vUcigW7-0-41060b9d8d663f67725af9de1f4daa3a)
上面的例子,defaultValue设置的默认值为something,而后续值的更改则由自己控制。类似地,select元素和textarea元素也支持通过defaultValue设置默认值,<input type="checkbox">和<input type="radio">则支持通过defaultChecked属性设置默认值。
非受控组件看似简化了操作表单元素的过程,但这种方式破坏了React对组件状态管理的一致性,往往容易出现不容易排查的问题,因此非特殊情况下,不建议大家使用。