React組件的受控和非受控

淺談React組件的受控和非受控

對於組件的props和state的不同運用方式,造成了react組件不同的使用方式

日常用到的react組件,可以分爲如下三種

  1. 完全非受控組件:沒有props,無法從外部控制組件,修改組件的狀態
  2. 完全受控組件:props和state完全分開,或者乾脆沒有state
  3. 薛定諤組件:部分state會被外部傳入的props所控制,但這些state在組件內部也會被控制到,所以處在受控和非受控的中間態
以下是自己的一些理解,並且爲了方便,例子都是hooks組件,請見諒

完全非受控組件

clipboard.png

// 沒有參數,傳了也不用
// 內部自己進行操作,外部無法干涉
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

clipboard.png

// 子組件,完全受控
// 姓名他老子可以控制,想讓叫什麼就叫什麼
// 年齡他老子控制不了,不能說讓他幾歲就幾歲
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>
    )
}

這種纔是平常寫組件應該追求的方式,儘量讓組件完全受控,不會出現如下恐怖的情況

薛定諤組件

這種組件是受控還是非受控的,難說,但是這種組件很常見

希望您看到這還沒有煩,下邊的內容纔是重點

clipboard.png

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依然是之前的值,然後組件就懵了,好吧我自己也有點懵了,如下圖

clipboard.png

這種情況,兩個選擇:

1. 設置優先級,優先採用外部的值還是內部的值
2. 對比,內部外部哪個改變了用哪個,兩個都改變,還是優先級...

數不盡的判斷接踵而至

想着避免這種情況

就如音樂中的和絃外音,放在和絃中感覺格格不入,當彈奏的時候總是希望他向穩定的和絃中音去靠攏
物理上,希望這種中間狀態倒向一個穩定狀態

1. 完全非受控

嗯,這是不可能的
但是可以做成假的完全非受控——第一次受控,以後就完全非受控了

就是設計一個props叫做defaultValue,只在組件第一次的時候(~(羞臉)~!),可以把外部的值傳到組件內

clipboard.png

但是這種組件,只讓控制一次,讓使用者沒有了控制慾

2. 完全受控

還是那個表單組件,控制一下,所有的修改都來自於外部就可以了,內部的修改不進行value的同步

重新規劃一下數據流

  • 外部修改props.value => 內部input顯示的value
  • 內部input修改 => 通知外部同步 => 外部修改props.value => 內部input顯示的value

這樣的話input的值永遠來自外部

實現上邊黑字的部分,就要搬出大名鼎鼎的onChange

clipboard.png

但是外部忘了做onChange的同步操作,那就會出現

clipboard.png

個人傾向還是第二種

總結

我自己是這樣認爲的(看別的大神的文章):

讓子組件變得彈性,不要阻塞數據流
也就是說,不要把props固定到組件內部,props和state完全分離
也就是說,讓父級組件的數據無障礙的流入子組件
也就是說,讓子組件變得完全受控

  • 外部props => 子組件處理展示
  • 子組件出發修改 => 通知外部同步 => 外部修改props => 子組件處理展示
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章