React Hooks 全解(一)


寫在前面

什么是 React Hooks ?

來段官網的解釋: Hooks 是一種函數,該函數允許您從函數式組件 “勾住(hook into)” React 狀態和生命周期功能。 Hooks 在類內部不起作用 - 它們允許你無需類就使用 React。

因此,記住重點 1. Hooks 只能在函數組件內使用; 2. Hooks 用於擴充函數組件的功能,使函數組件可以完全代替類組件

React Hooks 都掛在 React 對象上,因此使用時為 React.useState() 的形式,若嫌麻煩,可以提前導入,如下:

import React, { useState } from "react"

React 內置的 Hooks 有很多,這里介紹一些常用到的。全部的請看 Hooks API

用到了 Hook 的函數組件名必須首字母大寫,否則會被 ESLint 報錯

1. useState

const [state, setState] = useState(initialState)

1.1 概念三連問

  • 調用 useState 有什么作用?
    useState 是用於聲明一個狀態變量的,用於為函數組件引入狀態。

  • 我們傳遞給 useState 的參數是什么?
    useState 只接收一個參數,這個參數可以是數字、字符串、對象等任意值,用於初始化聲明的狀態變量。也可以是一個返回初始值的函數,最好是函數,可在渲染時減少不必要的計算。

  • useState返回的是什么?
    它返回一個長度為2的讀寫數組,數組的第一項是定義的狀態變量本身,第二項是一個用來更新該狀態變量的函數,約定是 set 前綴加上狀態的變量名。如 setState,setState() 函數接收一個參數,該參數可以是更新后的具體值,也可以是一個返回更新后具體值的函數。若 setState 接收的是一個函數,則會將舊的狀態值作為參數傳遞給接收的函數然后得到一個更新后的具體狀態值。

1.2 舉個例子

function App(){
  const [n, setN] = useState(0)
  const [m, setM] = useState(() => 0)
  return (
    <div>
      n: {n}
      <button onClick={() => setN(n+1)}>+1</button>
      <br/>
      m: {m}
      <button onClick={() => setM(oldM => oldM+1)}>+1</button>
    </div>
  )
}

1.3 注意事項

  • useState Hook 中返回的 setState 並不會幫我們自動合並對象狀態的屬性
  • setState 中接收的對象參數如果地址沒變的話會被 React 認為沒有改變,因此不會引起視圖的更新

2. useReducer

useReducer 是 useState 的升級版。在 useState 中返回的寫接口中,我們只能傳遞最終的結果,在 setN 的內部也只是簡單的賦值操作。
也就是說,得到結果的計算過程需要我們在函數組件內的回調函數中書寫,這無疑增加了函數組件的體積,而且也不符合 Flux 的思想(狀態由誰產生的,誰負責進行各種處理,並暴露處理接口出去給別人用)

因此,React 就提供了比 useState 更高級的狀態管理 Hook:useReducer,介紹如下:

2.1 使用方法

  • 創建初始狀態值 initialState

  • 創建包含所有操作的 reducer(state, action) 函數,每種操作類型均返回新的 state 值

  • 根據 initialState 和 reducer 使用 const [state, dispatch] = useReducer(reducer, initialState) 得到讀寫 API

  • 調用寫接口,傳遞的參數均掛在 action 對象上

2.2 舉個例子

import React, { useReducer } from 'react';
import ReactDOM from 'react-dom';

const initialState = {
  n: 0
}

const reducer = (state, action) => {
  switch(action.type){
    case 'addOne':
      return { n: state.n + 1 }
    case 'addTwo':
      return { n: state.n + 2 }
    case 'addX':
      return { n: state.n + action.x }
    default: {
      throw new Error('unknown type')
    }
  }
}

function App(){
  const [state, dispatch] = useReducer(reducer, initialState)
  return (
    <div>
      我是 App
      {state.n}
      <button onClick={()=>dispatch({type: 'addOne'})}>+1</button>
      <button onClick={()=>dispatch({type: 'addTwo'})}>+2</button>
      <button onClick={()=>dispatch({type: 'addX', x: 5})}>+5</button>
    </div>
  )
}


ReactDOM.render(<App/>,document.getElementById('root'));

3. useContext

context 是上下文的意思,上下文是局部的全局變量這個局部的范圍由開發者自己指定。

3.1 使用方法

useContext 的使用方法分三步走:

  • 使用 const x = createContext(null) 創建上下文,在創建時一般不設置初始值,因此為 null,一般是在指定上下文作用域時初始化。

  • 使用 <x.Provider value={}></x.Provider> 圈定上下文的作用域

  • 在作用域中使用 const value = useContext(x) 使用上下文的數據

3.2 舉個例子

import React, { useState, createContext, useContext } from 'react';
import ReactDOM from 'react-dom';

const Context = createContext(null)

function App(){
  const [n, setN] = useState(0)
  return (
    <Context.Provider value={{n, setN}}>
      <div>
        <Baba />
        <Uncle />
      </div>
    </Context.Provider>
  )
}

