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的內容,上面情況就會有問題。
3 Mistakes Junior Developers Make With React Function Component State
Modifying State Directly
Setting State That Relies on the Previous State Without Using a Function
Forgetting That the Setter Method from useState Is Asynchronous
所以,必須用第二種傳遞方式,就是接受一個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]);