1. useState: 狀態鈎子
基礎用法
const [state, setState] = useState(initialState);
返回一個 state,以及更新 state 的函數。
在初始渲染期間,返回的狀態 (state) 與傳入的第一個參數 (initialState) 值相同。
setState 函數用於更新 state。它接收一個新的 state 值並將組件的一次重新渲染加入隊列。
函數式更新
如果新的 state 需要通過使用先前的 state 計算得出,那么可以將函數傳遞給 setState。該函數將接收先前的 state,並返回一個更新后的值。下面的計數器組件示例展示了 setState 的兩種用法:
import React, { useState } from "react";
function Counter({ initialCount }) {
const [count, setCount] = useState(initialCount);
return (
<>
Count: {count}
<button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={() => setCount((prevCount) => prevCount - 1)}>-</button>
<button onClick={() => setCount((prevCount) => prevCount + 1)}>+</button>
</>
);
}
function Parent() {
return <Counter initialCount={1} />;
}
export default Parent;
注意
與 class 組件中的 setState 方法不同,useState 不會自動合並更新對象。你可以用函數式的 setState 結合展開運算符來達到合並更新對象的效果。
setState(prevState => {
return {...prevState, ...updatedValues};
});
惰性初始 state
initialState 參數只會在組件的初始渲染中起作用,后續渲染時會被忽略。如果初始 state 需要通過復雜計算獲得,則可以傳入一個函數,在函數中計算並返回初始的 state,此函數只在初始渲染時被調用:
const getInitState = () => {
const initialState = someExpensiveComputation(props);
return initialState;
}
const [state, setState] = useState(getInitState);
2. useEffect():副作用鈎子
用法
useEffect()用來引入具有副作用的操作,最常見的就是向服務器請求數據。以前,放在componentDidMount、componentDidUpdate里面的代碼,現在可以放在useEffect()。
useEffect(() => {
// Async Action
}, [dependencies])
- 上面用法中,useEffect()接受兩個參數。第一個參數是一個函數,異步操作的代碼放在里面。第二個參數是一個數組,用於給出 Effect 的依賴項,只要這個數組發生變化,useEffect()就會執行。
- 第二個參數可以省略,這時每次組件渲染時,就會執行useEffect()。
- 如果想執行只運行一次的 effect(僅在組件掛載和卸載時執行),可以傳遞一個空數組([])作為第二個參數。這就告訴 React 你的 effect 不依賴於 props 或 state 中的任何值,所以它永遠都不需要重復執行。
舉例
const Person = ({ personId }) => {
const [loading, setLoading] = useState(true);
const [person, setPerson] = useState({});
useEffect(() => {
setLoading(true);
fetch(`/api/url`)
.then(data => {
setPerson(data);
setLoading(false);
});
}, [personId])
if (loading === true) {
return <p>Loading ...</p>
}
return (
<div>
<p>{person.name}</p>
</div>
)
}
上面代碼中,每當組件參數personId發生變化,useEffect()就會執行。組件第一次渲染時,useEffect()也會執行。
3. useContext():共享狀態鈎子
如果需要在組件之間共享狀態,可以使用useContext()。
現在有兩個組件 Navbar 和 Messages,我們希望它們之間共享狀態。
<div className="App">
<Navbar/>
<Messages/>
</div>
第一步就是使用 React Context API,在組件外部建立一個 Context。
const AppContext = React.createContext({});
組件封裝代碼如下。
<AppContext.Provider value={{
username: 'superawesome'
}}>
<div className="App">
<Navbar/>
<Messages/>
</div>
</AppContext.Provider>
上面代碼中,AppContext.Provider提供了一個 Context 對象,這個對象可以被子組件共享。
Navbar 組件的代碼如下。
const Navbar = () => {
const { username } = useContext(AppContext);
return (
<div className="navbar">
<p>AwesomeSite</p>
<p>{username}</p>
</div>
);
}
上面代碼中,useContext()鈎子函數用來引入 Context 對象,從中獲取username屬性。
Message 組件的代碼也類似。
const Messages = () => {
const { username } = useContext(AppContext)
return (
<div className="messages">
<h1>Messages</h1>
<p>1 message for {username}</p>
<p className="message">useContext is awesome!</p>
</div>
)
}
4. useReducer():action 鈎子
用法
useReducer 可以作為 useState 的替代方案。它接收一個形如 (state, action) => newState 的 reducer,並返回當前的 state 以及與其配套的 dispatch 方法。
const [state, dispatch] = useReducer(reducer, initialState);
以下是用 reducer 重寫 useState 一節的計數器示例:
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
惰性初始化
可以選擇惰性地創建初始 state。為此,需要將 init 函數作為 useReducer 的第三個參數傳入,這樣初始 state 將被設置為 init(initialArg)。
這么做可以將用於計算 state 的邏輯提取到 reducer 外部,這也為將來對重置 state 的 action 做處理提供了便利:
function init(initialCount) {
return {count: initialCount};
}
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
case 'reset':
return init(action.payload);
default:
throw new Error();
}
}
function Counter({initialCount}) {
const [state, dispatch] = useReducer(reducer, initialCount, init);
return (
<>
Count: {state.count}
<button
onClick={() => dispatch({type: 'reset', payload: initialCount})}>
Reset
</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
5. useMemo()
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
把“創建”函數和依賴項數組作為參數傳入 useMemo,它僅會在某個依賴項改變時才重新計算 memoized 值。這種優化有助於避免在每次渲染時都進行高開銷的計算。
例如:
const Child = memo(({ data }) => {
console.log("子組件 render...", data.name);
return (
<div>
<div>child</div>
<div>{data.name}</div>
</div>
);
});
const Parent = () => {
console.log("父組件 render...");
const [count, setCount] = useState(0);
const [name, setName] = useState("haha");
const data = {
name,
};
return (
<div>
<div>{count}</div>
<button onClick={() => setCount(count + 1)}> 更新計數+1 </button>
<Child data={data} />
</div>
);
};
export default Parent;
當點擊按鈕更新count的時候,Parent組件會render,一旦render, 執行到這一行代碼:
const data = {
name,
};
這一行代碼會生成有新的內存地址的對象,那么就算帶着memo的Child組件,也會跟着重新render, 盡管最后其實Child使用到的值沒有改變。
這樣Child就多余render了,造成性能浪費,於是useMemo 作為一個有着暫存能力的鈎子,就派上用場了。
const Child = memo(({ data }) => {
console.log("子組件 render...", data.name);
return (
<div>
<div>child</div>
<div>{data.name}</div>
</div>
);
});
const Parent = () => {
console.log("父組件 render...");
const [count, setCount] = useState(0);
const [name, setName] = useState("haha");
const data = useMemo(() => {
return {
name,
};
}, [name]);
return (
<div>
<div>{count}</div>
<button onClick={() => setCount(count + 1)}> 更新計數+1 </button>
<Child data={data} />
</div>
);
};
當執行到代碼:
const data = useMemo(() => {
return {
name,
};
}, [name]);
會先根據[name]里面的name值判斷一下,因為useMemo 作為一個有着暫存能力的,暫存了上一次的name結果。如果name值沒有變化,那么data就不會重新賦值,沒有新的對象,就沒有新的內存地址,所以 Child 就不會重新render了。
6. useCallback
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
useCallback 和 useMemo 都可緩存函數的引用或值,但是從更細的使用角度來說 useCallback 緩存函數的引用,useMemo 緩存計算數據的值。
useCallback(fn, deps)
相當於 useMemo(() => fn, deps)
。
7. useRef
const refContainer = useRef(initialValue);
useRef 返回一個可變的 ref 對象,其 .current 屬性被初始化為傳入的參數(initialValue)。返回的 ref 對象在組件的整個生命周期內保持不變。
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` 指向已掛載到 DOM 上的文本輸入元素
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
參考:http://www.ruanyifeng.com/blog/2019/09/react-hooks.html; https://juejin.im/post/5e53d9116fb9a07c9070da44#heading-9