Hooks

Hooks

2021/07/08

React有很多hooks,也可以自訂hooks,在Functional Component 裡,只介紹了useState及useEffect。在這邊介紹useState及useEffect的一些深入的概念以及其他的hooks。

useState

Functional Component 裡,提到了要注意的是,state的操作不是馬上生效(非同步/asynchronous)的。如果在setCount中利用alert顯示count的內容,會發現,count還沒加一,這就是非同步的現象,也就是setCount並不會馬上生效,其實是在handleClick執行完,要重新顯示頁面前才會完成更新的動作。我們來看一下,下面的例子裡,四次的setCount取得的都是上次的值:

import React, {useState} from 'react';

export default function Click() {


const [count, setCount] = useState(0);


const handleClick = function() {

setCount(count+1);

setCount(count+1);

setCount(count+1);

setCount(count+1);


}


return (

<button onClick={handleClick}>{count}</button>

);

}

我們會以為每點一次應該會增加4,可是,答案是只增加1。

setState (setProducts)接受兩種參數,第一種參數是個值,setState會更新state的內容,上面情況會有問題。

所以,必須用第二種傳遞方式,就是接受一個callback函數,當參數是callback函數時,setState會將原有的值以及props傳給callback函數 (詳參:State 和生命週期) 。程式要改成:

import React, {useState} from 'react';

export default function Click() {


const [count, setCount] = useState(0);


const handleClick = function() {

setCount(prev=>prev+1);

setCount(prev=>prev+1);

setCount(prev=>prev+1);

setCount(prev=>prev+1);


}


return (

<button onClick={handleClick}>{count}</button>

);

}

useEffect

useEffect接受兩個參數,第一個是個call back function,第二個參數是個陣列,陣列裡放的是個陣列,當陣列裡的變數異動時,useEffect才會去執行call back function。

我們先試試看如果第二個參數是空陣列,就會發現只有在一開始useEffect才會啟動。

import React, {useState, useEffect} from 'react';

export default function Click() {


const [count, setCount] = useState(0);

const [count2, setCount2] = useState(0);


const handleClick = function() {

setCount(count+1);

}


const showCount = function(){

console.log(count);

}


useEffect(showCount, []);


return (

<button onClick={handleClick}>{count}</button>

);

}

*** eslint-plugin-react-hooks會檢查,第二個參數中是否包含了,useEffect中用到的propos及state變數,原因是避免我們使用這些變數時,會取得未更新的值。 ***

因為我們在showCount裡用到count,會警告我們,要包括count。

React Hook useEffect has a missing dependency: 'count'. Either include it or remove the dependency array react-hooks/exhaustive-deps

主要的原因是在使用useEffect時,有個常犯的錯誤,就是會取得過期的state變數。

以上面的例子,會只看到count的初始值,為避免這樣的疏忽,在create-react-app中,會幫我們啟動了eslint-plugin-react-hooks的檢查,來提醒我們避免這樣的問題。

如果showCount裡不使用count,就不會有警告了!

import React, {useState, useEffect} from 'react';

export default function Click() {


const [count, setCount] = useState(0);

const [count2, setCount2] = useState(0);


const handleClick = function() {

setCount(count+1);

}


const showCount = function(){

console.log("Hello");

}


useEffect(showCount, []);


return (

<button onClick={handleClick}>{count}</button>

);

}

如果只監視其中一個變數,就會發現只有在更動count的時候,useEffect才會啟動:

import React, {useState, useEffect} from 'react';

export default function Click() {


const [count, setCount] = useState(0);

const [count2, setCount2] = useState(0);


const handleClick = function() {

setCount(count+1);

}


const handleClick2 = function() {

setCount2(count2+1);

}


const showCount = function(){

console.log(count+","+count2);


}


useEffect(showCount, [count]);


return (

<div>

<button onClick={handleClick}>{count}</button>

<button onClick={handleClick2}>{count2}</button>

</div>

);

}

*** eslint-plugin-react-hooks會檢查,第二個參數中是否包含了,useEffect中用到的propos及state變數,原因是避免我們使用這些變數時,會取得未更新的值。 ***

