重新认识受控和非受控组件
該文章包含如下內(nèi)容
- 受控與非受控組件非受控組件受控組件
- 受控和非受控組件邊界
- 反模式
- 解決方案
前言
在 HTML 中,表單元素(<input>/<textarea>/<select>),通常自己會(huì)維護(hù) state,并根據(jù)用戶的輸入進(jìn)行更新
<form><label>名字:<input type="text" name="name" /></label><input type="submit" value="提交" /> </form>在這個(gè) HTML 中,我們可以在 input 隨意地輸入值,如果我們需要獲取到當(dāng)前 input 所輸入的內(nèi)容,應(yīng)該怎么做呢?
受控與非受控組件
非受控組件(uncontrolled component)
使用非受控組件,不是為每個(gè)狀態(tài)更新編寫(xiě)數(shù)據(jù)處理函數(shù),而是將表單數(shù)據(jù)交給系統(tǒng) DOM 節(jié)點(diǎn)來(lái)處理,可以使用 Ref 來(lái)獲取數(shù)據(jù)
 在非受控組件中,希望能夠賦予表單一個(gè)初始值,但是不去控制后續(xù)的更新。可以采用defaultValue指定一個(gè)默認(rèn)值
受控組件(controlled component)
在 React 中,可變狀態(tài)(mutable state)通常保存在組件里的 state 屬性中,并且只能夠通過(guò)setState 來(lái)更新
class NameForm extends React.Component {constructor(props) {super(props);this.state = {value: 'shuangxu'};}render() {return (<form onSubmit={this.handleSubmit}><label>名字:<input type="text" value={this.state.value}/></label><input type="submit" value="提交" /></form>);} }在上述的代碼中,在 Input 設(shè)置了 value 屬性值,因此顯示的值始終為this.state.value,這使得 state 成為了唯一的數(shù)據(jù)源。
const handleChange = (event) => {this.setState({ value: event.target.value }) }<input type="text" value={this.state.value} onChange={this.handleChange}/>如果我們?cè)谏厦娴氖纠袑?xiě)入handleChange 方法,那么每次按鍵都會(huì)執(zhí)行該方法并且更新 React 的 state,因此表單的值將隨著用戶的輸入而改變
React 組件控制著用戶輸入過(guò)程中表單發(fā)生的操作并且 state 還是唯一數(shù)據(jù)源,被 React 以這種方式控制取值的表單輸入元素叫做受控組件
受控和非受控組件邊界
非受控組件
Input 組件只接收一個(gè)defaultValue默認(rèn)值,調(diào)用 Input 組件的時(shí)候,只需要通過(guò) props 傳遞一個(gè)defaultValue 即可
//組件 function Input({defaultValue}){return <input defaultValue={defaultValue} /> }//調(diào)用 function Demo(){return <Input defaultValue='shuangxu' /> }受控組件
數(shù)值的展示和變更需要由state和setState,組件內(nèi)部控制 state,并實(shí)現(xiàn)自己的 onChange 方法
//組件 function Input() {const [value, setValue] = useState('shuangxu')return <input value={value} onChange={e=>setValue(e.target.value)} />; }//調(diào)用 function Demo() {return <Input />; }請(qǐng)問(wèn)這時(shí) Input 組件是受控還是非受控?如果我們采用之前的寫(xiě)法更改這個(gè)組件以及其調(diào)用
//組件 function Input({defaultValue}) {const [value, setValue] = useState(defaultValue)return <input value={value} onChange={e=>setValue(e.target.value)} />; }//調(diào)用 function Demo() {return <Input defaultValue='shuangxu' />; }此時(shí)的 Input 組件本身是一個(gè)受控組件,它是由唯一的 state 數(shù)據(jù)驅(qū)動(dòng)的。但是對(duì)于 Demo 來(lái)說(shuō),我們并沒(méi)有 Input 組件的一個(gè)數(shù)據(jù)變更權(quán)利,那么對(duì)于 Demo 組件來(lái)說(shuō),Input 組件就是一個(gè)非受控組件。(??以非受控組件的方式去調(diào)用受控組件是一種反模式)
如何修改當(dāng)前的 Input 和 Demo 組件代碼,才能夠使得 Input 組件本身也是一個(gè)受控組件,并且對(duì)于 Demo 組件來(lái)說(shuō)它也是受控的訥?
function Input({value, onChange}){return <input value={value} onChange={onChange} }function Demo(){const [value, setValue] = useState('shuangxu')return <Input value={value} onChange={e => setValue(e.target.value)} />反模式-以非受控組件的方式去調(diào)用受控組件
雖然受控和非受控通常用來(lái)指向表單的 inputs,也能用來(lái)描述數(shù)據(jù)頻繁更新的組件。
 通過(guò)上一節(jié)受控與非受控組件的邊界劃分,我們可以簡(jiǎn)單地分類為:
- 如果使用 props 傳入數(shù)據(jù),有對(duì)應(yīng)的數(shù)據(jù)處理方法,組件對(duì)于父級(jí)來(lái)說(shuō)認(rèn)為是可控的
- 數(shù)據(jù)只是保存在組件內(nèi)部的 state 中,組件對(duì)于父級(jí)來(lái)說(shuō)是非常受控的
??什么是派生 state
簡(jiǎn)單來(lái)說(shuō),如果一個(gè)組件的 state 中的某個(gè)數(shù)據(jù)來(lái)自外部,就將該數(shù)據(jù)稱之為派生狀態(tài)。
大部分使用派生 state 導(dǎo)致的問(wèn)題,不外乎兩個(gè)原因:
- 直接復(fù)制 props 到 state
- 如果 props 和 state 不一致就更新 state
直接復(fù)制 prop 到 state
??getDerivedStateFromProps和componentWillReceiveProps的執(zhí)行時(shí)期
- 在父級(jí)重新渲染時(shí),不管 props 是否有變化,這兩個(gè)生命周期都會(huì)執(zhí)行
- 所以在兩個(gè)方法里面直接復(fù)制 props 到 state 是不安全的,會(huì)導(dǎo)致 state 沒(méi)有正確渲染
點(diǎn)擊查看示例
給 Input 設(shè)置 props 傳來(lái)的初始值,在 Input 輸入時(shí)它會(huì)修改 state。但是如果父組件重新渲染時(shí),輸入框 Input 的值就會(huì)丟失,變成 props 的默認(rèn)值
即使我們?cè)谥刂们氨容^nextProps.email!==this.state.email仍然會(huì)導(dǎo)致更新
針對(duì)于目前這個(gè)小 demo 來(lái)說(shuō),可以使用shouldComponentUpdate來(lái)比較 props 中的 email 是否修改再來(lái)決定是否需要重新渲染。但是對(duì)于實(shí)際應(yīng)用來(lái)說(shuō),這種處理方式并不可行,一個(gè)組件會(huì)接收多個(gè) prop,任何一個(gè) prop 的改變都會(huì)導(dǎo)致重新渲染和不正確的狀態(tài)重置。加上行內(nèi)函數(shù)和對(duì)象 prop,創(chuàng)建一個(gè)完全可靠的shouldComponentUpdate會(huì)變得越來(lái)越難。shouldComponentUpdate這個(gè)生命周期更多是用于性能優(yōu)化,而不是處理派生 state。
 截止這里,講清為什么不能直接復(fù)制 prop 到 state。思考另一個(gè)問(wèn)題,如果只使用 props 中的 email 屬性更新組件訥?
