天天看點

React Hook 了解使用 useState、useEffect、useContext、useRef、自定義hookReact Hook

React Hook

react hooks

的出現,是對

react

中無狀态元件的一種更新,使得函數元件也能state 和 生命周期

React Hooks 要解決的問題是狀态共享,是繼 render-props(渲染屬性) 和 higher-order components(HOC;高階元件) 之後的第三種狀态邏輯複用方案,不會産生 JSX 嵌套地獄問題。

1. useState

hooks

的能力,就是讓我們在函數元件中使用

state

, 就是通過

useState

來實作的

UseState預設值

  • useState

    支援我們在調用的時候直接傳入一個值,來指定

    state

    的預設值,比如這樣

    useState(0)

    ,

    useState({ a: 1 })

    ,

    useState([ 1, 2 ])

  • 支援我們傳入一個函數,

    useState

    中的函數**隻會執行一次*

setUseState時

  • 擷取上一輪的值:使用

    useState

    的第二個參數時,我們想要擷取上一輪該

    state

    的值的話,隻需要在

    useState

    傳回的第二個參數,也就是我們上面的例子中的

    setCount

    使用時,傳入一個參數,該函數的參數就是上一輪的

    state

    的值
  • 更新值:setUseState必須傳入一個新的值與上次值不相等,否則将不會重新渲染(函數會繼續執行)
    setCount([...Array])
               

多個 useState 的情況

React依靠useState執行順序來識别useState,不可以打亂其執行順序即不能寫在判斷語句中

2. useEffect

Effect Hook

可以讓你在函數元件中執行副作用操作,這裡提到副作用,什麼是副作用呢,就是除了狀态相關的邏輯,比如網絡請求,監聽事件,查找

dom

useEffect

的第二個參數,有三種情況

  1. 什麼都不傳,元件每次 render 之後 useEffect 都會調用,相當于 componentDidMount 和 componentDidUpdate
  2. 傳入一個空數組 [], 隻會調用一次,相當于 componentDidMount 和 componentWillUnmount
  3. 傳入一個數組,其中包括變量,隻有這些變量任意一個變動時,useEffect 才會執行

3. useContext

Context

提供了一種方式,能夠讓資料在元件樹中傳遞時不必一級一級的手動傳遞

context

中的

Provider

Consumer

,在類元件和函數元件中都能使用,

contextType

隻能在類元件中使用,因為它是類的靜态屬性
import React, {createContext, useState, useContext} from 'react';

const Context = createContext(0)

function Parent () {
  const [ count, setCount ] = useState(0)
  return (
    <div>
      點選次數: { count } 
      <button onClick={() => { setCount(count + 1)}}>點我</button>
      <Context.Provider value={count}>
        <Child></Child>
      </Context.Provider>
    </div>
    )
}

function Child () {
  const count = useContext(Context);
  return (
    <div>{ count }</div>
  )
}

           

4. useMemo

根據依賴的值計算出結果(類似Vue計算屬性),當依賴的值未發生改變的時候,不觸發狀态改變

說白了React中 的

memo

就是函數元件的

PureComponent

useMemo

同理也是
function App () {
  const [ count, setCount ] = useState(0)
  const add = useMemo(() => count + 1 , [count])
  return (
    <div>
      點選次數: { count }<br/>
      次數加一: { add }<br/>
      <button onClick={() => { setCount(count + 1)}}>點我</button>
    </div>
  )
}
           

useMemo

也支援傳入第二個參數,用法和

useEffect

類似。需要注意的是,

useMemo

會在渲染的時候執行,而不是渲染之後執行,這一點和

useEffect

有差別,是以

useMemo

不建議有 副作用相關的邏輯
  1. 不傳數組,每次更新都會重新計算
  2. 空數組,隻會計算一次
  3. 依賴對應的值,當對應的值發生變化時,才會重新計算(可以依賴另外一個

    useMemo

    傳回的值)
const expensive = useMemo(() => {
  let sum = 0;
  for (let i = 0; i < count * 100; i++) {
    sum += i;
  }
  return sum;
}, [count]);
           

5. useCallback

可以說是

useMemo

的文法糖,能用

useCallback

實作的,都可以使用

useMemo

, 在 react 中我們經常面臨一個子元件渲染優化的問題,

useCallback

的第二個參數和

useMemo

一樣,沒有差別
useCallback(fn, deps) 相當于 useMemo(() => fn, deps)。
           

主要差別是

React.useMemo

将調用

