memo,useCallback,useMemo以及useEffect區別

問題引入

子組件的只依賴傳入的name屬性,但是父組件name屬性和text屬性變化都會導致Parent函數重新執行,所以即使傳入子組件props沒有任何變化,甚至子組件沒有依賴於任何props屬性,都會導致子組件重新渲染

const Child = ((props: any) => {
    console.log("子組件更新...");
    return (
        <div>
            <h3>子組件</h3>
            <div>text:{props.name}</div>
            <div>{new Date().getTime()}</div>
        </div>
    )
})
const Parent = () => {
    const [count, setCount] = useState(0);
    const [text, setText] = useState("")
    const handleClick = () => {
        setCount(count + 1);
    }
    const handleInputChange = (e) => {
        setText(e.target.value)
    }
    return (<div>
        <input onChange={handleInputChange} />
        <button onClick={handleClick}>+1</button>
         <div>count:{count}</div>
        <Child name ={text}/>
    </div>)
}

memo

使用memo包裹子組件時,只有props發生改變子組件纔會重新渲染。使用memo可以提升一定的性能。

const Child = memo((props: any) => {
    console.log("子組件更新..."); // 只有當props屬性改變,集name屬性改變時,子組件纔會重新渲染
    return (
        <div>
            <h3>子組件</h3>
            <div>text:{props.name}</div>
            <div>{new Date().getTime()}</div>
        </div>
    )
})
const Parent = () => {
    const [count, setCount] = useState(0);
    const [text, setText] = useState("")
    const handleClick = () => {
        setCount(count + 1);
    }
    const handleInputChange = (e) => {
        setText(e.target.value)
    }
    return (<div>
        <input onChange={handleInputChange} />
        <button onClick={handleClick}>+1</button>
         <div>count:{count}</div>
        <Child name ={text}/>
    </div>)
}

但如果傳入的props包含函數,父組件每次重新渲染都是創建新的函數,所以傳遞函數子組件還是會重新渲染,即使函數的內容還是一樣。如何解決這一問題,我們希望把函數也緩存起來,於是引入useCallback

const Child = memo((props: any) => {
    console.log("子組件更新..."); // 即使點擊按鈕count變化,但函數重新執行,handleInputChange已經指向新的函數實例,所以子組件依然會刷新
    return (
        <div>
            <h3>子組件</h3>
            <div>text:{props.name}</div>
            <div> <input onChange={props.handleInputChange} /></div>
            <div>{new Date().getTime()}</div>
        </div>
    )
})
const Parent = () => {
    const [count, setCount] = useState(0);
    const [text, setText] = useState("")
    const handleClick = () => {
        setCount(count + 1);
    }
    const handleInputChange = (e) => {
        setText(e.target.value)
    }
    return (<div>
        <button onClick={handleClick}>+1</button>
         <div>count:{count}</div>
        <Child name={text} handleInputChange={handleInputChange}/>
    </div>)
}

useCallback

useCallback用用於緩存函數,只有當依賴項改變時,函數纔會重新執行返回新的函數
對於父組件中的函數作爲props傳遞給子組件時,只要父組件數據改變,函數重新執行,作爲props的函數也會產生新的實例,導致子組件的刷新
使用useCallback可以緩存函數。需要搭配memo使用

const Child = memo((props: any) => {
    console.log("子組件更新..."); 
    return (
        <div>
            <h3>子組件</h3>
            <div>text:{props.name}</div>
            <div> <input onChange={props.handleInputChange} /></div>
            <div>{new Date().getTime()}</div>
        </div>
    )
})
const Parent = () => {
    const [count, setCount] = useState(0);
    const [text, setText] = useState("")
    const handleClick = () => {
        setCount(count + 1);
    }
    const handleInputChange =useCallback((e) => {
        setText(e.target.value )
    },[]) 
    return (<div>
        <button onClick={handleClick}>+1</button>
        <div>count:{count}</div>
        <Child name={text} handleInputChange={handleInputChange}/>
    </div>)
}