function Baba(){
  return (
    <div>
      我是爸爸
      <Child />
    </div>
  )
}

function Uncle(){
  const {n, setN} = useContext(Context)
  return (
    <div>
      我是叔叔
      我拿到的 context 數據為 {n}
    </div>
  )
}

function Child(){
  const {n, setN} = useContext(Context)
  return (
    <div>
      我是兒子
      我拿到的 context 數據為 {n}
      <button onClick={() => setN(n+5)}>
        點擊改變 context 數據
      </button>
    </div>
  )
}


ReactDOM.render(<App/>,document.getElementById('root'));

4. useEffect

effect 是副作用的意思,對環境的改變就是副作用。副作用好像是函數式編程里的一個概念,這里不做過多解讀,也不太懂。
在 React 中,useEffect 就是在每次 render 后執行的操作,相當於 afterRender, 接收的第一個參數是回調函數,第二個參數是回調時機。可用在函數組件中模擬生命周期。

如果同時出現多個 useEffect ,會按出現順序依次執行

4.1 模擬 componentDidMount

useEffect(()=>{
  console.log('只在第一次 render 后執行')
},[])

4.2 模擬 componentDidMount + componentDidUpdate

useEffect(()=>{
   console.log('每次 render 后都執行,包括第一次 render')
})

4.3 可添加依賴

useEffect(()=>{
    console.log('只在 x 改變后執行,包括第一次 x 從 undefined 變成 initialValue')
},[x])
//如果有兩個依賴,則是當兩個依賴中的任何一個變化了都會執行

4.3 模擬 componentWillUnmount

useEffect(()=>{
  console.log('每次 render 后都執行,包括第一次 render')
  return ()=>{
    console.log('該組件要被銷毀了')
  }
})
//直接 return 一個函數即可,該函數會在組件銷毀前執行

5. useLayoutEffect

useEffect 總是在瀏覽器渲染完視圖過后才執行,如果 useEffect 里面的回調函數有對 DOM 視圖的操作,則會出現一開始是初始化的視圖,后來執行了 useEffect 里的回調后立馬改變了視圖的某一部分,會出現一個閃爍的狀態。
為了避免這種閃爍,可以將副作用的回調函數提前到瀏覽器渲染視圖的前面執行,當還沒有將 DOM 掛載到頁面顯示前執行 Effect 中對 DOM 進行操作的回調函數,則在瀏覽器渲染到頁面后不會出現閃爍的狀態。

layout 是視圖的意思,useLayoutEffect 就是在視圖顯示出來前執行的副作用。

useEffect 和 useLayoutEffect 就是執行的時間點不同,useLayoutEffect 是在瀏覽器渲染前執行,useEffect 是在瀏覽器渲染后執行。但二者都是在 render 函數執行過程中運行,useEffect 是在 render 完畢后執行,useLayoutEffect 是在 render 完畢前(視圖還沒渲染到瀏覽器頁面上)執行。

因此 useLayoutEffect 總是在 useEffect 前執行。

一般情況下,如果 Effect 中的回調函數中涉及到 DOM 視圖的改變,就應該用 useLayoutEffect,如果沒有,則用 useEffect。

6. useRef

useRef Hook 是用來定義一個在組件不斷 render 時保持不變的變量。
組件每次 render 后都會返回一個虛擬 DOM,組件內對應的變量都只屬於那個時刻的虛擬 DOM。
useRef Hook 就提供了創建貫穿整個虛擬 DOM 更新歷史的屬於這個組件的局部的全局變量。
為了確保每次 render 后使用 useRef 獲得的變量都能是之前的同一個變量,只能使用引用做到,因此,useRef 就將這個局部的全局變量的值存儲到了一個對象中,屬性名為:current

useRef 的 current 變化時不會自動 render

useRef 可以將創建的 Refs 對象通過 ref 屬性的方式引用到 DOM 節點或者 React 實例。這個作用在 React—ref 屬性 中有介紹。

同樣也可以作為組件的局部的全局變量使用,如下例的記錄當前是第幾次渲染頁面。

function App(){
  const [state, dispatch] = useReducer(reducer, initialState)
  const count = useRef(0)
  useEffect(()=>{
    count.current++;
    console.log(`這是第 ${count.current} 次渲染頁面`)
  })
  return (
    <div>
      我是 App
      {state.n}
      <button onClick={()=>dispatch({type: 'addOne'})}>+1</button>
      <button onClick={()=>dispatch({type: 'addTwo'})}>+2</button>
      <button onClick={()=>dispatch({type: 'addX', x: 5})}>+5</button>
    </div>
  )
}

7. forwardRef(不是 Hook)

forwardRef 主要是用來對原生的不支持 ref屬性 函數組件進行包裝使之可以接收 ref屬性 的,具體使用方法可參考 React—ref 屬性

forwardRef 接收一個函數組件,返回一個可以接收 ref 屬性的函數組件


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM