setState
const [state, setState] = useState(initialState)
setState(currentState)
setState((prevState) => f(prevState))
- 接受一個值或者一個函數
- 不會自動Merge,需要解構自己merge(或者使用useReducer)
- 懶init 如果initialState需要通過複雜計算獲得,可以提供一個函數
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props)
return initialState
})
- 如果update的值跟之前的值相同,React會渲染組件本身,但不會渲染子組件和相關的effects。用Object.is來比較二者。
- 如果在渲染時有昂貴的計算,則用
useMemo
來優化
useEffect
在函數式組件/render階段中不允許有狀態的改變,訂閱,定時器,日誌和其他引起副作用的方法。但可以使用useEffect
來做這些事情。
useEffect(asyncFn, state)
asyncFn會在render已經完成後執行。在純函數的世界裏,這是一個通往命令世界的逃生艙。
asyncFn需要返回一個清理side-effect的函數。該函數會在組件從UI中移除之前運行,以避免內存泄露。
useEffect(() => {
const subscription = props.source.subscribe()
return () => {
subscription.unsubscirbe()
}
})
effects的時機
爲了避免在每次更新的時候都會清理上次、重新訂閱,可以注意下effects的時機。不像componentDidMount
和componentDidUpdate
, 傳給useEffect的函數在佈局和繪製(layout and paint)之後被觸發,處於一個被延遲的事件中。這樣對許多訂閱和事件監聽都是適用的,但並非所有的effects都可以被稍後執行。例如,一個展現給用戶看的DOM更新必須在下一次繪製的時候同步執行,對這類的effects, react提供了一個特別的hook,叫做useLayoutEffect
。它有着與useEffect相同的函數簽名,只是觸發的時機不同。
儘管useEffect會在瀏覽器繪製之後延遲觸發,但是它一定會在下一次的render之前觸發。
條件觸發一次effect
useEffect的函數接受第二個參數,一個數組,只有裏面的值變動了,纔會重新重新觸發Effect函數。
確保數組中包括了組件的所有會隨着時間改變,並被effect使用的props, state值
- 如果只想觸發一次和清理一次(mount and unmont),可以傳一個空數組
useContext
const value = useContext(MyContext)
接受一個context對象(從React.createContext)中創建,並返回一個代表當前的context的值,這個值由最近的<MyContext.Provider>的value Prop決定。當該Provider更新的時候,這個hook就會觸發一次重新渲染,會根據最近的一次傳給MyContext的value屬性變化。
useContext(MyContext) 基本與在class中static contextType = MyContext類似,或者類似於<MyContext.Consumer>
useReducer
const [state, dispatch] = useReducer(reducer, initialArg, init)
因爲可以把dispatch方法傳下去代替回調,對觸發深層結構更新的組件會有效率上的優化
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({initialState}) {
const [state, dispatch] = useReducer(reducer, initialState)
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</>
)
}
需要自己管理自己的狀態,外部組件不需要知道里面組件的狀態
React沒有像Redux一樣直接使用
state = initailState
, 這是因爲可能傳入的inital value 是基於props的,所以沒有從內部自己定義。
懶初始化
傳入第三個參數, init函數,則初始值會被set 爲 init(initialArg)。這樣可以把計算初始state的邏輯從reducer中抽離出來,對根據action的結果重置state也非常方便。
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: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</>
);
}
useCallback
const memoizedCallback = useCallback(() => { doSomething(a, b)}, [a, b])
a, b是依賴項。將會返回一個callback的記憶版本,只有當依賴項有改變時,纔會改變。對根據引用相等進行了渲染優化後的子組件,傳入callback很有效果。
useCallback(fn, deps) === useMemo(() => fn, deps)
useMemo
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
傳給useMemo的函數會在渲染時執行。後續react可能會遺忘掉一些不在頁面上的組件的memo,重新計算它們。慎用。
useRef
const refContainer = useRef(initialValue)
返回一個可變的ref object,其.current屬性由傳入的參數初始化。返回的對象會在組件的生命週期內一直存在。
比如,明確命令式地獲取一個child。
function TextInputWithFocusButton() {
const inputEl = useRef(null)
const onButtonClick = () => {
inputEl.current.focus()
}
return (
<>
<input ref={inputEl} />
<button onClick = {onButtonClick}>focus the input</button>
</>
)
}
useImperativeHandle
useImperativeHandle(ref, createHandle, [dep1, dep2, …])
生成一個自動focus的輸入框子組件:
function FancyInput(props, ref) {
const inputRef = useRef()
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus()
}
}))
return <input ref={inputRef} ... />
}
FancyInput = forwardRef(FancyInput)
useLayoutEffect
如果要把代碼從class component遷移到函數式的,注意useLayoutEffect
會在componentDidMount
和componentDidUpdate
的時候每次都觸發。更推薦用useEffect.
如果使用了服務端渲染,注意無論是useLayoutEffect
還是useEffect
都會直到js被下載纔會執行。
useDebugValue
useDebugValue(value, (value) => computed(value))
用於在react dev tools中顯示該label, 在定義customHoook 共享庫的時候可以使用。第二個參數用於懶初始化。