淺談React組件的受控和非受控
對於組件的props和state的不同運用方式,造成了react組件不同的使用方式
日常用到的react組件,可以分爲如下三種
- 完全非受控組件:沒有props,無法從外部控制組件,修改組件的狀態
- 完全受控組件:props和state完全分開,或者乾脆沒有state
- 薛定諤組件:部分state會被外部傳入的props所控制,但這些state在組件內部也會被控制到,所以處在受控和非受控的中間態
以下是自己的一些理解,並且爲了方便,例子都是hooks組件,請見諒
完全非受控組件
// 沒有參數,傳了也不用
// 內部自己進行操作,外部無法干涉
function Inc() {
const [number, setNumber] = useState(0)
const handleInc = () => {
setNumber(number + 1)
}
return [
(<p>{ number }</p>),
(<button onClick={handleInc}>加一</button>)
]
}
這種組件一般會用在寫頁面組件,不需要進行參數的配置,所有操作都在內部
完全受控組件
這類組件會把開放出來的狀態完全交給外部控制,但不代表沒有自己內部的狀態(我理解一個組件的狀態是指的屬於它當前的props和state的集合),所以,組件完全受控,就是state和props完全分開,或者乾脆沒有state
// 子組件,完全受控
// 姓名他老子可以控制,想讓叫什麼就叫什麼
// 年齡他老子控制不了,不能說讓他幾歲就幾歲
function Son({name}) {
const [age, setAge] = useState(0)
// @warn 如果對useEffect不太理解,這塊可以直接理解爲age一年加一
useEffect(() => {
setInterval(() => {
setAge(prevAge => prevAge + 1)
}, 31536000000)
}, [])
return [
(<p>兒子姓名: {name}</p>),
(<p>兒子年齡: {age}</p>),
]
}
function Dad(){
return (
<div>
<Son name="6牆冬">
</div>
)
}
這種纔是平常寫組件應該追求的方式,儘量讓組件完全受控,不會出現如下恐怖的情況
薛定諤組件
這種組件是受控還是非受控的,難說,但是這種組件很常見
希望您看到這還沒有煩,下邊的內容纔是重點
1. 兒子示例
我要改一下上邊的Son組件,模擬以下他爸爸是瀏覽器之神,讓兒子幾歲就幾歲
// 子組件,完全受控
// 姓名他老子可以控制,想讓叫什麼就叫什麼
// 年齡他老子也控制,說讓他幾歲就幾歲,但是兒子還是有自己的成長
function Son({ageFromDad, name}) {
const [age, setAge] = useState(ageFromDad)
// @warn 如果對useEffect不太理解,這塊可以直接理解爲age一年加一
useEffect(() => {
setInterval(() => {
setAge(prevAge => prevAge + 1)
}, 31536000000)
}, [])
// @warn 如果對useEffect不太理解
// 這塊可以直接理解爲如果傳入的ageFromDad改變,則設置age爲ageFromDad
useEffect(() => {
setAge(ageFromDad)
}, [ageFromDad])
return [
(<p>兒子姓名: {name}</p>),
(<p>兒子年齡: {age}</p>),
]
}
// 爸爸讓兒子一年長兩歲
function Dad(){
const [ageFromDad, setAgeFromDad] = useState(0)
// @warn 如果對useEffect不太理解,這塊可以直接理解爲age一年加 二!!!
useEffect(() => {
setInterval(() => {
setState(prevAge => prevAge + 2)
}, 31536000000)
}, [])
return (
<div>
<Son name="6牆冬" age={ageFromDad}>
</div>
)
}
接下來,兒子的年齡就會出現波動,在下一年到來之前,他永遠不能確定自己多大了
這個Son組件state中的age字段,出現了無法預測的問題,就是由於外部和內部都可以控制其修改
2. 表單示例
這種組件用起來很難受,但是又很多很多組件都是這樣設計的
例如表單組件中Input組件,都會有一個props叫做value的屬性,這個屬性反應了當前輸入框的內容,但是操作輸入框的話也會引起value的變化,但是外部傳入的value依然是之前的值,然後組件就懵了,好吧我自己也有點懵了,如下圖
這種情況,兩個選擇:
1. 設置優先級,優先採用外部的值還是內部的值
2. 對比,內部外部哪個改變了用哪個,兩個都改變,還是優先級...
數不盡的判斷接踵而至
想着避免這種情況
就如音樂中的和絃外音,放在和絃中感覺格格不入,當彈奏的時候總是希望他向穩定的和絃中音去靠攏
物理上,希望這種中間狀態倒向一個穩定狀態
1. 完全非受控
嗯,這是不可能的
但是可以做成假的完全非受控——第一次受控,以後就完全非受控了
就是設計一個props叫做defaultValue,只在組件第一次的時候(~(羞臉)~!),可以把外部的值傳到組件內
但是這種組件,只讓控制一次,讓使用者沒有了控制慾
2. 完全受控
還是那個表單組件,控制一下,所有的修改都來自於外部就可以了,內部的修改不進行value的同步
重新規劃一下數據流
- 外部修改props.value => 內部input顯示的value
- 內部input修改 => 通知外部同步 => 外部修改props.value => 內部input顯示的value
這樣的話input的值永遠來自外部
實現上邊黑字的部分,就要搬出大名鼎鼎的onChange了
但是外部忘了做onChange的同步操作,那就會出現
個人傾向還是第二種
總結
我自己是這樣認爲的(看別的大神的文章):
讓子組件變得彈性,不要阻塞數據流
也就是說,不要把props固定到組件內部,props和state完全分離
也就是說,讓父級組件的數據無障礙的流入子組件
也就是說,讓子組件變得完全受控
- 外部props => 子組件處理展示
- 子組件出發修改 => 通知外部同步 => 外部修改props => 子組件處理展示