React基礎之Hook

本文旨在介紹常用的Hook的用途和用法。官方文檔地址

1. State Hook

  • useState

    • 功能: 在組件的頂層調(diào)用 useState 來聲明一個 狀態(tài)變量。

    • 使用示例

      import { useState } from 'react';
      // 聲明狀態(tài)變量
      const [age, setAge] = useState(28); // 唯一的參數(shù)就是初始化原始值。
      const [name, setName] = useState('Taylor');
      cosnt [fullName, setFullName] = useState({firstName: 'Tom', lastName: 'James'})
      const [todos, setTodos] = useState(() => createTodos()) // 用回調(diào)的形式填入 createTodos 的返回值
      
      const initArr = () => new Array(9).fill(null)
      const [list, setList] = useState(initArr) // 把函數(shù)本身當參數(shù)傳入。如果將函數(shù)傳遞給 useState,React 僅在初始化期間調(diào)用它。當組件重新渲染,它也不會再次運行。
      
      // 修改state
      setAge(age => age + 1) // nextState的更新函數(shù)。只接受待定的 state 作為其唯一參數(shù),并應返回下一個狀態(tài)。
      setName('Tom')
      setFullName({...fullName, firstName: 'Jerry'}) // 更新對象
      // 更新數(shù)組,新增項, 修改項, 刪除項。
      setTodos([
        ...todos,
        {
          id: id,
          title,
          done: false
        }
      ])
      setTodos(todos.filter(t => t.id !== todoId)) // 通過過濾返回刪除后剩余的項目。
      setTodos(tods.map(todo => todo.id !== nextTodo.id ? todo : nextTodo)) // 更新項
      
      // 調(diào)用狀態(tài)變量,每次setAge后,會觸發(fā)頁面的重新渲染。
      <div>{age} {fullName.firstName}</div>
      
    • 使用 Immer 來簡化寫法

      import {useImmer} from 'use-immer'
      const [todos, updateTodos] = useImmer([{name: 'Tom', age: 18}])
      updateTods(draft => {
        const obj = draft.find(item => item.name === 'Tom')
        obj.age = 19
      })
      
      
    • 使用 key 來重置狀態(tài)

      • 渲染列表 時,你經(jīng)常會遇到 key 屬性。然而,它還有另外一個用途。通過向組件傳遞不同的 key 來重置組件的狀態(tài)。

        const [count, setCount] = useState(0)
        const [version, setVersion] = useState(0)
        
        setVersion(version + 1)
        
        // count更新時視圖都會重新渲染,version變化時,不管count有沒有變化視圖都會重新渲染
        <div key={version}>{count}</div>
        
  • 存儲前一次渲染的信息

    • 在極為罕見的情況下,在組件渲染時調(diào)用 set 函數(shù)來基于已經(jīng)渲染的值更新狀態(tài)。
  • set函數(shù)執(zhí)行時機,是遇見set函數(shù)就推進執(zhí)行隊列,要當前作用域內(nèi)容執(zhí)行之后才會進行執(zhí)行隊列進行更新狀態(tài)值

    const [count, setCount] = useState(0)
    const handleClick = () => {
        // log(count) 0
        setCount(2)
        // log(count) 0
        setTimeout(() => {
            // log(count) 0
        })
    }
    // 解決辦法, 使用臨時變量
    const handleClick = () => {
        const nextCount = count + 1
        setCount(nextCount)
        // log(count) 0
        // log(nextCount) 1
    }
    
  • useReducer

  • 在組件的頂層作用域調(diào)用 useReducer 以創(chuàng)建一個用于管理狀態(tài)的 reducer。

  • 使用場景:批量更新 state時候使用。

  • useReducer的三個參數(shù)

    import {useReducer} from 'react'
    const [state, dispatch] = useReducer(reducer, initialArg, init?)
    
    • reducer: 用于更新 state 的純函數(shù)。參數(shù)為 state 和 action,返回值是更新后的 state。state 與 action 可以是任意合法值。
    • initialArg:用于初始化 state 的任意值。初始值的計算邏輯取決于接下來的 init 參數(shù)。
    • 可選參數(shù) init:用于計算初始值的函數(shù)。如果存在,使用 init(initialArg) 的執(zhí)行結(jié)果作為初始值,否則使用 initialArg。
  • dispatch 函數(shù): useReducer 返回的 dispatch 函數(shù)允許你更新 state 并觸發(fā)組件的重新渲染

  • 使用示例:

    import {useReducer, useState} from 'react'
    cosnt initData = [
      {id: 1, name: 'Tom', age: 18},
      {id: 2, name: 'Jerry', age: 16}
    ]
    /**
    * 聲明reducer函數(shù)
    * 兩個參數(shù),list是當前的state, action 可以是任意合法值,包含了要操作的類型和需要跟新的數(shù)據(jù)。
    * 返回值是 更新后的 state
    */
    const stuReducer (list, action) {
      switch (action.type) {
        case 'add': { // 往數(shù)組中添加數(shù)據(jù)
          return [
            ...list,
            {id: action.id, name: action.name, age: action.age}
          ]
        }
        case 'update': { // 更新數(shù)組中一項的數(shù)據(jù)
          return list.map(item => item.id === action.id ? ({id: action.id, name: action.name, age: action.age}) : item)
        }
        case 'delete': { // 過濾掉要刪除的數(shù)據(jù)
          return list.filter(item => item.id !== aciton.id)
        }
        default: {
          throw Error ('沒有找到對應的' + action.type)
        }
      }
    }
    
    const Demo = () => {
      /**
      * useReducer三個參數(shù)
      * 第一個參數(shù):stuReducer 用于更新 state 的純函數(shù)
      * 第二個參數(shù):initData 用于初始化 state 的任意值。
      * 補?。旱诙€參數(shù)initData也可以是一個函數(shù)的返回值比如formatArr(initData)。但是這樣的話每次更新state時候formatArr函數(shù)都會被調(diào)用,請把formatArr函數(shù)本身作為第三個參數(shù)傳入useReducer中。例如:useReducer(stuReducer, initData, formatArr)
      * 還有第三個可選參數(shù),這里省略了。是用于計算初始值的函數(shù)。
      * 
      */
      const [list, dispatch] = useReducer(stuReducer, initData)
      
      const oprateList = (type) {
        // 根據(jù)不同的type值調(diào)用reducer函數(shù)返回對應更新后的state.
        dispatch({
          type: type,
                id: 'xx',
          name: 'xx',
          age: 99
        })
        // dispatch之后打印一下list,發(fā)現(xiàn)還是更新之前的數(shù)據(jù),這里的原理與 useState 相同。這是因為 state 的行為和快照一樣。更新 state 會使用新的值來對組件進行重新渲染,但是不會改變當前執(zhí)行的事件處理函數(shù)里面 state 的值。
      }
      return <div>要渲染的內(nèi)容以及操作的按鈕內(nèi)容</div>
    }
    
    
  • reducer 和初始化函數(shù)運行了兩次

    • 嚴格模式 下 React 會調(diào)用兩次 reducer 和初始化函數(shù),但是這不應該會破壞你的代碼邏輯。
    • 這個 僅限于開發(fā)模式 的行為可以幫助你 保持組件純粹:React 會使用其中一次調(diào)用結(jié)果并忽略另一個結(jié)果。如果你的組件、初始化函數(shù)以及 reducer 函數(shù)都是純函數(shù),這并不會影響你的邏輯。不過一旦它們存在副作用,這個額外的行為就可以幫助你發(fā)現(xiàn)它。

2. Context Hook

  • useContext

    • 向組件樹深層傳遞數(shù)據(jù)。與prop的父子組件組件傳值相比,context可跨代進行數(shù)據(jù)傳遞。并能更新傳遞的數(shù)據(jù)。

    • 使用示例

      // createContext.ts
      // 第一步要創(chuàng)建context
      import {createContext} from 'react'
      const ThemeContext = createContext(null) // 也可以默認主體色 light createContext('light') 
      export default ThemeContext
      
      // 組件中調(diào)用context
      import {useContext, useState} from 'react'
      import ThemeContext from './createContext'
      const App = () => {
        const [theme, setTheme] = useState('dark')
        
        /**
        * value='dark' 修改了 themeContext的值
        * 如果這里的 value值 不輸入,則取的是 createContext的默認值。
        * 下面是通過使用state來初始化和更新 themeContext的值的示例。
        * <ThemeContext.Provider value={theme}><Form /></ThemeContext.Provider>
        * setTheme('light') 會更新themeContext的值
        */
        return (
          <ThemeContext.Provider value='dark'><Form /></ThemeContext.Provider>
        )
      }
      const Form = () => {
        const theme = useContext(ThemeContext)
        const className = `box-${theme}` // box-dark
        return <div className={className}></div>
      }
      
  • 補丁: 通過在 provider 中使用不同的值包裝樹的某個部分,可以覆蓋該部分的 context。

    <ThemeContext.Provider value="dark">
      ...
      <ThemeContext.Provider value="light">
        <Footer />
      </ThemeContext.Provider>
      ...
    </ThemeContext.Provider>
    

3. Ref Hook

  • useRef

    • 引用一個不需要渲染的值,通過ref操作 DOM,儲存定時器 interval ID。

      const ref = useRef(initialValue)
      # initialValue:ref 對象的 current 屬性的初始值。可以是任意類型的值。這個參數(shù)在首次渲染后被忽略。
      
    • 改變Ref不會觸發(fā)頁面的重新渲染

    • 使用示例

      import {useRef} from 'react'
      const MyApp = () => {
        const inputRef = useRef(null) // 引用input的元素
        const intervalRef = useRef(null)
        
        const handleClick = () => {
          const intervalRef = setInterval(() => {}, 10)
          // 清除定時器
          clearInterval(intervalRef.current)
          
          // 引用dom
          inputRef.current.focus()
        }
        
        return <>
          <input ref={inputRef} />
        </>
      }
      