React Hook useEffect has a missing dependency: 'count2'. Either include it or remove the dependency array react-hooks/exhaustive-deps

所以,當showCount裡不使用count2,就不會有警告了!

import React, {useState, useEffect} from 'react';

export default function Click() {


const [count, setCount] = useState(0);

const [count2, setCount2] = useState(0);


const handleClick = function() {

setCount(count+1);

}


const handleClick2 = function() {

setCount2(count2+1);

}


const showCount = function(){

console.log(count);


}


useEffect(showCount, [count]);


return (

<div>

<button onClick={handleClick}>{count}</button>

<button onClick={handleClick2}>{count2}</button>

</div>

);

}

如果兩個state變數被更動時,需要執行不同的function,就可以有兩個useEffect。

import React, {useState, useEffect} from 'react';

export default function Click() {


const [count, setCount] = useState(0);

const [count2, setCount2] = useState(0);


const handleClick = function() {

setCount(count+1);

}


const handleClick2 = function() {

setCount2(count2+1);

}


const showCount = function(){

console.log(count);


}

const showCount2 = function(){

console.log(count2);


}


useEffect(showCount, [count]);

useEffect(showCount2, [count2]);


return (

<div>

<button onClick={handleClick}>{count}</button>

<button onClick={handleClick2}>{count2}</button>

</div>

);

}

如果去掉第二個變數,那麼,任一個state變數變動,就會啟動useEffect。

import React, {useState, useEffect} from 'react';

export default function Click() {


const [count, setCount] = useState(0);

const [count2, setCount2] = useState(0);


const handleClick = function() {

setCount(count+1);

}


const handleClick2 = function() {

setCount2(count2+1);

}


const showCount = function(){

console.log(count+","+count2);


}


useEffect(showCount);


return (

<div>

<button onClick={handleClick}>{count}</button>

<button onClick={handleClick2}>{count2}</button>

</div>

);

}

Memo

src/Click.js

我們在Click裡面多新增了一個AnotherClick

import React, {useState, useEffect} from 'react';

import AnotherClick from './AnotherClick';

export default function Click() {


const [count, setCount] = useState(0);


const handleClick = function() {

setCount(count+1);

}


const showCount = function(){

console.log(count);

}


useEffect(showCount, [count]);


return (

<div>

<button onClick={handleClick}>{count}</button>

<AnotherClick/>

</div>

);

}

src/AnotherClick.js

import React, {useState, useEffect} from 'react';

export default function AnotherClick() {


const [count, setCount] = useState(0);

console.log(".");


const handleClick = function() {

setCount(count+1);

}


const showCount = function(){

console.log(count);

}


useEffect(showCount, [count]);


return (

<div>

<button onClick={handleClick}>{count}</button>

</div>

);

}

會發現當我們就算只點到Click的按鈕,也會出現「.」,因為react除了會rerender「Click」之外,「AnotherClick」也會,如果,我們希望「AnotherClick」只在相關的props及state更動時才rerender,就要採用memo:

export default function AnotherClick() {


}

先將export改為:

function AnotherClick() {


}

export default AnotherClick;

再套用memo,這樣的寫法稱之為Higher Order Component (HOC)

function AnotherClick() {


}

export default memo(AnotherClick);

這樣子,AnotherClick就只會在只在相關的props及state更動時才rerender。當我們的程式越寫越大,有很多複雜的元件時,採用memo,會節省很多的rerender時間。

src/AnotherClick.js

import React, {useState, useEffect, memo} from 'react';

function AnotherClick() {


const [count, setCount] = useState(0);

console.log(".");


const handleClick = function() {

setCount(count+1);


}


const showCount = function(){

console.log(count);

}


useEffect(showCount, [count]);


return (

<div>

<button onClick={handleClick}>{count}</button>

</div>

);

}

export default memo(AnotherClick);

useMemo

簡單的說,React.memo是針對整個元件進行記憶(memoization),不需要rerender時就不會去rerender。useMemo則是針對一個function的內容進行。

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

參考資料