在 props 變化后修改 state
接著上述示例,只使用props.email來(lái)更新組件,這樣可以防止修改 state 導(dǎo)致的 bug
class EmailInput extends React.Component {constructor(props) {super(props);this.state = {email: this.props.email //初始值為props中email};}componentWillReceiveProps(nextProps) {if(nextProps.email !== this.props.email){this.setState({ email: nextProps.email }); //email改變時(shí),重新給state賦值}}//... }通過(guò)這個(gè)改造,組件只有在 props.email 改變時(shí)才會(huì)重新給 state 賦值,那這樣改造會(huì)有問(wèn)題嗎?
在下列場(chǎng)景中,對(duì)擁有兩個(gè)相同 email 的賬號(hào)進(jìn)行切換的時(shí),這個(gè)輸入框不會(huì)重置,因?yàn)楦附M件傳來(lái)的 prop 值沒(méi)有任何變化
 點(diǎn)擊查看示例
 這個(gè)場(chǎng)景是構(gòu)建出來(lái)的,可能設(shè)計(jì)奇怪,但是這樣子的錯(cuò)誤很常見(jiàn)。對(duì)于這種反模式來(lái)說(shuō),有兩種方案可以解決這些問(wèn)題。關(guān)鍵在于,任何數(shù)據(jù),都要保證只有一個(gè)數(shù)據(jù)來(lái)源,而且避免直接復(fù)制它。
解決方案
完全可控的組件
從 EmailInput 組件中刪除 state,直接使用 props 來(lái)獲取值,將受控組件的控制權(quán)交給父組件。
function EmailInput(props){return <input onChange={props.onChange} value={props.email}/> }如果想要保存臨時(shí)的值,需要父組件手動(dòng)執(zhí)行保存。
有 key 的非受控組件
讓組件存儲(chǔ)臨時(shí)的 email state,email 的初始值仍然是通過(guò) prop 來(lái)接受的,但是更改之后的值就和 prop 沒(méi)有關(guān)系了
function EmailInput(props){const [email, setEmail] = useState(props.email)return <input value={email} onChange={(e) => setEmail(e.target.value)}/> }在之前的切換賬號(hào)的示例中,為了在不同頁(yè)面切換不同的值,可以使用key這個(gè) React 特殊屬性。當(dāng) key 變化時(shí),React 會(huì)創(chuàng)建一個(gè)新的組件而不是簡(jiǎn)單的更新存在的組件(獲取更多)。我們經(jīng)常使用在渲染動(dòng)態(tài)列表時(shí)使用 key 值,這里也可以使用。
<EmailInputemail={account.email}key={account.id} />點(diǎn)擊查看示例
每次 id 改變的時(shí)候,都會(huì)重新創(chuàng)建EmailInput,并將其狀態(tài)重置為最近 email 值。
可選方案
點(diǎn)擊查看示例
剛剛兩種方式,均是再有唯一標(biāo)識(shí)值的情況下。如果在沒(méi)有合適的key值時(shí),也想要重新創(chuàng)建組件。第一種方案就是生成隨機(jī)值或者遞增的值當(dāng)作key值,另一種就是使用示例方法強(qiáng)制重置內(nèi)部狀態(tài)父組件使用ref調(diào)用這個(gè)方法,點(diǎn)擊查看示例
那我們?nèi)绾芜x
在我們的業(yè)務(wù)開(kāi)發(fā)中,盡量選擇受控組件,減少使用派生 state,過(guò)量的使用 componentWillReceiveProps 可能導(dǎo)致 props 判斷不夠完善,倒是重復(fù)渲染死循環(huán)問(wèn)題。
在組件庫(kù)開(kāi)發(fā)中,例如 Ant Design,將受控與非受控的調(diào)用方式都開(kāi)放給用戶,讓用戶自主選擇對(duì)應(yīng)的調(diào)用方式。比如 Form 組件,我們常使用 getFieldDecorator 和 initialValue 來(lái)定義表單項(xiàng),但是我們根本不關(guān)心中間的輸入過(guò)程,在最后提交的時(shí)候通過(guò) getFieldsValue 或者 validateFields 拿到所有的表單值,這就是非受控的調(diào)用方式。或者是,我們?cè)谥挥幸粋€(gè) Input 的時(shí)候,我們可以直接綁定 value 和 onChange 事件,這也就是受控的方式調(diào)用。
總結(jié)
在本文中,首先介紹了非受控組件和受控組件的概念。對(duì)于受控組件來(lái)說(shuō),組件控制用戶輸入的過(guò)程以及 state 是受控組件唯一的數(shù)據(jù)來(lái)源。
接著介紹了組件的調(diào)用問(wèn)題,對(duì)于組件調(diào)用方而言,組件提供方是否為受控組件。對(duì)于調(diào)用方而言,組件受控以及非受控的邊界劃分取決于當(dāng)前組件對(duì)于子組件值的變更是否擁有控制權(quán)。
接著介紹了以非受控組件的方式調(diào)用受控組件這種反模式用法,以及相關(guān)示例。不要直接復(fù)制 props 到 state,而是使用受控組件。對(duì)于不受控的組件,當(dāng)你想在 prop 變化時(shí)重置 state 的話,可以選擇以下幾種方式:
- 建議: 使用key屬性,重置內(nèi)部所有的初始 state
- 選項(xiàng)一:僅更改某些字段,觀察特殊屬性的變化(具有唯一性的屬性)
- 選項(xiàng)二:使用 ref 調(diào)用實(shí)例方法
最后總結(jié)了一下,應(yīng)當(dāng)如何選擇受控組件還是非受控組件。
如果本文對(duì)你有幫助,別忘記給我個(gè)3連 ,點(diǎn)贊,轉(zhuǎn)發(fā),評(píng)論,,咱們下期見(jiàn)。
收藏 等于白嫖,點(diǎn)贊才是真情。
親愛(ài)的小伙伴們,有需要JAVA面試文檔資料請(qǐng)點(diǎn)贊+轉(zhuǎn)發(fā),關(guān)注我后,私信我333就可以領(lǐng)取免費(fèi)資料哦
總結(jié)
以上是生活随笔為你收集整理的重新认识受控和非受控组件的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
 
                            
                        - 上一篇: 女孩子录取了计算机类,苏州中学小女生录取
- 下一篇: 三十六、前端基础 CSS
