React hooks API小結

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的時機。不像componentDidMountcomponentDidUpdate, 傳給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會在componentDidMountcomponentDidUpdate的時候每次都觸發。更推薦用useEffect.
如果使用了服務端渲染,注意無論是useLayoutEffect還是useEffect都會直到js被下載纔會執行。

useDebugValue

useDebugValue(value, (value) => computed(value))
用於在react dev tools中顯示該label, 在定義customHoook 共享庫的時候可以使用。第二個參數用於懶初始化。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章