React基础之Hook

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

1. State Hook

  • useState

    • 功能: 在组件的顶层调用 useState 来声明一个 状态变量

    • 使用示例

      import { useState } from 'react';
      // 声明状态变量
      const [age, setAge] = useState(28); // 唯一的参数就是初始化原始值。
      const [name, setName] = useState('Taylor');
      cosnt [fullName, setFullName] = useState({firstName: 'Tom', lastName: 'James'})
      const [todos, setTodos] = useState(() => createTodos()) // 用回调的形式填入 createTodos 的返回值
      
      const initArr = () => new Array(9).fill(null)
      const [list, setList] = useState(initArr) // 把函数本身当参数传入。如果将函数传递给 useState,React 仅在初始化期间调用它。当组件重新渲染,它也不会再次运行。
      
      // 修改state
      setAge(age => age + 1) // nextState的更新函数。只接受待定的 state 作为其唯一参数,并应返回下一个状态。
      setName('Tom')
      setFullName({...fullName, firstName: 'Jerry'}) // 更新对象
      // 更新数组,新增项, 修改项, 删除项。
      setTodos([
        ...todos,
        {
          id: id,
          title,
          done: false
        }
      ])
      setTodos(todos.filter(t => t.id !== todoId)) // 通过过滤返回删除后剩余的项目。
      setTodos(tods.map(todo => todo.id !== nextTodo.id ? todo : nextTodo)) // 更新项
      
      // 调用状态变量,每次setAge后,会触发页面的重新渲染。
      <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 来重置状态

      • 渲染列表 时,你经常会遇到 key 属性。然而,它还有另外一个用途。通过向组件传递不同的 key 来重置组件的状态

        const [count, setCount] = useState(0)
        const [version, setVersion] = useState(0)
        
        setVersion(version + 1)
        
        // count更新时视图都会重新渲染,version变化时,不管count有没有变化视图都会重新渲染
        <div key={version}>{count}</div>
        
  • 存储前一次渲染的信息

    • 在极为罕见的情况下,在组件渲染时调用 set 函数来基于已经渲染的值更新状态。
  • set函数执行时机,是遇见set函数就推进执行队列,要当前作用域内容执行之后才会进行执行队列进行更新状态值

    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

  • 在组件的顶层作用域调用 useReducer 以创建一个用于管理状态的 reducer

  • 使用场景:批量更新 state时候使用。

  • useReducer的三个参数

    import {useReducer} from 'react'
    const [state, dispatch] = useReducer(reducer, initialArg, init?)
    
    • reducer: 用于更新 state 的纯函数。参数为 state 和 action,返回值是更新后的 state。state 与 action 可以是任意合法值。
    • initialArg:用于初始化 state 的任意值。初始值的计算逻辑取决于接下来的 init 参数。
    • 可选参数 init:用于计算初始值的函数。如果存在,使用 init(initialArg) 的执行结果作为初始值,否则使用 initialArg
  • dispatch 函数: useReducer 返回的 dispatch 函数允许你更新 state 并触发组件的重新渲染

  • 使用示例:

    import {useReducer, useState} from 'react'
    cosnt initData = [
      {id: 1, name: 'Tom', age: 18},
      {id: 2, name: 'Jerry', age: 16}
    ]
    /**
    * 声明reducer函数
    * 两个参数,list是当前的state, action 可以是任意合法值,包含了要操作的类型和需要跟新的数据。
    * 返回值是 更新后的 state
    */
    const stuReducer (list, action) {
      switch (action.type) {
        case 'add': { // 往数组中添加数据
          return [
            ...list,
            {id: action.id, name: action.name, age: action.age}
          ]
        }
        case 'update': { // 更新数组中一项的数据
          return list.map(item => item.id === action.id ? ({id: action.id, name: action.name, age: action.age}) : item)
        }
        case 'delete': { // 过滤掉要删除的数据
          return list.filter(item => item.id !== aciton.id)
        }
        default: {
          throw Error ('没有找到对应的' + action.type)
        }
      }
    }
    
    const Demo = () => {
      /**
      * useReducer三个参数
      * 第一个参数:stuReducer 用于更新 state 的纯函数
      * 第二个参数:initData 用于初始化 state 的任意值。
      * 补丁:第二个参数initData也可以是一个函数的返回值比如formatArr(initData)。但是这样的话每次更新state时候formatArr函数都会被调用,请把formatArr函数本身作为第三个参数传入useReducer中。例如:useReducer(stuReducer, initData, formatArr)
      * 还有第三个可选参数,这里省略了。是用于计算初始值的函数。
      * 
      */
      const [list, dispatch] = useReducer(stuReducer, initData)
      
      const oprateList = (type) {
        // 根据不同的type值调用reducer函数返回对应更新后的state.
        dispatch({
          type: type,
                id: 'xx',
          name: 'xx',
          age: 99
        })
        // dispatch之后打印一下list,发现还是更新之前的数据,这里的原理与 useState 相同。这是因为 state 的行为和快照一样。更新 state 会使用新的值来对组件进行重新渲染,但是不会改变当前执行的事件处理函数里面 state 的值。
      }
      return <div>要渲染的内容以及操作的按钮内容</div>
    }
    
    
  • reducer 和初始化函数运行了两次

    • 严格模式 下 React 会调用两次 reducer 和初始化函数,但是这不应该会破坏你的代码逻辑。
    • 这个 仅限于开发模式 的行为可以帮助你 保持组件纯粹:React 会使用其中一次调用结果并忽略另一个结果。如果你的组件、初始化函数以及 reducer 函数都是纯函数,这并不会影响你的逻辑。不过一旦它们存在副作用,这个额外的行为就可以帮助你发现它。