useCallback第二個參數依賴項什麼情況下使用呢,看下面的例子

//修改handleInputChange
const handleInputChange =useCallback((e) => {
        setText(e.target.value + count)
    },[]) 

以上例子count改變會發生什麼事呢?
count改變,但handleInputChange不依賴與任何項,所以handleInputChange只在初始化的時候調用一次函數就被緩存起來,當文本改變時或者count改變時函數內部的count始終爲0,至於爲什麼需要看useCallback源碼後解答。
所以需要將count加入到依賴項,count變化後重新生成新的函數,改變函數內部的count值

const handleInputChange =useCallback((e) => {
        setText(e.target.value + count)
    },[count])

useMemo

useMemo使用場景請看下面這個例子,getTotal假設是個很昂貴的操作,但該函數結果僅依賴於count和price,但是由於color變化,DOM重新渲染也會導致該函數的執行,
useMemo便是用於緩存該函數的執行結果,僅當依賴項改變後纔會重新計算

const Parent = () => {
    const [count, setCount] = useState(0);
    const [color,setColor] = useState("");
    const [price,setPrice] = useState(10);
    const handleClick = () => {
        setCount(count + 1);
    }
    const getTotal = ()=>{
        console.log("getTotal exec ...") // 該函數依賴於count和price,但color變化也會導致該函數的執行
        return count * price
    }
    return (<div>

        <div> 顏色: <input onChange={(e) => setColor(e.target.value)}/></div>
        <div> 單價: <input value={price}  onChange={(e) => setPrice(Number(e.target.value))}/></div>
        <div> 數量:{count} <button onClick={handleClick}>+1</button></div>
        <div>總價:{getTotal()}</div>
    </div>)
}

修改後如下,注意useMemo緩存的是函數執行的結果

const Parent = () => {
    console.log("parent exec...")
    const [count, setCount] = useState(0);
    const [color,setColor] = useState("");
    const [price,setPrice] = useState(10);
    const handleClick = () => {
        setCount(count + 1);
    }
    const getTotal = useMemo(()=>{
        console.log("getTotal exec ...") 
        return count * price
    },[count, price])
    return (<div>

        <div> 顏色: <input onChange={(e) => setColor(e.target.value)}/></div>
        <div> 單價: <input value={price}  onChange={(e) => setPrice(Number(e.target.value))}/></div>
        <div> 數量:{count} <button onClick={handleClick}>+1</button></div>
        <div>總價:{getTotal}</div>
    </div>)
}

useMemo和useEffect區別

以上useMemo例子也可以使用useEffect實現,使用useEffect改寫的結果

const Parent = () => {
    console.log("parent exec...")
    const [count, setCount] = useState(0);
    const [color,setColor] = useState("");
    const [price,setPrice] = useState(10);
    const [total,setTotal] = useState(0)
    useEffect(()=>{
        setTotal(price * count)
    },[price,count])
    const handleClick = () => {
        setCount(count + 1);
    }
    
    return (<div>
        <div> 顏色: <input onChange={(e) => setColor(e.target.value)}/></div>
        <div> 單價: <input value={price}  onChange={(e) => setPrice(Number(e.target.value))}/></div>
        <div> 數量:{count} <button onClick={handleClick}>+1</button></div>
        <div>總價:{total}</div>
    </div>)
}

注意觀察useEffect是在price,count其中任何一個變化後觸發,然後total改變,total改變又會觸發函數的重新執行,導致組件重新渲染,所以在控制套可以看到“parent exec...”被打印了兩次,而第一個例子中,“parent exec...”只被打印了一次
所以可以得出如下總結:
useEffect是在DOM改變之後觸發,useMemo在DOM重新渲染之前就觸發
useEffect設置值會再次重新渲染,但useMemo不會

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章