- useState:用來設定 component 的 state
- 使用方法
const [currentValue, setCurrentValue] = useState(initialValue);
currentValue
:用來存放 state 的值- 是一個變數,可自行命名
- 是一個變數,可自行命名
setCurrentValue
:用來設定 state 的值- 是一個函式,可自行命名
- 可以是更新後的具體值,也可以是一個返回更新後具體值的函數
註:若
setCurrentValue
接收的是一個函數,則會將舊的 state 作為引數傳遞給此函數
initialValue
:用來設定 state 的初始值- 可以是數字、字串、物件等任意值,也可以是一個 function
- 當
initialValue
是一個 function 時,這個 function 叫做 Lazy Intializers,且這個 function 只會在 Mount (第一次載入頁面或頁面重整) 時執行 註 1:通常會在初始值需要做複雜計算時使用此方法,這樣就只有 Mount 時會做運算,可以有效減少效能浪費 註 2:用 Lazy Intializers 搭配非同步函式是沒用的,因為它只看同步的結果
- 第一次 render 時,回傳的 state 值會和
initialValue
回傳的值相同 - 可透過
setCurrentValue
來更新 state(currentValue
),state 一旦改變,就會觸發 React 去重新渲染畫面
const [count, setCount] = useState(initialCount);
setCount(newValue);
======================================
const [count, setCount] = useState(initialCount);
// setCurrentValue 中也可以傳入函式
// 在這個函式中可以拿到前一次的 state
setCurrentValue((prevCount) => {
// 最後要記得把新的 state 回傳回去
return {
...preCount,
newThing
}
});
- useEffect:用來告訴 React,component 在 render 完且『瀏覽器 paint 畫面後』要做的事情
- 使用方法
useEffect(() => {
// do something here
console.log("do something");
return () => {
// cleanup function
// do another thing here
console.log("do another thing");
};
}, []);
- 第一個參數傳入的是 function,會在「render 完且畫面渲染完成後」被呼叫
- 可以在第一個參數的 funtion 裡 return 另一個 funtion,此 function 會被稱為 cleanup function
- cleanup function 只會在下一個
useEffect
執行前執行 註:執行 cleanup function 時,裡面用到的 props 跟 state 的值,會是上一個 useEffect 時的資料狀態
- 第二個參數傳入一個陣列,用來放想要關注的資料,當此資料改變時才會重新呼叫
useEffect
- 不管第二個參數的陣列有沒有傳入值,第一次 render 一定會執行
useEffect
- 如果同時有多個
useEffect
,會按程式碼的順序依次執行
useEffect(() => {
const subscription = props.source.subscribe();
return () => {
// 此 function 為 cleanup function
subscription.unsubscribe();
};
}, [props.source]);
- useContext:解決 Component 內部跨多層(父 -> 子)傳遞資料的問題,就像建立了全域變數
- 使用方法
const PassSomething = createContext();
function App() {
return (
<PassSomething.Provider value={something}>
<Dad>
<Child />
</Dad>
</PassSomething.Provider>
);
}
function Child() {
const something = useContext(PassSomething);
}
- 讓父層的資料能夠被底下的任意子層存取,讓 props 不需要再一層一層傳遞下去,解決 prop drilling 的問題
- 用
React.createContext
建立一個 context,並由<MyContext.Provider value={}>
來傳下該物件的值,底下的子層就可以直接透過useContext
來存取 MyContext - 傳下去的 context 可以是任意值
註 1:傳下去的值是什麼,子層接收到的
useContext
的值就會是什麼 註 2:傳下去的 context 可以在中間層被改變,底下的子層則會『往上層找距離最近的 context 的值』 註 3:如果接收 context 的子層跟父層不在同一個檔案時,需要先引入父層宣告的 context 才能接收到
// 父層和子層在同一個檔案時
import React, { useState, createContext, useContext } from "react";
// 會像 component 一樣使用,所以要用大寫開頭
const Context = createContext(null);
function App() {
const [state, setState] = useState(0);
return (
// 此處傳入的是物件,子層接收到的 useContext 就會是物件
<Context.Provider value={{ state, setState }}>
<div>
<Baba />
<Uncle />
</div>
</Context.Provider>
);
}
function Baba() {
return (
<div>
我是爸爸
<Child />
</div>
);
}
function Uncle() {
const { state, setState } = useContext(Context);
return <div>我是叔叔 我拿到的 context 資料為 {state}</div>;
}
function Child() {
const { state, setState } = useContext(Context);
return (
<div>
我是兒子 我拿到的 context 資料為 {state}
<button onClick={() => setState(state + 5)}>點選改變 context 資料</button>
</div>
);
}
======================================
// 父層和子層在不同檔案時
// App.js
import React, { useState, createContext, useContext } from "react";
// 會像 component 一樣使用,所以要用大寫開頭
export const Context = createContext(null);
function App() {
const [state, setState] = useState(0);
return (
// 此處傳入的是物件,子層接收到的 useContext 就會是物件
<Context.Provider value={{ state, setState }}>
<div>
<Baba />
<Uncle />
</div>
</Context.Provider>
);
}
// Child.js
import React, { useContext } from "react";
import { Context } from "./App"
// 不同檔案的話,要先引入父層宣告的 Context 才能使用
function Child() {
const { state, setState } = useContext(Context);
return (
<div>
我是兒子 我拿到的 context 資料為 {state}
<button onClick={() => setState(state + 5)}>點選改變 context 資料</button>
</div>
);
}
- useLayoutEffect:用來告訴 React,component 在 render 完且『瀏覽器 paint 畫面前』要做的事情 註 1:功能與用法都跟 useEffect 相似,區別只在於執行的時機點 註 2:useLayoutEffect 會在 useEffect 前執行
- 使用方法
useLayoutEffect(() => {
// do something here
console.log("do something");
return () => {
// cleanup function
// do another thing here
console.log("do another thing");
};
}, []);
- 第一個參數傳入的是 function,會在「render 完且畫面渲染完成後」被呼叫
- 可以在第一個參數的 funtion 裡 return 另一個 funtion,此 function 會被稱為 cleanup function
- cleanup function 只會在下一個
useLayoutEffect
執行前執行 註:執行 cleanup function 時,裡面用到的 props 跟 state 的值,會是上一個 useLayoutEffect 時的資料狀態
- 第二個參數傳入一個陣列,用來放想要關注的資料,當此資料改變時才會重新呼叫
useLayoutEffect
- 不管第二個參數的陣列有沒有傳入值,第一次 render 一定會執行
useLayoutEffect
- 如果同時有多個
useLayoutEffect
,會按程式碼的順序依次執行
useLayoutEffect(() => {
const subscription = props.source.subscribe();
return () => {
// 此 function 為 cleanup function
subscription.unsubscribe();
};
}, [props.source]);
- useRef:用來抓取 DOM 節點,存放的值不會受到 render 影響
- 使用方法
const refTarget = useRef(initialValue)
- 會回傳一個 mutable(可變的) 的 ref object,其
.current
屬性會被初始為傳入的參數initialValue
- 當
.current
屬性有變動時不會觸發重新 render,而每次 render 時都會給同一個 ref object 註:因為此特性,所以可以保證每一次 render 後的 ref object 都是同一個 object - 可以直接對變數做使用,也可以對 DOM 元素使用
註 1:對 DOM 元素使用時,要在 DOM 裡面傳入
ref={refTarget}
的屬性用來存取此 DOM 註 2:對 DOM 元素使用的refTarget
回傳的 object 的.current
就會是此 DOM 元素
// 對變數做使用
const refContainer = useRef(initialValue);
console.log(refContainer.current); // 會是 initialValue
======================================
// 對 DOM 元素做使用
const refTarget = useRef(initialValue);
function App() {
...
return (
<div>
<input ref={refTarget}/>
</div>
)
}
console.log(refContainer.current); // 會是 input 這個 DOM 元素
- useMemo:當 component 重新渲染時,可以避免 component 內複雜的運算被重複執行
- 使用方法
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
- 第一個參數是要執行的 function
- 第二個參數是其依賴陣列
useMemo
會回傳一個 memoized 的值(也就是把傳入的第一個參數 function return 的值給記起來並回傳)- 跟
useCallback
的用法其實差不多,但大部分的情況都只會用到 useMemo - 主要目的是避免在 component 內部的複雜運算,會因為每次的 render 需要不斷重新計算,導致效能上不必要的消耗,使用
useMemo
可以讓複雜的運算結果被記起來,並且只會在依賴改變時才需要重新計算。
const redStyle = {
color: "red",
};
const blueStyle = {
color: "blue",
};
function App() {
const [value, setValue] = useState(5);
const handleClick = () => {
setValue(value - 1);
};
const s = useMemo(() => {
console.log("complex calculate s");
return value ? redStyle : blueStyle;
}, [value]);
// UI
return (
<div className="App">
Count: {value}
<button onClick={handleClick}>click</button>
<Test style={s} />
</div>
);
}
- useCallback:用來解決當 component 重新渲染後,component 內的 function 也會重新產生的問題 註:useCallback 的底層就是用 useRef 做的
- 使用方法
// 原本的 function
const Example = (a, b) => {
console.log(a + b);
};
// 使用 useCallback 後的 function
const Example = useCallback((a + b) => {
console.log(a + b);
}, []);
- 第一個參數是要回傳的 function
- 第二個參數是其依賴陣列
useCallback
會回傳一個 memoized 的 callback(也就是會把傳入的第一個參數 function 的版本給記起來並回傳)- 註 1:
useCallback
是useMemo
的一種變體,用來記住一個 function instance - 註 2:其實就相當於一個回傳 function
useMemo
- 註 3:
useCallback(fn, deps)
相當於useMemo( () => fn, deps)
- 註 1:
- 主要目的是避免在 component 內部宣告的 function,會因為每次的 render 不斷重新被宣告跟建立,導致每次拿到的都是不同的 instance。這樣的 function 如果被當成 prop 往下傳給其他 component,就可能導致下面的 component 無意義地被重新 render,使用
useCallback
可以讓 function 只會在依賴改變時才更新,防止不必要的渲染,減少效能上的消耗。
// 原本的新增 todo
const handleButtonClick = () => {
setTodos([
{
id: id.current,
content: value,
isFinished: false,
},
...todos,
]);
setValue("");
id.current++;
}
// 用 useCallback 後的新增 todo
const handleButtonClick = useCallback(() => {
setTodos([
{
id: id.current,
content: value,
isFinished: false,
},
...todos,
]);
setValue("");
id.current++;
}, []);
function App() {
return (
<div>
<button onClick={handleButtonClick}>
</div>
)
};
/*
component 第一次 render 的時候會執行到 useCallback(...) 小括號裡面的程式碼,
然後 useCallback 就會幫我把 handleButtonClick 這個 function 記起來。
component 觸發 re-render 後,因為 dependency array 傳空陣列,所以永遠不會有東西改變,
因此 handleButtonClick 這個 function 也永遠都不會變。
下一次執行時就會直接用 useCallback 記起來的那個版本,
也就是說,不會再有新的 handleButtonClick 產生了(handleButtonClick 永遠都會是同一個 function)
*/
- useImperativeHandle:可以在父層調用子層中 ref,選取指定的 DOM 節點
- 使用方法
useImperativeHandle(ref, createHandle, [deps])
useImperativeHandle
可以讓使用ref
時能向父 component 暴露自定義的 instance 值- 第一個參數是 component 接收到的 ref
- 第二個參數是傳給父層的方法
useImperativeHandle
應與forwardRef
一同使用- 註:
forwardRef
用法可參考 React.forwardRef - React 官方文件
- 註:
function App() {
const myRef = useRef(null);
useEffect(() => {
console.log(myRef.current.real);
console.log(myRef.current.getParent());
}, []);
return (
<div>
我是父元件
<Child ref={myRef} />
</div>
);
}
const Child = forwardRef((props, ref) => {
const childRef = useRef(null);
useImperativeHandle(ref, () => {
return {
real: childRef.current,
getParent() {
return childRef.current.parentNode;
},
};
});
return (
<div>
我是子元件,我有一個子DOM
<button ref={childRef}>按鈕</button>
</div>
);
});
/*
在這個範例中,render 子 component <Child ref={myRef} /> 的父 component <App />
能呼叫 myRef.current.real 或是 myRef.current.getParent()
*/
- useDebugValue:可用來在 React DevTools 中顯示自訂義 hook 的標籤 註: 詳細用法建議參考 useDebugValue 基础用法 - GitHub
- 使用方法
useDebugValue(value, function);
useDebugValue
可以用來讓自訂義的 hook 在 React DevTools 中顯示額外的信息,方便我們一眼就能找到對應的自定義 hook- 註: React 官方不建議在每個自定義的 Hook 都加上 debug 值
- 註: React 官方不建議在每個自定義的 Hook 都加上 debug 值
- 第一個參數是我們想要在 React DevTools 中顯示的標籤內容
- 第二個參數是一個格式化 function (可傳可不傳)
- 註: 該 function 只有在 Hook 被檢查時才會被呼叫。它接受 debug 值作為參數,然後回傳一個被格式化的顯示值
// 直接傳 debug 值
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
// ...
// Show a label in DevTools next to this Hook
// e.g. "FriendStatus: Online"
useDebugValue(isOnline ? "Online" : "Offline");
return isOnline;
}
======================================
// 延遲格式化 debug 值
const date = new Date()
useDebugValue(date, date => date.toDateString())
- useReducer:是 useState 的升級版,當 state 邏輯變得複雜,需要操作多種 state 時可使用
- 使用方法
const [state, dispatch] = useReducer(reducer, initialState);
- state:目前的 state 值
- 對應到 Redux 中『Store 裡面的狀態』
- 對應到 Redux 中『Store 裡面的狀態』
- dispatch:透過不同參數來和 reducer 溝通,藉此控制處理方式
- 對應到 Redux 中『由 dispatch 指定 Store 執行哪些事』
- 對應到 Redux 中『由 dispatch 指定 Store 執行哪些事』
- reducer: 是一個 function
- 會接受兩個參數 state(目前的狀態), action(要執行的操作),並根據對應的 dispatch 方法,回傳新的 state
- 會接受兩個參數 state(目前的狀態), action(要執行的操作),並根據對應的 dispatch 方法,回傳新的 state
- initialState:用來設定 state 的初始值
// React 官方範例
const [state, dispatch] = useReducer(reducer, initialState);
// 初始狀態為 count: 0
const initialState = { count: 0 };
// 由 reducer 回傳 state
function reducer(state, action) {
switch (action.type) {
case "increment":
// 根據不同的 action,return 新的 state 並取代原本的 state
return { count: state.count + 1 };
case "decrement":
return { count: state.count - 1 };
default:
// 非預期指令時則丟出 Error
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
{/* 由 dispatch 發送指令,{type: 'decrement'} 這個物件就代表一個動作 */}
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
<button onClick={() => dispatch({ type: "increment" })}>+</button>
</>
);
}
參考來源:
Hook 概觀 - React 官方文件
[學習筆記] React 內建的所有 Hooks 功能整理 - 前端新米
30 分鐘帶你全面瞭解 React Hooks
什麼時候該使用 useMemo 跟 useCallback
[Day 07]React Hooks,useRef 與 useMemo 與 useCallback
React Hook 系列教程,学习和探索 Hooks 世界。 - GitHub
圖片來源:React LifeCycle Methods Diagram
- Mount:初始化階段(也就是把元件放到畫面上,只會執行一次)
- Update:元件更新階段(也就是 re-render)
- Unmount:元件卸載階段(也就是把元件從畫面拿掉,只執行一次)
註:以下 method 皆按觸發順序排列
constructor
:會在 component 被 mount 之前被呼叫- 你沒有初始化 state 也不綁定方法的話,你的 React component 就不需要 constructor
- 進行資料宣告、初始化、預備、函式綁定的地方,但通常只會用來做兩件事:
- 透過指定一個 this.state 物件來初始化內部 state
- 為 event handler 方法綁定 instance 註 1: 在 ES6 的時候,如果沒有寫 super() ,就調用 this 來初始化 state 的話會報錯 註 2: 但是 ES7 出現之後可以不用寫 constructor 甚至不用調用 this 就可以寫 state
getDerivedStateFromProps
:constructor
初始化後或者 props/state 改變時執行render
:將 component 輸出到 DOMcomponentDidMount
:render
執行完畢且放到 DOM 上後呼叫- 第一次 render 後唯一觸發的生命週期函數,只會執行一次
- 通常用於處理數據,比方說 call API 資料
getDerivedStateFromProps
:constructor
初始化後或 props/state 改變時執行shouldComponentUpdate
:在render
前執行- 用來決定 component 會不會跟著被更新的 state 或者 props 而有所變動
- 回傳 false 時,可以阻止 component 重新 render,避免沒必要的更新,以達到優化效能的效果
- 註:因為 React 的預設行為是每次只要有更新就會觸發重新 render,所以在使用這個方法時要特別注意。雖然這個方法的存在是為了效能最佳化,使我們可以透過比對前後的 state, prop 來決定是否做此次的更新,但透過回傳 false 不會阻止子元件在自身 state 改變時的重新 render
render
:將 component 輸出到 DOMgetSnapshotBeforeUpdate
:在render
執行完但是 DOM 真正更新前 執行- 讓元件在 DOM 發生變化前捕獲一些信息(如滾動位置),用意是把更新前最後一刻的 DOM 狀況紀錄下來
- 註:當使用了這個方法時,會回傳一個值作為參數傳遞給
componentDidUpdate()
componentDidUpdate
:render
執行完畢且 DOM 也更新完畢後會馬上執行- 在這邊也非常適合做網路請求,比方說 call API 資料
- 註:需要特別注意的是,在
componentDidUpdate
中記得比較前後的值是否有差異,如果有差異的話才做新的一次網路請求,否則可能會影響 component 的效能。
componentWillUnmount
:元件被移除時呼叫,只會執行一次- 通常用來進行一些收尾工作,比方說移除新增的元素、監聽事件與定時器
參考來源:
Class-based Component 的生命週期(實際操作)
Class-based Component 生命週期方法(用法講解))
[Day06]生命週期 Lifecycle(Class Component)
React lifeCycle 生命週期
- 透過 ES6 的 class 語法來實作物件導向的 class component
- 由於 this 指向的關係,state 和 props 會拿到最新的結果,但是會較不易於進行 callback 操作
- 提供許多 lifecycle method 使用,方便管理較複雜的 component 狀態
- 關注的是「每個生命週期」要做的事情
- 只要調用了 setState function 就會觸發 component 的重新渲染。
- 透過 function 來實作的 function component
- 透過閉包的形式來管理狀態(props 會一直是傳進來的那個)
- 生命週期的方法,是以 useEffect 來決定 render 要做的事情
- 關注的是「每次的 render」要做的事情,因為每一次的 render,都是「重新」呼叫一次 function,並且會記住「當次 render 」傳入的值
- 把許多 method 都寫在 function 中,自己本身就像是 render function,較容易抽出共同邏輯,或是進行模組化測試
- 如果我們使用 setState 時,是用與目前的 state 相同的值來更新 state 的話,React 將會跳過子 component 的 render 及該 component effect 的執行,但要注意依然會 render 該 component。
Class component 與 Function component 兩者用法差異的實際範例推薦: How Are Function Components Different from Classes? 從實際案例看 class 與 function component 的差異
參考來源:
[week 22] 再探 React:Function component vs Class component
[Day 8]Class component && Functional component
[Day 07] Functional Component v.s Class Component
React Class-based vs Functional Component - 從特性淺談兩種寫法之異同
兩者之間最大的差別,其實就在於 component 的資料是否由 React 控制
- 例如 input、textarea 等表單元素,如果作為 component 通常不會用
useState
來綁定 state - 若想取得 uncontrolled component 的值,可透過直接操作 DOM 或使用 useRef 來選取特定元素
- 也就是 component 有透過
useState
來保存資料,setState
來更新資料 - 如果將資料的控制權交給 React 來處理,畫面就會根據 state 是否改變來重新渲染
參考來源: [week 21] React Hooks API:useState & 再戰 Todo List [Day 15] React controlled components v.s uncontrolled components React Controlled Component 與 Uncontrolled Component 差別