2. Context Hook

  • useContext

    • 向组件树深层传递数据。与prop的父子组件组件传值相比,context可跨代进行数据传递。并能更新传递的数据。

    • 使用示例

      // createContext.ts
      // 第一步要创建context
      import {createContext} from 'react'
      const ThemeContext = createContext(null) // 也可以默认主体色 light createContext('light') 
      export default ThemeContext
      
      // 组件中调用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 属性的初始值。可以是任意类型的值。这个参数在首次渲染后被忽略。
      
    • 改变Ref不会触发页面的重新渲染

    • 使用示例

      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

    • 允许你 将组件与外部系统同步

      useEffect(setup, dependencies?)     
      
      • setup:处理 Effect 的函数。setup 函数选择性返回一个 清理(cleanup) 函数。当组件被添加到 DOM 的时候,React 将运行 setup 函数。在每次依赖项变更重新渲染后,React 将首先使用旧值运行 cleanup 函数(如果你提供了该函数),然后使用新值运行 setup 函数。在组件从 DOM 中移除后,React 将最后一次运行 cleanup 函数。

      • 可选 dependenciessetup 代码中引用的所有响应式值的列表。<font color='red'>响应式值包括 props、state 以及所有直接在组件内部声明的变量和函数</font>。如果你的代码检查工具 配置了 React,那么它将验证是否每个响应式值都被正确地指定为一个依赖项。依赖项列表的元素数量必须是固定的,并且必须像 [dep1, dep2, dep3] 这样内联编写。React 将使用 Object.is 来比较每个依赖项和它先前的值。如果省略此参数,则在每次重新渲染组件之后,将重新运行 Effect 函数。

        • 如果指定了依赖项,则 Effect 在 初始渲染后以及依赖项变更的重新渲染后 运行。

          useEffect(() => {
            // ...
          }, [a, b]); // 如果 a 或 b 不同则会再次运行
          
  - 如果你的 Effect 确实没有使用任何响应式值,则它仅在 **初始渲染后** 运行。

    ```jsx
    useEffect(() => {
      // ...
    }, []); // 不会再次运行(开发环境下除外)
    ```

    

  - 如果完全不传递依赖数组,则 Effect 会在组件的 **每次单独渲染(和重新渲染)之后** 运行。

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

    • useLayoutEffectuseEffect 的一个版本,在浏览器重新绘制屏幕之前触发。 可能会影响性能。<font color="red">谨慎使用</font>
    • 在浏览器重新绘制屏幕前计算布局。
  • useInsertionEffect

    • 是为 CSS-in-JS 库的作者特意打造的。除非你正在使用 CSS-in-JS 库并且需要注入样式,否则你应该使用 useEffect 或者 useLayoutEffect

5. 性能 Hook

  • useMemo
    • 它在每次重新渲染的时候能够缓存计算的结果。
  • useCallback
    • 是一个允许你在多次渲染中缓存函数的 React Hook。
  • useTransition
    • 是一个帮助你在不阻塞 UI 的情况下更新状态的 React Hook。
    • 它允许你在某些状态更新时实现过渡效果,即这些更新可以有不同的优先级,从而可以延迟一些不那么重要的更新,以便更快速地响应更关键的用户交互。这个Hook的主要作用是在状态更新时,提供了一种方式来“标记”某些更新为“过渡”,这意味着这些更新可能会延迟,以便React可以先处理其他更高优先级的更新,如用户的点击事件。
    • 常用于优化视图切换时的用户体验。例如,当某个组件的渲染特别耗时,如Movie组件,如果在渲染该组件期间页面的UI被阻塞,用户会感觉页面卡顿。通过使用useTransition,可以优化这种情况下的用户体验。此外,useTransition还可以帮助你控制过渡效果的持续时间和延迟时间等参数,使得用户可以看到过渡效果,而不是直接看到新的组件,从而提高用户体验。
  • useDeferredValue
    • 可以让你延迟更新 UI 的某些部分。
    • 可以将 useDeferredValue 作为性能优化的手段。当你的 UI 某个部分重新渲染很慢、没有简单的优化方法,同时你又希望避免它阻塞其他 UI 的渲染时,使用 useDeferredValue 很有帮助。

6. 资源 Hook

  • use 允许读取像 Promisecontext 这样的资源的值。仅在 Canary 与 experimental 渠道中可用

7. 其他 Hook

  • 使用 useDebugValue 自定义 React 开发者工具为自定义 Hook 添加的标签。
  • 使用 useId 将唯一的 ID 与组件相关联,其通常与可访问性 API 一起使用。
  • 使用 useSyncExternalStore 订阅外部 store。

8. 自定义 Hook

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 225,208评论 6 524
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 96,502评论 3 405
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 172,496评论 0 370
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 61,176评论 1 302
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 70,185评论 6 401
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,630评论 1 316
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,992评论 3 431
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,973评论 0 280
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 47,510评论 1 325
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 39,546评论 3 347
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,659评论 1 355
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 37,250评论 5 351
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,990评论 3 340
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 33,421评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,569评论 1 277
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 50,238评论 3 382
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,732评论 2 366

推荐阅读更多精彩内容