4. Effect Hook

  • useEffect

    • 允許你 將組件與外部系統(tǒng)同步

      useEffect(setup, dependencies?)     
      
      • setup:處理 Effect 的函數(shù)。setup 函數(shù)選擇性返回一個 清理(cleanup) 函數(shù)。當組件被添加到 DOM 的時候,React 將運行 setup 函數(shù)。在每次依賴項變更重新渲染后,React 將首先使用舊值運行 cleanup 函數(shù)(如果你提供了該函數(shù)),然后使用新值運行 setup 函數(shù)。在組件從 DOM 中移除后,React 將最后一次運行 cleanup 函數(shù)。

      • 可選 dependenciessetup 代碼中引用的所有響應式值的列表。<font color='red'>響應式值包括 props、state 以及所有直接在組件內(nèi)部聲明的變量和函數(shù)</font>。如果你的代碼檢查工具 配置了 React,那么它將驗證是否每個響應式值都被正確地指定為一個依賴項。依賴項列表的元素數(shù)量必須是固定的,并且必須像 [dep1, dep2, dep3] 這樣內(nèi)聯(lián)編寫。React 將使用 Object.is 來比較每個依賴項和它先前的值。如果省略此參數(shù),則在每次重新渲染組件之后,將重新運行 Effect 函數(shù)。

        • 如果指定了依賴項,則 Effect 在 初始渲染后以及依賴項變更的重新渲染后 運行。

          useEffect(() => {
            // ...
          }, [a, b]); // 如果 a 或 b 不同則會再次運行
          
  - 如果你的 Effect 確實沒有使用任何響應式值,則它僅在 **初始渲染后** 運行。

    ```jsx
    useEffect(() => {
      // ...
    }, []); // 不會再次運行(開發(fā)環(huán)境下除外)
    ```

    

  - 如果完全不傳遞依賴數(shù)組,則 Effect 會在組件的 **每次單獨渲染(和重新渲染)之后** 運行。

    ```jsx
    useEffect(() => {
      // ...
    }); // 總是再次運行
    ```
  • useLayoutEffect

    • useLayoutEffectuseEffect 的一個版本,在瀏覽器重新繪制屏幕之前觸發(fā)。 可能會影響性能。<font color="red">謹慎使用</font>
    • 在瀏覽器重新繪制屏幕前計算布局。
  • useInsertionEffect

    • 是為 CSS-in-JS 庫的作者特意打造的。除非你正在使用 CSS-in-JS 庫并且需要注入樣式,否則你應該使用 useEffect 或者 useLayoutEffect。

5. 性能 Hook

  • useMemo
    • 它在每次重新渲染的時候能夠緩存計算的結(jié)果。
  • useCallback
    • 是一個允許你在多次渲染中緩存函數(shù)的 React Hook。
  • useTransition
    • 是一個幫助你在不阻塞 UI 的情況下更新狀態(tài)的 React Hook。
    • 它允許你在某些狀態(tài)更新時實現(xiàn)過渡效果,即這些更新可以有不同的優(yōu)先級,從而可以延遲一些不那么重要的更新,以便更快速地響應更關鍵的用戶交互。這個Hook的主要作用是在狀態(tài)更新時,提供了一種方式來“標記”某些更新為“過渡”,這意味著這些更新可能會延遲,以便React可以先處理其他更高優(yōu)先級的更新,如用戶的點擊事件。
    • 常用于優(yōu)化視圖切換時的用戶體驗。例如,當某個組件的渲染特別耗時,如Movie組件,如果在渲染該組件期間頁面的UI被阻塞,用戶會感覺頁面卡頓。通過使用useTransition,可以優(yōu)化這種情況下的用戶體驗。此外,useTransition還可以幫助你控制過渡效果的持續(xù)時間和延遲時間等參數(shù),使得用戶可以看到過渡效果,而不是直接看到新的組件,從而提高用戶體驗。
  • useDeferredValue
    • 可以讓你延遲更新 UI 的某些部分。
    • 可以將 useDeferredValue 作為性能優(yōu)化的手段。當你的 UI 某個部分重新渲染很慢、沒有簡單的優(yōu)化方法,同時你又希望避免它阻塞其他 UI 的渲染時,使用 useDeferredValue 很有幫助。

6. 資源 Hook

  • use 允許讀取像 Promisecontext 這樣的資源的值。僅在 Canary 與 experimental 渠道中可用

7. 其他 Hook

  • 使用 useDebugValue 自定義 React 開發(fā)者工具為自定義 Hook 添加的標簽。
  • 使用 useId 將唯一的 ID 與組件相關聯(lián),其通常與可訪問性 API 一起使用。
  • 使用 useSyncExternalStore 訂閱外部 store。

8. 自定義 Hook

  • 開發(fā)者可以 自定義 Hook 作為 JavaScript 函數(shù)。
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容