Hook使用規則
-
確保在React函數頂層使用Hooks,不能在循環、條件、嵌套函數中調用:當單個組件中有多個Hook時,React通過
Hooks的調用順序
來保證Hooks的狀態正確 - 只在函數組件或自定義Hook中調用
常用Hooks
useState
- 在函數組件內部使用
useState
,可以使該組件變成有狀態組件
。這樣在不使用class組件情況下也可以使用state及其他特性 - 使用方法:
const [state, setState] = useState(initialState);
參數:狀態初始值,可以是變量或函數
- 變量:初始渲染期間,state與initialState的值相同
- 函數:如果初始參數需要通過復雜的計算得到,可以傳入一個函數,返回initialState。當初始狀態需要通過高性能操作后才能獲得時,使用函數獲取初始值可以提高性能:該函數只在初始渲染時被調用,在后續組件渲染中不會再調用
返回值:包含兩個元素的數組,可解構
-
state
:狀態值 -
setState
:狀態更新函數。調用后,重新渲染組件,狀態更新到最新
-
惰性初始state:
initialState
只會在組件的初始渲染中起作用,后續渲染時會被忽略 - 函數式更新:新的state依賴于先前的state時,可以為setState方法傳入一個函數,函數接收先前的state,返回更新后的值
- 跳過state更新:若為setState傳入當前state,React將跳過子組件的渲染及effect的執行
- 在多個useState調用中,渲染之間的調用順序必須相同
-
函數式更新
可以防止合并更新問題
function Bulb(props) {
const [bulbState, setBulbState] = useState(false);
const switchBulb = () => {
setTimeout(() => {
setBulbState(!bulbState);
// setBulbState(bulbState => !bulbState);
}, 3000);
};
return (
<>
<div className={bulbState ? "bulbOn" : "bulbOff"}>我是燈泡</div>
<button onClick={switchBulb}>開關</button>
</>
);
}
- 使用
普通更新
:由于更新函數在setTimeout中調用,在3s內多次點擊時,多次state更新會合并成一次,只改變一次state的狀態
setBulbState(!bulbState);
- 使用
函數式更新
:在3s內多次(n)點擊時,每次的更新函數確保收到的是最新的狀態,所以會在3s后切換n次狀態
setBulbState(bulbState => !bulbState);
useEffect
-
useEffect用于執行函數組件中的副作用(Side Effects)。副作用一般包含請求數據、事件處理、訂閱、改變DOM等操作。React要求函數組件必須是一個純函數,其中不能包含副作用。因此,React Hooks提供
useEffect
,以便在函數組件中執行副作用操作 -
useEffect
默認在每次渲染結束后執行 - 使用方法:
useEffect(() => {
// Todo
}, [xxx]);
參數:包含命令式、可能有副作用的函數。第二個參數可選,為副作用依賴值的數組
- 當無第二個參數時:每次組件渲染完成后都會執行
- 當第二個參數為
[]
時:空數組表示effect中沒有依賴props或state中的值,因此僅執行一次(僅在組件掛載和卸載時執行)。類似于class組件中的componentDidMount
和componentWillUnmount
- 當第二個參數為變量數組時:當數組中的變量發生變化時,執行effect。如果數組中包含多個變量,當該次渲染的數組與上一次渲染的數組中的元素都相等時,會跳過本次effect的執行,但即使只有一個元素發生變化,也會執行。類似于
componentDidUpdate
中對prevProps
或prevState
的判斷
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // 僅在 count 更改時更新
注意:
- 每次渲染都執行effect可能會導致性能問題,利用useEffect的第二個參數可以跳過 Effect 的執行,從而進行性能優化
- 數組中包含的是所有外部作用域中會隨時間變化并且在effect中使用的變量
- 兩種副作用:
- 不需要清除的effect:在渲染完成后執行的一些額外的代碼,執行后即可忽略。如發送網絡請求,手動變更 DOM,記錄日志等
- 需要清除的effect:如訂閱外部數據源、定時器操作等,effect執行后要進行清除操作,以防止內存泄露
-
清除effect:不需要單獨的effect來執行清除操作,只需在effect中返回一個清除函數,React將會在執行清除操作時調用該函數
清除時機:React會在組件卸載時清理effect,由于effect默認每次渲染時都會執行,所以React會在調用一個新的 effect 之前對前一個 effect 進行清理
useEffect(() => {
// Todo
return () => {
// 清除effect操作
}
});
- useEffect會在瀏覽器繪制后、新的渲染前執行,因此不會阻止瀏覽器的頁面渲染
useContext
-
Context
為組件樹提供數據共享的方法,數據無需層層傳遞,即不需要該數據的中間層組件無需作為“快遞員”傳遞數據,避免props層層傳遞過于繁瑣、組件關系維護困難。具體見文檔
// 創建一個Context上下文
// defaultValue:初始共享值
const MyContext = React.createContext(defaultValue);
// Provider:提供者,提供共享數據
// value即共享數據
// 當value值發生變化時,Provider中所有使用該共享值的組件都會重新渲染
<MyContext.Provider value={xxx}>
</MyContext.Provider>
// Consumer:消費者,使用共享數據
<MyContext.Consumer>
{value => /* 基于 context 值進行渲染*/}
</MyContext.Consumer>
-
useContext
即為函數組件提供的獲取共享數據的Hook - 使用方法:
const value = useContext(MyContext);
參數:通過React.createContext
創建的上下文組件對象
返回值:該上下文的當前值,即距離當前組件最近
的 <MyContext.Provider>
的value
示例:
const animal = {
dog: {
name: 'Kiki',
age: 1
},
cat: {
name: 'Sara',
age: 2
}
}
// 創建上下文,傳入初始值
const PetContext = React.createContext(animal);
function App() {
return (
<PetContext.Provider value={animal.dog}>
<MyPet />
</PetContext.Provider>
);
}
// 使用共享數據的子組件
function MyPet() {
const pet = useContext(PetContext);
return (
<div>Hello, {pet.name}!</div> // Hello, Kiki!
);
}
- 使用Context的
Provider
組件包裹函數組件后,該函數組件才能共享上下文狀態
<PetContext.Provider value={animal.dog}>
<MyPet />
</PetContext.Provider>
- 當組件依賴的(上層距離最近的)
Provider
的value值發生變化時,useContext
會觸發重新渲染,獲取最新的value值 - 使用
useContext
相當于在函數組件
中訂閱Context上下文的變化
// 在class組件中的訂閱方式:
static contextType = MyContext
// 或
<MyContext.Consumer>
useReducer
-
useReducer
是useState
的替代方案,與Redux
中的reducer
功能類似,都是提供狀態改變功能;相對于useState
來說,useReducer
更適用于state邏輯復雜且包含多個子值(對象數組嵌套結構)的情景 - 使用方法:
const [state, dispatch] = useReducer(reducer, initialArg, init);
參數
-
reducer
:一個函數,接收當前狀態state
和觸發行為action
,返回計算后的新的狀態newState
(state, action) => newState
action
:觸發狀態改變的行為,是一個對象,包含:
type:表示行為的描述,如增、刪、改、查用戶等;
payload(可選):表示攜帶的數據,用于newState的計算
const action = {
type: 'addUser',
payload: {
name: 'Bobo',
sex: 0
}
}
注意:
- 不要直接修改state!因為它是
immutable
(React 使用 Object.is 來比較 state
)- 可以通過
解構賦值
創建newState并返回
舉個栗子:
function myReducer(state, action) {
const {type, payload} = action
if (type === 'addUser') {
return [
...state,
payload
]
}
if (type === 'removeUser') {
// 移除該用戶,并把新的用戶數組返回
// ...
}
// type的其他判斷...
return state
}
-
initialArg
:初始狀態值 -
init
:一個方法,用于惰性初始化state,當傳該方法時,初始值就變成了init(initialArg)
,可以用來重置初始狀態
返回值:包含兩個元素的數組,可解構
-
state
:當前狀態值 -
dispatch
:方法,用于觸發事件以更新state,參數為一個action對象,即將action中的payload
作為參數,執行type操作,更改狀態
// ...
const action = {
type: 'addUser',
payload: {
name: 'Bobo',
sex: 0
}
}
// ...
<button onClick={() => dispatch(action)}>點擊增加用戶</button>
- 跳過dispatch:若返回的state與當前的state相同,則不會進行子組件的重新渲染和執行副作用
-
useReducer
可以結合useContext
執行復雜的狀態更新操作。當組件嵌套比較深時,可以將dispatch
作為共享數據傳遞給子組件,這樣在子組件中就可以使用dispatch
觸發事件,更改狀態。參考此處
useCallback
-
useCallback
用于緩存回調函數,減少不必要的渲染 - 在組件第一次渲染的時候執行,之后會在其依賴的變量發生改變時再次執行,返回新的函數
- 使用方法:
useCallback(fn, deps)
參數:
-
fn
:函數 -
deps
:依賴項
返回值:緩存的函數
useMemo
- 同
useCallback
類似,useMemo
用于針對返回值進行緩存優化 - 在組件第一次渲染的時候執行,之后會在其依賴的變量發生改變時再次執行,返回新的值
- 使用方法:
useMemo(() => fn, deps)
參數:
-
fn
:函數 -
deps
:依賴項
返回值:緩存的值
useRef
- 先來了解一下Refs和DOM,在HTML元素或class組件上可以通過
React.createRef()
創建ref
引用該元素 - 在函數組件中引入了
useRef
,用于保存任何可變的值,每次都會返回相同的ref引用 - 使用方法:
const refContainer = useRef(initialValue);
參數:
-
initialValue
:初始值
返回值:可變的 ref 對象,其.current
屬性被初始化為傳入的參數initialValue