# 受控组件和非受控组件 `React`的受控组件与非受控组件的概念是相对于表单而言的,在`React`中表单元素通常会持有一下内部的`state`,因此它的工作方式与其他`HTML`元素不一样,而获取表单元素内部`state`的实现方式的不同,就产生了受控组件和非受控组件。 ## 受控组件 在`HTML`的表单元素中,它们通常自己维护一套`state`,并随着用户的输入自己进行`UI`上的更新,这种行为是不被我们程序所管控的,而如果将`React`里的`state`属性和表单元素的值建立依赖关系,再通过`onChange`事件与`setState()`结合更新`state`属性,就能达到控制用户输入过程中表单发生的操作,`React`以这种方式控制取值的表单输入元素就叫做受控组件。 在`React`中定义了一个`input`输入框的话,它并没有类似于`Vue`里`v-model`的这种双向绑定功能,也就是说我们并没有一个指令能够将数据和输入框结合起来,用户在输入框中输入内容,然后数据同步更新。 ``` class Input extends React.Component { render () { return } } ``` 用户在界面上的输入框输入内容时,它是自己维护了一个`state`,这个`state`并不是我们平常看见的`this.state`,而是每个表单元素上抽象的`state`,这样的话就能根据用户的输入自己进行`UI`上的更新,如果我们想要控制输入框的内容,而输入框的内容取决的是`input`中的`value`属性,那么我们可以在`this.state`中定义一个名为`username`的属性,并将`input`上的`value`指定为这个属性。 ``` class Input extends React.Component { constructor (props) { super(props); this.state = { username: "1" }; } render () { return } } ``` 但是这时候你会发现`input`的内容是只读的,因为`value`会被我们的`this.state.username`所控制,当用户输入新的内容时,`this.state.username`并不会自动更新,这样的话`input`内的内容也就不会变了,此时控制台通常会抛出一个`Warning`。 ``` Warning: You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultValue`. Otherwise, set either `onChange` or `readOnly`. ``` 您为表单字段提供了一个没有`onChange`处理程序的`value`属性,这将呈现只读字段,如果字段应该是可变的,请使用`defaultValue`,否则请设置`onChange`或`readOnly`。 这段`Warning`其实给出了对于这个问题的解决方案,我们只需要对组件的`onChange`事件来监听输入内容的改变并使用`setState`更新`this.state.username`即可,如此我们在当前组件中能够控制这个表单元素的值,这就是受控组件。 ``` class Input extends React.Component { constructor (props) { super(props); this.state = { username: "1" }; } render () { return ( <> this.setState({username: e.target.value})} /> ) } } ``` 此外需要注意的是,如果是讲此组件作为一个共用的组件用以调用的话,是有弊端的,尽管此时`Input`组件本身是一个受控组件,但与之相对的调用方失去了更改`Input`组件值的控制权,所以对调用方而言,`Input`组件是一个非受控组件,以非受控组件的使用方式去调用受控组件是一种反模式,下边的例子都是属于`Hooks`的写法。 ``` // 组件提供方 function Input({ defaultValue }) { const [value, setValue] = React.useState(defaultValue) return setValue(e.target.value)} /> } // 调用方 function UseInput() { return } ``` 如果要对于组件提供方还是调用方`Input`组件都为受控组件,只需要提供方让出控制权即可。 ``` // 组件提供方 function Input({ value, onChange }) { return } // 调用方 function UseInput() { const [value, setValue] = React.useState(1); return setValue(e.target.value)} /> } ``` ## 非受控组件 如果表单元素并不经过`state`,而是通过`ref`修改或者直接操作`DOM`,那么它的数据无法通过`state`控制,这就是非受控组件。 ``` class Input extends React.Component { constructor (props) { super(props); this.input = React.createRef(); } render () { return ( <> ) } } ``` ## 总结 ### 受控组件 * 每当表单的状态发生变化时,都会被写入到组件的`state`中。 * 在受控组件中,组件渲染出的状态与它的`value`或`checked prop`相对应。 * `react`受控组件更新`state`的流程: * 通过在初始`state`中设置表单的默认值。 * 每当表单的值发生变化时,调用`onChange`事件处理器。 * 事件处理器通过合成对象`event`拿到改变后的状态,并更新应用的`state`。 * `SetState`触发视图的重新渲染,完成表单组件值的更新。 ### 非受控组件 * 如果一个表单组件没有`value prop`就可以称为非受控组件。 * 非受控组件是一种反模式,它的值不受组件自身的`state`或`props`控制。 * 通常需要为其添加`ref prop`来访问渲染后的底层`DOM`元素。 * 可通过添加`defaultValue`指定`value`值。 ## 每日一题 ``` https://github.com/WindrunnerMax/EveryDay ``` ## 参考 ``` https://muyunyun.cn/posts/8bdf2cdf/ https://zhuanlan.zhihu.com/p/89223413 https://juejin.cn/post/6844904154133954568 https://juejin.cn/post/6858276396968951822 https://segmentfault.com/a/1190000022925043 https://segmentfault.com/a/1190000012458996 https://zh-hans.reactjs.org/docs/glossary.html ```