• React18中各种Hooks用法总结( 内附案例讲解)


    React中各种Hooks用法总结

    内附案例讲解

    一、useState

    useState 是一个 React Hook,它允许你向组件添加一个 状态变量

    import React, { FC, memo, useState } from 'react'
    import { MainContainer } from './style'
    interface IProps {
      children?: React.ReactNode
    }
    const Main: FC = (props) => {
      const [age, setAge] = useState(0)
      return (
        
          

    今年我:{age}岁了!

    ) } export default memo(Main)

    React中的 useState 其实就相当于Vue中的ref()和reactive()

    React不同于Vue,在Vue中数据都是使用响应式对象其实就是一个 Proxy,Vue会对数据进行劫持,当数据发生改变的时候,Vue会调用Render函数对视图进行更新,But React你如果想触发Render函数你必须使用setAge进行修改数据

    React.js 使用一种称为虚拟 DOM(Virtual DOM)的机制来实现高效的响应式更新。当状态(State)发生变化时,React.js 会重新计算虚拟 DOM 树,并与之前的虚拟 DOM 树进行比较,找出需要更新的部分,然后仅更新这些部分到实际 DOM。这样可以减少对实际 DOM 的操作,提高性能。(所以说当你调用setAge后它会进行一个比较然后跟新数据)

    另外Setstate是异步更新的有兴趣的话可以了解一下不展开说了!

    二、useRef

    useRef 是一个 React Hook,它能帮助引用一个不需要渲染的值。

    useRef 是 React 提供的一个 Hook,用于在函数组件中持有一个可变的引用值,该值在组件的整个生命周期中保持不变,而不会引发重新渲染。

    • 持有 DOM 引用

      useEffect——后面会讲解先别管!

      import React, { FC, memo, useRef, useEffect } from 'react'
      import { MainContainer } from './style'
      interface IProps {
        children?: React.ReactNode
      }
      const Main: FC = (props) => {
        const inputRef = useRef(null)
        useEffect(() => {
          if (inputRef.current) {
            inputRef.current.focus()
          }
        }, [])
        return (
          
            
          
        )
      }
      export default memo(Main)
      
    • 持有可变值

      import React, { FC, memo, useRef } from 'react'
      import { MainContainer } from './style'
      
      interface IProps {
        children?: React.ReactNode
      }
      
      const Main: FC = (props) => {
        const countRef = useRef(0) // 使用 useRef 创建一个可变值引用
      
        const handleClick = () => {
          countRef.current += 1 // 修改引用的 current 属性
          console.log(`当前计数值: ${countRef.current}`)
        }
      
        return (
          
            

      点击按钮增加计数值{countRef.current}

      ) } export default memo(Main)

      你会发现这种数据只能打印放到页面上不更新 ,这您受得了吗?

      以下是useRef 持有可变值的应用场景:

      useRef 持有可变值的主要作用是在组件的生命周期内存储某些不需要触发重新渲染的数据。它适用于以下场景:

      1. 存储前一次渲染的数据: 用于在每次渲染之间保持一些数据,比如上一次渲染的某个值。
      2. 访问和操作 DOM 元素: 使用 useRef 来持有对 DOM 元素的引用,以便直接操作这些元素。
      3. 存储定时器 ID 或其他外部资源的标识符: 在清理副作用时,存储和管理定时器 ID、网络请求取消标识符等。
      4. 防抖和节流: 用于防抖和节流函数中,存储最后一次执行函数的时间戳或计时器 ID。

    三、useMemo

    useMemo 是一个 React Hook,它在每次重新渲染的时候能够缓存计算的结果。

    useMemo相当于 Vue 计算属性(computed properties)

    useMemo 是 React 提供的一个 Hook,用于优化性能,通过在依赖项未发生变化时缓存计算结果,从而避免不必要的重复计算。我们可以使用 useMemo 来缓存一些计算结果,使得只有在依赖项变化时才重新计算。

    假设我们有一个耗时的计算操作,每次点击按钮时,我们都重新计算这个结果,但只有在 count 变化时才重新计算。

    • 不使用useMemo

      import React, { memo, useState } from 'react'
      import type { FC, ReactNode } from 'react'
      
      interface IPerson {
        children?: ReactNode
      }
      
      const Main: FC = () => {
        const [age, setAge] = useState(0)
        const handelageadd = () => {
          console.log('ageadd')
          setAge(age + 1)
        }
        const totalcount = (num: number) => {
          let sum = 0
          for (let i = 1; i <= num; i++) {
            sum += i
          }
          console.log('function totalcount')
          return sum
        }
        return (
          

      useMemo案例

      当前年龄:{age}

      当前求和:{totalcount(10)}

      ) } export default memo(Main)

      我点了五次 totalcount在控制台打印的了五次,证明Render函数执行了五次 你受得了吗,对于这种 用户它乐意点,你就让他点呗,自个等着呗,我们干嘛费劲更新,耗费性能呢?

    • 使用useMemo对count进行缓存

      import React, { memo, useState, useMemo } from 'react'
      import type { FC, ReactNode } from 'react'
      
      interface IPerson {
        children?: ReactNode
      }
      
      const Main: FC = () => {
        const [age, setAge] = useState(0)
        const handelageadd = () => {
          console.log('ageadd')
          setAge(age + 1)
        }
        const totalcount = (num: number) => {
          let sum = 0
          for (let i = 1; i <= num; i++) {
            sum += i
          }
          console.log('function totalcount')
          return sum
        }
        const memoizedTotalCount = useMemo(() => {
          console.log('useMemo')
          return totalcount(10)
        }, [])
        return (
          

      useMemo案例

      当前年龄:{age}

      当前求和:{memoizedTotalCount}

      ) } export default memo(Main)

      我们发现对于一些比较耗费性能的计算我们可以使用useMemo对其进行性能优化 ,只有当指定的值变化后才会调用该函数!

    四、useCallback

    useCallback 是一个允许你在多次渲染中缓存函数的 React Hook。

    useCallback和useMemo又异曲同工之妙,在后面我会对其做出总结!

    useCallback 钩子在 React 中的作用。useCallback 是一个用于性能优化的钩子,它返回一个记忆(memoized)的回调函数。这对于在子组件中传递回调函数特别有用,可以防止不必要的重新渲染。

    注意是子组件,别写个函数就用useCallback去包裹,是传递给子组件才用到这个,看个案例您就明白了!

    import React, { useState, useCallback, memo } from 'react'
    
    // 子组件
    const Child = memo(({ onIncrement }: { onIncrement: () => void }) => {
      console.log('Child rendered')
      return (
        
    ) }) Child.displayName = 'Child' // 父组件 const Parent: React.FC = () => { const [count, setCount] = useState(0) const [message, setMessage] = useState('Coder') // 使用 useCallback 来创建一个记忆化的回调函数 const handleIncrement = useCallback(() => { setCount((prevCount) => prevCount + 1) }, []) // 普通函数 const handleIncrement2 = () => { setCount((prevCount) => prevCount + 1) } return (

    Count: {count}

    Message: {message}

    ) } export default Parent

    当我们使用handleIncrement2 作为处理函数的时候 ,我点击Change Message,您猜怎么着Child渲染了,我改message 关你Child什么事情 你瞎渲染什么???

    而当我使用handleIncrement作为处理函数的时候,我点击Change Message,Child没有重新渲染,所以这就是useCallback的作用 ,收集依赖 ,把一个函数传递给子组件做性能优化!

    Tips:useCallback常常和memo一起使用,为什么?

    应为 memo的作用是用于记忆化组件,确保在 props 不变的情况下,避免子组件重新渲染。这对于优化子组件渲染非常有效。

    我们传递给子组件的函数其实就是一个prop我们如果不使用memo其实我感觉useCallback没啥用处!

    总结useMemo和useCallback的区别:

    useMemouseCallback 是 React 中用于性能优化的两个 Hook,它们的主要区别在于记忆化的内容不同:

    useMemo 用于记忆化计算结果;

    useCallback 用于记忆化函数;

    1、useMemo

    • 用途:记忆化某个计算结果,只有在依赖项改变时才重新计算。
    • 返回值:返回的是计算结果的值。
    • 常见用法:用于优化复杂计算或昂贵的计算,以避免每次渲染时都重新计算。

    示例

    import React, { useState, useMemo } from 'react';
    
    const ExpensiveComponent: React.FC = () => {
      const [count, setCount] = useState(0);
      const [text, setText] = useState('');
    
      // 使用 useMemo 记忆化计算结果
      const expensiveCalculation = useMemo(() => {
        console.log('Calculating...');
        return count * 2;
      }, [count]);
    
      return (
        

    Expensive Calculation: {expensiveCalculation}

    setText(e.target.value)} />
    ); }; export default ExpensiveComponent;

    在这个例子中,useMemo 用于记忆化 expensiveCalculation 的结果,只有在 count 变化时才重新计算。

    2.useCallback

    • 用途:记忆化函数,只有在依赖项改变时才返回新的函数实例。
    • 返回值:返回的是记忆化的函数。
    • 常见用法:用于优化传递给子组件的回调函数,避免因函数引用变化导致的子组件不必要重新渲染。
    import React, { useState, useCallback, memo } from 'react';
    
    // 子组件
    const Child = memo(({ onIncrement }: { onIncrement: () => void }) => {
      console.log('Child rendered');
      return (
        
    ); }); // 父组件 const Parent: React.FC = () => { const [count, setCount] = useState(0); // 使用 useCallback 记忆化函数 const handleIncrement = useCallback(() => { setCount((prevCount) => prevCount + 1); }, []); return (

    Count: {count}

    ); }; export default Parent;

    在这个例子中,useCallback 用于记忆化 handleIncrement 函数,只有在依赖项变化时才返回新的函数实例。

    3.主要区别总结

    1. 记忆化内容

      • useMemo:记忆化计算结果。
      • useCallback:记忆化函数。
    2. 返回值

      • useMemo:返回计算结果的值。
      • useCallback:返回记忆化的函数。
    3. 使用场景

      • useMemo:当有昂贵的计算需要优化时使用。
      • useCallback:当需要将稳定的回调函数传递给子组件时使用,避免不必要的重新渲染。
    4. 依赖项

      • 两者都接受一个依赖项数组,当依赖项发生变化时,useMemo 会重新计算值,useCallback 会返回一个新的函数实例。

    通过合理使用 useMemouseCallback,可以有效提升 React 应用的性能,避免不必要的渲染和计算。

    五、useContext

    useContext 是一个 React Hook,可以让你读取和订阅组件中的 context

    您需要提前创建好文件:

    UserContext.tsx

    UserDisplay.tsx

    UserUpdate.tsx

    Main.tsx

    文件内容如下:

    UserContext.tsx(提供者)

    import React, { memo } from 'react'
    import type { FC, ReactNode } from 'react'
    import { createContext, useContext, useState } from 'react'
    
    interface IPerson {
      children?: ReactNode
    }
    const UserContext = createContext<
      | { user: string; setUser: React.Dispatch> }
      | undefined
    >(undefined)
    
    const UserProvider: FC = (props) => {
      const [user, setUser] = useState('Guest')
      return (
        { user, setUser }}>
          {props.children}
        
      )
    }
    
    export default memo(UserProvider)
    // 自定义 Hook 来使用 UserContext
    const useUser = () => {
      const context = useContext(UserContext)
      if (!context) {
        throw new Error('useUser must be used within a UserProvider')
      }
      return context
    }
    
    export { UserProvider, useUser }
    

    UserDisplay.tsx(消费者)

    import React from 'react'
    import { useUser } from './UserContext'
    
    const UserDisplay: React.FC = () => {
      const { user } = useUser()
    
      return 
    Current User: {user}
    } export default UserDisplay

    UserUpdate.tsx(消费者)

    import React from 'react'
    import { useUser } from './UserContext'
    
    const UserUpdate: React.FC = () => {
      const { setUser } = useUser()
    
      const updateUser = () => {
        setUser('John Doe')
      }
    
      return 
    }
    
    export default UserUpdate
    

    Main.tsx(终极父级) 其实一般会在app中使用 这里做个演示

    import React, { FC, memo } from 'react'
    import { UserProvider } from './UserContext'
    import UserDisplay from './UserDisplay'
    import UserUpdate from './UserUpdate'
    interface IProps {
      children?: React.ReactNode
    }
    const Main: FC = (props) => {
      return (
        
          

    Welcome to the User App

    ) } export default memo(Main)

    有没有感觉 useContext 很像 useState

    六、useId

    useId 是一个 React Hook,可以生成传递给无障碍属性的唯一 ID。

    import React, { FC, memo } from 'react'
    import useId from '@/hooks/useId'
    interface IProps {
      children?: React.ReactNode
    }
    const Main: FC = (props) => {
      const { id, setId, getNextId } = useId()
      const handleSetId = () => {
        const newId = getNextId()
        setId(newId)
      }
      return (
        

    Generated ID: {id}

    ) } export default memo(Main)

    useId.ts

    // useId.ts
    import { useState, useRef } from 'react'
    interface UseIdReturnType {
      id: number
      setId: (value: number) => void
      getNextId: () => number
    }
    
    const useId = (): UseIdReturnType => {
      const [id, setId] = useState<number>(0)
      const nextIdRef = useRef<number>(1)
    
      const getNextId = (): number => {
        const currentId = nextIdRef.current
        nextIdRef.current += 1
        return currentId
      }
    
      return { id, setId, getNextId }
    }
    
    export default useId
    

    七、useEffect

    useEffect 是一个 React Hook,它允许你 将组件与外部系统同步

    useEffect 是 React 中一个非常重要的 Hook,用于处理副作用操作,例如数据获取、订阅、手动 DOM 操作等。

    import React, { useState, useEffect } from 'react'
    
    const LifecycleComponent: React.FC = () => {
      const [count, setCount] = useState(0)
    
      // componentDidMount 相当于首次渲染后执行
      useEffect(() => {
        console.log('Component mounted')
    
        // componentWillUnmount 相当于组件卸载时执行
        return () => {
          console.log('Component will unmount')
        }
      }, []) // 传入空数组,表示仅在首次渲染后执行一次
    
      // componentDidUpdate 相当于依赖变化后执行
      useEffect(() => {
        console.log('Component updated')
    
        // 在下一次更新之前执行清理工作
        return () => {
          console.log('Cleanup before next update')
        }
      }, [count]) // 传入[count],count 变化时执行
    
      const handleIncrement = () => {
        setCount((prevCount) => prevCount + 1)
      }
    
      return (
        

    Count: {count}

    ) } export default LifecycleComponent

    八、useDebugValue

    useDebugValue 是一个 React Hook,可以让你在 React 开发工具 中为自定义 Hook 添加标签。

    useDebugValue 是一个专为开发者调试自定义 hook 而设计的 React hook。对于大多数应用开发者来说,可能不会直接使用它,但对于那些需要创建和维护自定义 hooks 的开发者来说,useDebugValue 可以提供一个更加友好的显示,帮助开发者更容易地理解该 Hook 的当前状态。

    import React, { memo, useState, useDebugValue } from 'react'
    import type { FC, ReactNode } from 'react'
    const useCounter = (initialCount: number) => {
      const [count, setCount] = useState(initialCount)
    
      // 增加计数
      const increment = () => {
        setCount((prevCount) => prevCount + 1)
      }
    
      // 减少计数
      const decrement = () => {
        setCount((prevCount) => prevCount - 1)
      }
    
      // 在开发者工具中显示调试值
      useDebugValue(
        count ? '✅ Online(useDebugValue)' : '❌ Disconnected(useDebugValue)'
      )
    
      return { count, increment, decrement }
    }
    interface IPerson {
      children?: ReactNode
    }
    
    const Main: FC = () => {
      const { count, increment, decrement } = useCounter(1)
      return (
        

    Count: {count}

    ) } export default memo(Main)

    在这里插入图片描述

    九、useTransition

    useTransition 是一个帮助你在不阻塞 UI 的情况下更新状态的 React Hook。

    useTransition 是 React 18 引入的一个新的 Hook,用于管理并发模式(Concurrent Mode)下的过渡状态。它可以帮助我们在进行一些可能耗时的操作时,优雅地处理加载状态的展示,避免页面闪烁或不必要的重渲染。

    假设我们有一个包含从0到19,999数字的数组。这些数字在用户界面上显示为一个列表。该用户界面还有一个文本框,允许我们过滤这些数字。例如,我可以通过在文本框中输入数字99来过滤掉以99开头的数字。

    import React, { useState, useTransition, useEffect } from 'react'
    
    // 生成从 0 到 19999 的数字数组
    const numberArray = Array.from({ length: 19999 }, (_, index) => index)
    
    const FilteredList: React.FC = () => {
      const [inputValue, setInputValue] = useState('')
      const [filteredNumbers, setFilteredNumbers] = useState(numberArray)
    
      const [isPending, startTransition] = useTransition()
    
      const handleChange = (event: React.ChangeEvent) => {
        const value = event.target.value
        setInputValue(value)
    
        startTransition(() => {
          const filtered = numberArray.filter((num) =>
            num.toString().startsWith(value)
          )
          setFilteredNumbers(filtered)
        })
      }
    
      return (
        

    Filter Numbers with useTransition

    { marginBottom: '10px' }} /> {isPending ? (

    Loading...

    ) : (
      {filteredNumbers.map((num) => (
    • {num}
    • ))}
    )}
    ) } export default FilteredList

    十、useDeferredValue

    当useDeferredValue发现您的主线程压力过大他会 使用你上一次更新前的值

    useDeferredValue 是一个 React Hook,可以让你延迟更新 UI 的某些部分。

    useDeferredValue 是 React 18 引入的一个新的 Hook,用于提升交互体验和优化性能。它的作用是延迟更新某些状态或值,以便在主线程空闲时执行,从而减少不必要的重渲染和提升应用的响应速度。

    最主要用法就行结合useTransition进行优化

    import React, {
      useState,
      useDeferredValue,
      useTransition,
      useEffect
    } from 'react'
    import { FixedSizeList as List } from 'react-window'
    
    // 生成从 0 到 19999 的数字数组
    const numberArray = Array.from({ length: 20000 }, (_, index) => index)
    
    const FilteredList: React.FC = () => {
      const [inputValue, setInputValue] = useState('')
      const [filteredNumbers, setFilteredNumbers] = useState(numberArray)
      const deferredInputValue = useDeferredValue(inputValue)
      const [isPending, startTransition] = useTransition()
    
      useEffect(() => {
        startTransition(() => {
          const filtered = numberArray.filter((num) =>
            num.toString().startsWith(deferredInputValue)
          )
          setFilteredNumbers(filtered)
        })
      }, [deferredInputValue, startTransition])
    
      const handleChange = (event: React.ChangeEvent) => {
        setInputValue(event.target.value)
      }
    
      const Row = ({
        index,
        style
      }: {
        index: number
        style: React.CSSProperties
      }) => 
    {filteredNumbers[index]}
    return (

    Filter Numbers with useDeferredValue and useTransition

    { marginBottom: '10px' }} /> {isPending ?
    Loading...
    : null} {Row}
    ) } export default FilteredList

    十一、useImperativeHandle

    useImperativeHandle 是 React 中的一个 Hook,它能让你自定义由 ref 暴露出来的句柄。

    useImperativeHandle 是一个 React Hook,用于自定义暴露给父组件的实例值。在某些情况下,父组件需要直接调用子组件的某些方法或获取其某些值,但我们希望对子组件的内部实现进行更细粒度的控制。这时,useImperativeHandle 就派上了用场。

    回想一下,我们在vue和react中获取一个dom的实例,需要通过ref去获取,但是如果想获取一个子组件的实例呢,这时候我们就不能通过单一的ref去获取了我们需要使用useImperativeHandle和forwardRef去获取!

    来看一个案例 子组件定义一个输入框,父组件通过点击按钮让子组件获取焦点

    Tips:

    在 React 中,当我们使用 forwardRef 时,我们希望将 ref 转发到子组件的某个 DOM 元素或自定义实例方法中。为了确保 TypeScript 能够正确推断和检查 props 和 ref 的类型,我们可以使用 ForwardRefRenderFunction

    为什么要用 forwardRef

    1. 传递 ref 给子组件:
      • 默认情况下,ref 不能直接传递给函数组件。如果需要在函数组件中使用 ref,必须使用 forwardRef
    2. 父组件操作子组件的内部元素:
      • 通过 forwardRef,父组件可以直接访问和操作子组件中的 DOM 元素或调用子组件暴露的方法。
    ForwardRefRenderFunction {
      (props: P, ref: React.Ref): React.ReactElement | null;
      displayName?: string;
    }
    

    父组件:

    import React, { useRef } from 'react'
    import CustomInput from './CustomInput'
    
    const ParentComponent: React.FC = () => {
      const inputRef = useRef<{ focus: () => void; clear: () => void }>(null)//这个ref是为了获取子组件实例
    
      return (
        

    UseImperativeHandle Example

    ) } export default ParentComponent

    子组件:

    import React, {
      useRef,
      useImperativeHandle,
      forwardRef,
      ForwardRefRenderFunction
    } from 'react'
    
    type CustomInputProps = React.InputHTMLAttributes
    
    interface CustomInputHandles {
      focus: () => void
      clear: () => void
    }
    
    // 定义自定义输入框组件
    const CustomInput: ForwardRefRenderFunction<
      CustomInputHandles,
      CustomInputProps
    > = (props, ref) => {
        //在react19中我们可以直接省略props了,直接写{ref},我这里用的react18.3
      const inputRef = useRef(null)//这个ref是为了获取到焦点用的
    
      // 使用 useImperativeHandle 来自定义暴露给父组件的实例值
      useImperativeHandle(ref, () => ({
        focus: () => {
          if (inputRef.current) {
            inputRef.current.focus()
          }
        },
        clear: () => {
          if (inputRef.current) {
            inputRef.current.value = ''
          }
        }
      }))
    
      return 
    }
    
    export default forwardRef(CustomInput)//别忘了包裹
    

    十二、useLayoutEffect

    useLayoutEffect 可能会影响性能。尽可能使用 useEffect

    useLayoutEffect 是 React 中的一个 Hook,允许你在浏览器完成布局和绘制后同步执行 DOM 操作。它和 useEffect 类似,但是会在所有 DOM 变更之后同步触发。这使得它适合用来读取布局并同步触发重新渲染。

    使用 useLayoutEffectuseEffect 的区别

    • useEffect 异步执行,适合不影响布局的副作用,如数据获取。
    • useLayoutEffect 同步执行,适合需要读取 DOM 布局和强制同步渲染的副作用。

    十三、useReducer

    useReducer 是一个 React Hook,它允许你向组件里面添加一个 reducer

    import React, { useReducer } from 'react'
    
    // 定义 action 的类型
    type ActionType = 'increment' | 'decrement' | 'reset'
    
    // 定义状态的类型
    interface State {
      count: number
    }
    
    // 定义 reducer 函数
    const reducer = (state: State, action: { type: ActionType }): State => {
      switch (action.type) {
        case 'increment':
          return { count: state.count + 1 }
        case 'decrement':
          return { count: state.count - 1 }
        case 'reset':
          return { count: 0 }
        default:
          return state
      }
    }
    
    const Counter: React.FC = () => {
      // 使用 useReducer Hook
      const [state, dispatch] = useReducer(reducer, { count: 0 })
    
      return (
        

    Count: {state.count}

    ) } export default Counter

    十四、useSyncExternalStore

    useSyncExternalStore 是一个让你订阅外部 store 的 React Hook。

    count: number
    }

    // 定义 reducer 函数
    const reducer = (state: State, action: { type: ActionType }): State => {
    switch (action.type) {
    case ‘increment’:
    return { count: state.count + 1 }
    case ‘decrement’:
    return { count: state.count - 1 }
    case ‘reset’:
    return { count: 0 }
    default:
    return state
    }
    }

    const Counter: React.FC = () => {
    // 使用 useReducer Hook
    const [state, dispatch] = useReducer(reducer, { count: 0 })

    return (


    Count: {state.count}



    )
    }

    export default Counter

    
    ## 十四、useSyncExternalStore
    
    `useSyncExternalStore` 是一个让你订阅外部 store 的 React Hook。
    
  • 相关阅读:
    【yolov4】基于yolov4深度学习网络目标检测MATLAB仿真
    领英如何不让对方查看自己的好友
    uniapp介绍
    最少拦截系统 (贪心算法)
    爬楼梯(小数据方式+大数据方式)
    数学建模【对粒子群算法中惯性权重和学习因子的改进】
    【软件与系统安全笔记】二、软件与系统安全基础
    clickhouse分组排序,行号,取特定数量数据
    leetcode 4-寻找两个正序数组的中位数
    OS模块中获取当前文件的绝对路径的相关方法
  • 原文地址:https://blog.csdn.net/weixin_47121832/article/details/139907604