快来加入我们吧!
"小和山的菜鸟们",为前端开发者提供技术相关资讯以及系列基础文章。为更好的用户体验,请您移至我们官网小和山的菜鸟们 ( https://xhs-rookies.com/ ) 进行学习,及时获取最新文章。
"Code tailor" ,如果您对我们文章感兴趣、或是想提一些建议,微信关注 “小和山的菜鸟们” 公众号,与我们取的联系,您也可以在微信上观看我们的文章。每一个建议或是赞同都是对我们极大的鼓励!
前言
这节我们将介绍 React
中受控组件和非受控组件的概念及其使用。
本文会向你介绍以下内容:
- 什么是受控组件/非受控组件
- 受控组件
- 非受控组件
什么是受控组件/非受控组件
在 HTML
中,表单元素如 <input>
,<textarea>
和 <select>
表单元素通常保持自己的状态,并根据用户输入进行更新。而在 React
中,可变状态一般保存在组件的 state(状态)
属性中,并且只能通过 setState()
更新。
我们可以通过使 React
的 state
成为 “单一数据源原则” 来结合这两个形式。然后渲染表单的 React 组件也可以控制在用户输入之后的行为。
这种形式,其值由 React
控制的输入表单元素称为“受控组件”。
那么相反的,值并不由 React
进行控制,该组件自己输入,减少等等,该元素成为非受控组件。
关于什么时候使用受控组件,什么时候使用非受控组件,可以查看这一篇文章:
受控组件
认识受控组件
默认提交表单方式
HTML
表单元素与 React
中的其他 DOM
元素有所不同,因为表单元素自然地保留了一些内部状态。
例如,这个纯 HTML
表单接受一个单独的 name
:
<form>
<label>
名字:
<input type="text" name="name" />
</label>
<input type="submit" value="提交" />
</form>
该表单和 HTML
表单的默认行为一致,当用户提交此表单时浏览器会打开一个新页面。如果你希望 React
中保持这个行为,也可以工作。
但是多数情况下,我们会让 React
组件来管理这些数据,并在点击提交这些数据并触发打开新页面的操作。
这就用到了“受控组件(controlled components
)”。
受控组件提交表单
在 HTML
中,表单元素(如<input>
、 <textarea>
和 <select>
)之类的表单元素通常自己维护 state,并根据用户输入进行更新。
而在 React
中,可变状态(mutable state
)通常保存在组件的 state 属性中,并且只能通过使用 setState()
来更新。
- 我们将两者结合起来,使
React
的state
成为“唯一数据源”; - 渲染表单的
React
组件还控制着用户输入过程中表单发生的操作; - 被
React
以这种方式控制取值的表单输入元素就叫做“受控组件”;
例如,如果我们想让前一个示例在提交时打印出名称,我们可以将表单写为受控组件:
class App extends PureComponent {
constructor(props) {
super(props)
this.state = {
username: '',
}
}
render() {
const { username } = this.state
return (
<div>
<form onSubmit={(e) => this.handleSubmit(e)}>
<label htmlFor="username">
用户名:
<input
type="text"
id="username"
onChange={(e) => this.handleUsernameChange(e)}
value={username}
/>
</label>
<input type="submit" value="提交" />
</form>
</div>
)
}
handleUsernameChange(event) {
this.setState({
username: event.target.value,
})
}
handleSubmit(event) {
console.log(this.state.username)
event.preventDefault()
}
}
由于在表单元素上设置了 value
属性,因此显示的值将始终为 this.state.value
,这使得 React 的 state 成为唯一数据源。
由于 handleUsernameChange
在每次按键时都会执行并更新 React
的 state
,因此显示的值将随着用户输入而更新。
非受控组件
Ref 的创建和使用
在 React
的开发模式中,通常情况下不需要、也不建议直接操作 DOM
原生,但是某些特殊的情况,确实需要获取到 DOM
进行某些操作:
- 管理焦点,文本选择或媒体播放。
- 触发强制动画。
- 集成第三方
DOM
库。
我们这里如果使用非受控组件就会有一个问题,如何获取该组件的数据,这里就可以用 Refs
来获取该组件,然后就可以获取到该组件的数据了。因此我们先简述 Refs
的内容。
注意:当然还有其他的方法可以获取组件内容,例如:变量提升到父组件统一管理、事件监听。
创建 ref 的方式
如何创建 refs
来获取对应的 DOM
呢?目前有三种方式:
- 方式一:传入字符串
使用时通过 this.refs.传入的字符串
格式获取对应的元素;
- 方式二:传入一个对象
对象是通过 React.createRef()
方式创建出来的;使用时获取到创建的对象其中有一个current
属性就是对应的元素;
- 方式三:传入一个函数
该函数会在 DOM
被挂载时进行回调,这个函数会传入一个 元素对象,我们可以自己保存;使用时,直接拿到之前保存的元素对象即可;
代码演练:
class App extends PureComponent {
constructor(props) {
super(props)
this.titleRef = createRef()
this.titleEl = null
}
render() {
return (
<div>
<h2 ref="title">String Ref</h2>
<h2 ref={this.titleRef}>Hello Create Ref</h2>
<h2 ref={(element) => (this.titleEl = element)}>Callback Ref</h2>
<button onClick={(e) => this.changeText()}>改变文本</button>
</div>
)
}
changeText() {
this.refs.title.innerHTML = '你好啊,小和山的菜鸟们'
this.titleRef.current.innerHTML = '你好啊,小和山的菜鸟们'
this.titleEl.innerHTML = '你好啊,小和山的菜鸟们'
}
}
Ref 节点的类型
ref
的值根据节点的类型而有所不同:
- 当
ref
属性用于HTML
元素时,构造函数中使用React.createRef()
创建的ref
接收底层DOM
元素作为其current
属性; - 当
ref
属性用于自定义class
组件时,ref
对象接收组件的挂载实例作为其current
属性; - 你不能在函数组件上使用
ref
属性,因为他们没有实例;
这里我们演示一下 ref
引用一个 class
组件对象:
class Counter extends PureComponent {
constructor(props) {
super(props)
this.state = {
counter: 0,
}
}
render() {
return (
<div>
<h2>当前计数: {this.state.counter}</h2>
<button onClick={(e) => this.increment()}>+1</button>
</div>
)
}
increment() {
this.setState({
counter: this.state.counter + 1,
})
}
}
class App extends PureComponent {
constructor(props) {
super(props)
this.counterRef = createRef()
}
render() {
return (
<div>
<Counter ref={this.counterRef} />
<button onClick={(e) => this.increment()}>app +1</button>
</div>
)
}
increment() {
this.counterRef.current.increment()
}
}
函数式组件是没有实例的,所以无法通过 ref
获取他们的实例,但是某些时候,我们可能想要获取函数式组件中的某个 DOM
元素,这个时候我们可以通过 React.forwardRef
,后面我们也会学习 hooks
中如何使用 ref
。
认识非受控组件
React
推荐大多数情况下使用受控组件来处理表单数据:
- 一个受控组件中,表单数据是由
React
组件来管理的; - 另一种替代方案是使用非受控组件,这时表单数据将交由
DOM
节点来处理;
如果要使用非受控组件中的数据,那么我们需要使用 Ref
来从 DOM
节点中获取表单数据。
我们来进行一个简单的演练:
- 使用
ref
来获取input
元素; - 在非受控组件中通常使用
defaultValue
来设置默认值;
class App extends PureComponent {
constructor(props) {
super(props)
this.usernameRef = createRef()
}
render() {
return (
<div>
<form onSubmit={(e) => this.handleSubmit(e)}>
<label htmlFor="">
用户:
<input defaultValue="username" type="text" name="username" ref={this.usernameRef} />
</label>
<input type="submit" value="提交" />
</form>
</div>
)
}
handleSubmit(event) {
event.preventDefault()
console.log(this.usernameRef.current.value)
}
}
同样,<input type="checkbox">
和 <input type="radio">
支持 defaultChecked
,<select>
和 <textarea>
支持 defaultValue
。
下节预告
本节我们学习了 React
中控组件和非受控组件的内容,在下一个章节我们将继续学习 React
中高阶组件以及组件补充的内容,敬请期待!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。