受控組件和非受控組件
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 <input name="username" />
}
}
用戶在界面上的輸入框輸入內容時,它是自己維護了一個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 name="username" value={this.state.username} />
}
}
但是這時候你會發現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 (
<>
<input name="username" value={this.state.username}
onChange={e => this.setState({username: e.target.value})}
/>
<button onClick={() => console.log(this.state.username)} >Log</button>
</>
)
}
}
此外需要注意的是,如果是講此組件作為一個共用的組件用以調用的話,是有弊端的,盡管此時Input
組件本身是一個受控組件,但與之相對的調用方失去了更改Input
組件值的控制權,所以對調用方而言,Input
組件是一個非受控組件,以非受控組件的使用方式去調用受控組件是一種反模式,下邊的例子都是屬於Hooks
的寫法。
// 組件提供方
function Input({ defaultValue }) {
const [value, setValue] = React.useState(defaultValue)
return <input value={value} onChange={e => setValue(e.target.value)} />
}
// 調用方
function UseInput() {
return <Input defaultValue={1} />
}
如果要對於組件提供方還是調用方Input
組件都為受控組件,只需要提供方讓出控制權即可。
// 組件提供方
function Input({ value, onChange }) {
return <input value={value} onChange={onChange} />
}
// 調用方
function UseInput() {
const [value, setValue] = React.useState(1);
return <Input value={value} onChange={e => setValue(e.target.value)} />
}
非受控組件
如果表單元素並不經過state
,而是通過ref
修改或者直接操作DOM
,那么它的數據無法通過state
控制,這就是非受控組件。
class Input extends React.Component {
constructor (props) {
super(props);
this.input = React.createRef();
}
render () {
return (
<>
<input name="username" ref={this.input} />
<button onClick={() => console.log(this.input.current.value)} >Log</button>
</>
)
}
}
總結
受控組件
- 每當表單的狀態發生變化時,都會被寫入到組件的
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