如果你之前對於Hooks
沒有了解,那麼你可能需要看下概述部分。你或許也可以在一些常見的問題中找到有用的資訊。
Baisc Hooks
useState
const [state, setState] = useState(initialState);
複製程式碼
返回有狀態值,以及更新這個狀態值的函式。
在初始渲染的時候,返回的狀態(state
)與作為第一個引數(initialState
)傳遞的值相同。
setState
函式用於更新state
。setState
接受一個新的狀態值,並將元件的重新渲染排入佇列。
setState(newState);
複製程式碼
在後續重新渲染期間,useState
返回的第一個值將始終是應用更新後的最新狀態。
作為功能更新
如果更新狀態需要用到前面的狀態,那可以傳遞一個函式給setXxxx
,類似於類元件中的setState
。這個函式可以接收先前的值,然後返回更新之後的值。
function Counter({initialCount}) {
const [count, setCount] = useState(initialCount);
return (
<>
Count: {count}
<button onClick={() : setCount(0)}>Reset</button>
<button onClick={() : setCount(prevCount : prevCount + 1)}>+</button>
<button onClick={() : setCount(prevCount : prevCount - 1)}>-</button>
</>
);
}
複製程式碼
就像這樣,給setCount
傳遞一個函式,這個函式接收一個引數(上一個狀態值),最後返回新的狀態值用以setCount
。
注意: 與類元件中的
setState
方法不同,useState
不會自動合併更新物件。 但是你可以自己做到這點:
setXxxx(prevState : { // Object.assign would also work return {...prevState, ...updatedValues}; }); 複製程式碼
另一個選項是使用
useReducer
,它更適合管理包含多個子值的狀態物件。
延遲初始化
initialState
引數是初始渲染期間使用的狀態。在隨後的渲染中,它被忽略了。如果初始狀態是複雜計算的結果,則可以改為提供函式,該函式僅在初始渲染時執行:
const [state, setState] = useState(() : {
const initialState = someExpensiveComputation(props);
return initialState;
});
複製程式碼
useEffect
useEffect(didUpdate);
複製程式碼
接受包含命令式,可能有副作用程式碼的函式。
函式元件的主體內部不允許發生改變,訂閱,計時器,日誌記錄和其他副作用(稱為React
的渲染階段)。這樣做會導致UI
中的錯誤和不一致性混亂。
相反,使用useEffect
。傳遞給useEffect
的函式將在渲染結束後執行。將效果(副作用)視為從React
的純函式進入命令式的逃脫艙。
預設情況下,效果在每次完成渲染後執行,但是你可以選擇僅在某些值發生更改時觸發它。前面介紹effect hook時有提到,本文下面仍然會詳細介紹。
清理effect
通常,效果會建立一些在元件解除安裝時需要清理的資源,例如訂閱或計時器ID。為此,傳遞給useEffect
的函式可能會返回一個清理函式。例如,要建立訂閱:
useEffect(() : {
const subscription = props.source.subscribe();
return () : {
// Clean up the subscription
subscription.unsubscribe();
};
});
複製程式碼
返回的清除函式在從UI中解除安裝元件之前執行,以防止記憶體洩漏。此外,如果元件呈現多次(通常如此),則在執行下一個效果之前會清除先前的效果。 在我們的示例中,這意味著每次更新都會建立一個新訂閱。要避免在每次更新時觸發效果,請繼續往下看。
effect的觸發時間
與componentDidMount
和componentDidUpdate
不同,傳遞給useEffect
的函式在延遲事件期間在佈局和繪製後觸發。這使得它適用於許多常見的副作用,例如設定訂閱和事件處理程式,因為大多數型別的工作不應阻止瀏覽器更新螢幕。
但是,並非所有效果都可以推遲。例如,使用者可見的DOM改變必須在下一次繪製之前同步觸發,以便使用者不會感覺到視覺上的不一致。對於這些型別的效果,React
提供了兩個額外的Hook:useMutationEffect和useLayoutEffect。這些Hook
與useEffect
具有相同的api
,並且僅在觸發時有所不同。
雖然useEffect
會延遲到瀏覽器繪製完成之後,但它保證在任何新渲染之前觸發,也就是說在開始新的更新之前,React
將始終重新整理先前渲染的效果。
條件控制的effect
效果的預設行為是在每次完成渲染後觸發效果。這樣,如果其中一個輸入發生變化,則始終會重新建立效果。
但是,在某些情況下,這可能是不需要的,例如上一節中的訂閱示例。僅當source prop
已更改時,我們無需在每次更新時建立新訂閱。
要實現此功能,請將第二個引數傳遞給useEffect
,它是效果所依賴的值陣列。我們更新的示例現在看起來像這樣:
useEffect(
() : {
const subscription = props.source.subscribe();
return () : {
subscription.unsubscribe();
};
},
[props.source],
);
複製程式碼
現在只有在 props.source
更改時才會重新建立訂閱。傳入一個空陣列[]輸入告訴React
你的效果不依賴於元件中的任何值,因此該效果僅在mount
和unmount
上執行,從不在更新時執行。
注意 輸入陣列不作為引數傳遞給效果函式。 但從概念上講,這就是它們所代表的內容:效果函式中引用的每個值也應出現在輸入陣列中,這樣才有意義。並且從之前可以得知,只要陣列裡的內容有一個不同,那就會再次呼叫這個效果。
useContext
const context = useContext(Context);
複製程式碼
接受上下文物件(從React.createContext
返回的值)並返回當前上下文值,由給定上下文的最近上下文提供程式給出。
當提供程式更新時,此Hook
將使用最新的上下文值觸發重新呈現。
其他的鉤子
以下鉤子可以是上一節中基本鉤子的變體,也可以僅用於特定邊緣情況。不強調預先學習它們。
useReducer
const [state, dispatch] = useReducer(reducer, initialState);
複製程式碼
useState的替代方案。接受型別為(state,action) : newState
的reducer
,並返回與dispatch
方法配對的當前狀態。 (如果熟悉Redux
,你已經知道它是如何工作的。)
這是useState
部分的計數器示例,用reducer
重寫:
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'reset':
return initialState;
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
}
}
function Counter({initialCount}) {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() : dispatch({type: 'reset'})}>
Reset
</button>
<button onClick={() : dispatch({type: 'increment'})}>+</button>
<button onClick={() : dispatch({type: 'decrement'})}>-</button>
</>
);
}
複製程式碼
延遲初始化
useReducer
接受可選的第三個引數initialAction
。如果提供,則在初始渲染期間應用初始操作。這對於計算包含通過props傳遞的值的初始狀態非常有用:
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'reset':
return {count: action.payload};
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
}
}
function Counter({initialCount}) {
const [state, dispatch] = useReducer(
reducer,
initialState,
{type: 'reset', payload: initialCount},
);
return (
<>
Count: {state.count}
<button
onClick={() : dispatch({type: 'reset', payload: initialCount})}>
Reset
</button>
<button onClick={() : dispatch({type: 'increment'})}>+</button>
<button onClick={() : dispatch({type: 'decrement'})}>-</button>
</>
);
}
複製程式碼
當你具有涉及多個子值的複雜狀態邏輯時,useReducer
通常優於useState
。它還允許你優化觸發深度更新的元件的效能,因為你可以傳遞排程而不是回撥。
useCallback
const memoizedCallback = useCallback(
() : {
doSomething(a, b);
},
[a, b],
);
複製程式碼
返回一個memoized回撥。
傳遞內聯回撥和一組輸入。 useCallback
將返回一個回憶的memoized
版本,該版本僅在其中一個輸入發生更改時才會更改。當將回撥傳遞給依賴於引用相等性的優化子元件以防止不必要的渲染(例如,shouldComponentUpdate
)時,這非常有用。
useCallback(fn,inputs) 等效 useMemo(() : fn,inputs)。
注意 輸入陣列不作為引數傳遞給回撥。但從概念上講,這就是它們所代表的內容:回撥中引用的每個值也應出現在輸入陣列中。將來,一個足夠先進的編譯器可以自動建立這個陣列。類似於上面提到的
effect
第二個引數。
useMemo
const memoizedValue = useMemo(() : computeExpensiveValue(a, b), [a, b]);
複製程式碼
返回一個memoized
值。
傳遞“建立”功能和輸入陣列。 useMemo
只會在其中一個輸入發生更改時重新計算memoized
值。此優化有助於避免在每個渲染上進行昂貴的計算。
如果未提供陣列,則只要將新函式例項作為第一個引數傳遞,就會計算新值。 (使用行內函數,在每個渲染上。)
注意: 輸入陣列不作為引數傳遞給函式。但從概念上講,這就是它們所代表的內容:函式內部引用的每個值也應出現在輸入陣列中。
useRef
const refContainer = useRef(initialValue);
複製程式碼
useRef
返回一個可變的ref
物件,其.current
屬性被初始化為傳遞的引數(initialValue
)。返回的物件將持續整個元件的生命週期。
一個常見的用例是強制訪問child
:
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () : {
// `current` points to the mounted text input element
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
複製程式碼
請注意,useRef
比ref
屬性更有用。保持任何可變值的方式類似於在類中使用例項欄位的方法。
useImperativeMethods
useImperativeMethods(ref, createInstance, [inputs])
複製程式碼
useImperativeMethods
自定義使用ref
時公開給父元件的例項值。與往常一樣,在大多數情況下應避免使用refs
的命令式程式碼。 useImperativeMethods
應與forwardRef
一起使用:
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeMethods(ref, () : ({
focus: () : {
inputRef.current.focus();
}
}));
return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);
複製程式碼
在此示例中,呈現<FancyInput ref = {fancyInputRef} />
的父元件將能夠呼叫fancyInputRef.current.focus()
。
useMutationEffect
api
與useEffect
相同,但在更新兄弟元件之前,它在React
執行其DOM改變的同一階段同步觸發。使用它來執行自定義DOM改變。
在可能的情況下首選標準useEffect
以阻止可見的更新。
注意 避免在
useMutationEffect
中讀取DOM。在讀取計算樣式或佈局資訊時,useLayoutEffect
更合適。
useLayoutEffect
api
與useEffect
相同,但在所有DOM改變後同步觸發。使用它來從DOM讀取佈局並同步重新渲染。在瀏覽器有機會繪製之前,將在useLayoutEffect
內部計劃的更新將同步重新整理。
在可能的情況下首選標準useEffect
以阻止視覺更新。
提示 如果你正在從類元件遷移程式碼,則
useLayoutEffect
會在與componentDidMount
和componentDidUpdate
相同的階段觸發,因此如果你不確定Hook
要使用哪種效果,則他可能風險最小。