fn

函數并傳回其結果,而

React.useCallback

将傳回

fn

函數而不調用它。

使用場景是:有一個父元件,其中包含子元件,子元件接收一個函數作為props;通常而言,如果父元件更新了,子元件也會執行更新;但是大多數場景下,更新是沒有必要的,我們可以借助useCallback來傳回函數,然後把這個函數作為props傳遞給子元件;這樣,子元件就能避免不必要的更新。

function Parent() {
  const [count, setCount] = useState(1)
  const [val, setVal] = useState('')
  const callback = useCallback(() => {return count}, [count])
  return (<div>
      <h4>{count}</h4>
      <Child callback={callback}/>
      <div>
          <button onClick={() => setCount(count + 1)}>+</button>
          <input value={val} onChange={event => setVal(event.target.value)}/>
      </div>
  </div>)
}

function Child({ callback }) {
  const [count, setCount] = useState(() => callback())
  useEffect(() => {setCount(callback())}, [callback])
  return <div>{count}</div>
}
           

6. useRef

useRef

的作用,總共有兩種用法
  1. 擷取子元件的執行個體(隻有類元件可用)
  2. 在函數元件中的一個全局變量,不會因為重複

    render

    重複申明, 類似于類元件的

    this.xxx

對于第2點:

有些情況下,我們需要保證函數元件每次 render 之後,某些變量不會被重複申明,比如說 Dom 節點,定時器的 id 等等,在類元件中,我們完全可以通過給類添加一個自定義屬性來保留,比如說 this.xxx, 但是函數元件沒有 this,自然無法通過這種方法使用,有的朋友說,我可以使用

useState 來保留變量的值,但是 useState 會觸發元件 render,在這裡完全是不需要的,我們就需要使用 useRef 來實作了,具體看下面例子

function App () {
  const [ count, setCount ] = useState(0)
  const timer = useRef(null)
  let timer2
  
  useEffect(() => {
    let id = setInterval(() => {setCount(count => count + 1)}, 500)
    timer.current = id
    timer2 = id
    return () => {clearInterval(timer.current)}
  }, [])

  const onClickRef = () => {clearInterval(timer.current)}
  const onClick = () => {clearInterval(timer2)}
  

  return (
    <div>
      點選次數: { count }
      <button onClick={onClick}>普通</button>
      <button onClick={onClickRef}>useRef</button>
    </div>
    )
}
           

當我們們使用普通的按鈕去暫停定時器時發現定時器無法清除,因為 App 元件每次 render,都會重新申明一次 timer2, 定時器的 id 在第二次 render 時,就丢失了,是以無法清除定時器,針對這種情況,就需要使用到 useRef,來為我們保留定時器 id,類似于 this.xxx,這就是 useRef 的另外一種用法

7. useReducer

useReducer

是什麼呢,它其實就是類似

redux

中的功能,相較于

useState

,它更适合一些邏輯較複雜且包含多個子值,或者下一個

state

依賴于之前的

state

等等的特定場景,

useReducer

總共有三個參數
  1. 一個 reducer,就是一個函數類似 (state, action) => newState 的函數,傳入 上一個 state 和本次的 action
  2. 初始 state,也就是預設值,是比較簡單的方法
  3. 惰性初始化,這麼做可以将用于計算 state 的邏輯提取到 reducer 外部,這也為将來對重置 state 的 action 做處理提供了便利
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 App() {
  const [state, dispatch] = useReducer(reducer, {count: 0});
  return (
    <>
      點選次數: {state.count}
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
    </>
  );
}
           

8. useEffect和useLayoutEffect的差別

useEffect是在渲染完成後執行,如果在渲染後執行DOM操作就可能出現元素的閃爍、瞬移,useLayoutEffect裡面的callback函數會在DOM更新完成後立即執行,但是會在浏覽器進行任何繪制之前運作完成,阻塞了浏覽器的繪制.

差別就在執行的時間不同:

DOM更新 ⇒ 渲染 ⇒ useEffect

DOM更新 ⇒ useLayoutEffect ⇒ 渲染

9. 自定義Hook

關于自定義hook使用:

當元件的整個邏輯可以抽離時候,适當的封裝起來,便于引用使用。打個比方,用React Hook 想做一個輸入框的UI元件,我們把邏輯抽離,對于UI元件的樣式就可以随便寫,有幾種樣式就能生成幾種不同的輸入框元件,隻需要引入自定義hook實作邏輯複用。

繼續閱讀