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<IProps> = (props) => {const [age, setAge] = useState(0)return (<MainContainer><h1>今年我:{age}岁了!</h1><button onClick={() => setAge(age + 1)}>点我加一</button></MainContainer>)
}
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<IProps> = (props) => {const inputRef = useRef<HTMLInputElement>(null)useEffect(() => {if (inputRef.current) {inputRef.current.focus()}}, [])return (<MainContainer><input ref={inputRef} type="text" placeholder="自动获得焦点的输入框" /></MainContainer>) } export default memo(Main)
-
持有可变值:
import React, { FC, memo, useRef } from 'react' import { MainContainer } from './style'interface IProps {children?: React.ReactNode }const Main: FC<IProps> = (props) => {const countRef = useRef(0) // 使用 useRef 创建一个可变值引用const handleClick = () => {countRef.current += 1 // 修改引用的 current 属性console.log(`当前计数值: ${countRef.current}`)}return (<MainContainer><h1>点击按钮增加计数值{countRef.current}</h1><button onClick={handleClick}>点我加一</button></MainContainer>) }export default memo(Main)
你会发现这种数据只能打印放到页面上不更新 ,这您受得了吗?
以下是
useRef
持有可变值的应用场景:useRef
持有可变值的主要作用是在组件的生命周期内存储某些不需要触发重新渲染的数据。它适用于以下场景:- 存储前一次渲染的数据: 用于在每次渲染之间保持一些数据,比如上一次渲染的某个值。
- 访问和操作 DOM 元素: 使用
useRef
来持有对 DOM 元素的引用,以便直接操作这些元素。 - 存储定时器 ID 或其他外部资源的标识符: 在清理副作用时,存储和管理定时器 ID、网络请求取消标识符等。
- 防抖和节流: 用于防抖和节流函数中,存储最后一次执行函数的时间戳或计时器 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<IPerson> = () => {const [age, setAge] = useState(0)const handelageadd = () => {console.log('ageadd')setAge(age + 1)}const totalcount = (num: number) => {let sum = 0for (let i = 1; i <= num; i++) {sum += i}console.log('function totalcount')return sum}return (<div><h1>useMemo案例</h1><h1>当前年龄:{age}</h1><h1>当前求和:{totalcount(10)}</h1><button onClick={handelageadd}>点我年龄加一</button></div>) }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<IPerson> = () => {const [age, setAge] = useState(0)const handelageadd = () => {console.log('ageadd')setAge(age + 1)}const totalcount = (num: number) => {let sum = 0for (let i = 1; i <= num; i++) {sum += i}console.log('function totalcount')return sum}const memoizedTotalCount = useMemo(() => {console.log('useMemo')return totalcount(10)}, [])return (<div><h1>useMemo案例</h1><h1>当前年龄:{age}</h1><h1>当前求和:{memoizedTotalCount}</h1><button onClick={handelageadd}>点我年龄加一</button></div>) }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 (<div><button onClick={onIncrement}>Increment in Child</button></div>)
})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 (<div><h1>Count: {count}</h1><h2>Message: {message}</h2><button onClick={() => setMessage('Joon')}>Change Message</button><button onClick={handleIncrement2}>Increment in Parent</button><Child onIncrement={handleIncrement2} /></div>)
}export default Parent
当我们使用handleIncrement2 作为处理函数的时候 ,我点击Change Message,您猜怎么着Child渲染了,我改message 关你Child什么事情 你瞎渲染什么???
而当我使用handleIncrement作为处理函数的时候,我点击Change Message,Child没有重新渲染,所以这就是useCallback的作用 ,收集依赖 ,把一个函数传递给子组件做性能优化!
Tips:useCallback常常和memo一起使用,为什么?
应为 memo的作用是用于记忆化组件,确保在 props 不变的情况下,避免子组件重新渲染。这对于优化子组件渲染非常有效。
我们传递给子组件的函数其实就是一个prop我们如果不使用memo其实我感觉useCallback没啥用处!
总结useMemo和useCallback的区别:
useMemo
和 useCallback
是 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 (<div><h1>Expensive Calculation: {expensiveCalculation}</h1><button onClick={() => setCount(count + 1)}>Increment Count</button><input value={text} onChange={(e) => setText(e.target.value)} /></div>);
};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 (<div><button onClick={onIncrement}>Increment in Child</button></div>);
});// 父组件
const Parent: React.FC = () => {const [count, setCount] = useState(0);// 使用 useCallback 记忆化函数const handleIncrement = useCallback(() => {setCount((prevCount) => prevCount + 1);}, []);return (<div><h1>Count: {count}</h1><Child onIncrement={handleIncrement} /></div>);
};export default Parent;
在这个例子中,useCallback
用于记忆化 handleIncrement
函数,只有在依赖项变化时才返回新的函数实例。
3.主要区别总结
-
记忆化内容:
useMemo
:记忆化计算结果。useCallback
:记忆化函数。
-
返回值:
useMemo
:返回计算结果的值。useCallback
:返回记忆化的函数。
-
使用场景:
useMemo
:当有昂贵的计算需要优化时使用。useCallback
:当需要将稳定的回调函数传递给子组件时使用,避免不必要的重新渲染。
-
依赖项:
- 两者都接受一个依赖项数组,当依赖项发生变化时,
useMemo
会重新计算值,useCallback
会返回一个新的函数实例。
- 两者都接受一个依赖项数组,当依赖项发生变化时,
通过合理使用 useMemo
和 useCallback
,可以有效提升 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<React.SetStateAction<string>> }| undefined
>(undefined)const UserProvider: FC<IPerson> = (props) => {const [user, setUser] = useState('Guest')return (<UserContext.Provider value={{ user, setUser }}>{props.children}</UserContext.Provider>)
}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 <div>Current User: {user}</div>
}
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 <button onClick={updateUser}>Update User</button>
}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<IProps> = (props) => {return (<UserProvider><h1>Welcome to the User App</h1><UserDisplay /><UserUpdate /></UserProvider>)
}
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<IProps> = (props) => {const { id, setId, getNextId } = useId()const handleSetId = () => {const newId = getNextId()setId(newId)}return (<div><h1>Generated ID: {id}</h1><button onClick={handleSetId}>Generate New ID</button></div>)
}export default memo(Main)
useId.ts
// useId.ts
import { useState, useRef } from 'react'
interface UseIdReturnType {id: numbersetId: (value: number) => voidgetNextId: () => number
}const useId = (): UseIdReturnType => {const [id, setId] = useState<number>(0)const nextIdRef = useRef<number>(1)const getNextId = (): number => {const currentId = nextIdRef.currentnextIdRef.current += 1return 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<number>(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 (<div><h1>Count: {count}</h1><button onClick={handleIncrement}>Increment Count</button></div>)
}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<IPerson> = () => {const { count, increment, decrement } = useCounter(1)return (<div><h1>Count: {count}</h1><button onClick={increment}>Increment</button><button onClick={decrement}>Decrement</button></div>)
}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<number[]>(numberArray)const [isPending, startTransition] = useTransition()const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {const value = event.target.valuesetInputValue(value)startTransition(() => {const filtered = numberArray.filter((num) =>num.toString().startsWith(value))setFilteredNumbers(filtered)})}return (<div><h2>Filter Numbers with useTransition</h2><inputtype="text"value={inputValue}onChange={handleChange}placeholder="Enter filter..."style={{ marginBottom: '10px' }}/>{isPending ? (<p>Loading...</p>) : (<ul>{filteredNumbers.map((num) => (<li key={num}>{num}</li>))}</ul>)}</div>)
}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<number[]>(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<HTMLInputElement>) => {setInputValue(event.target.value)}const Row = ({index,style}: {index: numberstyle: React.CSSProperties}) => <div style={style}>{filteredNumbers[index]}</div>return (<div><h2>Filter Numbers with useDeferredValue and useTransition</h2><inputtype="text"value={inputValue}onChange={handleChange}placeholder="Enter filter..."style={{ marginBottom: '10px' }}/>{isPending ? <div>Loading...</div> : null}<Listheight={600}itemCount={filteredNumbers.length}itemSize={35}width="100%">{Row}</List></div>)
}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
- 传递
ref
给子组件:- 默认情况下,
ref
不能直接传递给函数组件。如果需要在函数组件中使用ref
,必须使用forwardRef
。
- 默认情况下,
- 父组件操作子组件的内部元素:
- 通过
forwardRef
,父组件可以直接访问和操作子组件中的 DOM 元素或调用子组件暴露的方法。
- 通过
ForwardRefRenderFunction<T, P = {}> {(props: P, ref: React.Ref<T>): 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 (<div><h2>UseImperativeHandle Example</h2><CustomInput ref={inputRef} placeholder="Enter text here..." /><button onClick={() => inputRef.current?.focus()}>Focus Input</button><button onClick={() => inputRef.current?.clear()}>Clear Input</button></div>)
}export default ParentComponent
子组件:
import React, {useRef,useImperativeHandle,forwardRef,ForwardRefRenderFunction
} from 'react'type CustomInputProps = React.InputHTMLAttributes<HTMLInputElement>interface CustomInputHandles {focus: () => voidclear: () => void
}// 定义自定义输入框组件
const CustomInput: ForwardRefRenderFunction<CustomInputHandles,CustomInputProps
> = (props, ref) => {//在react19中我们可以直接省略props了,直接写{ref},我这里用的react18.3const inputRef = useRef<HTMLInputElement>(null)//这个ref是为了获取到焦点用的// 使用 useImperativeHandle 来自定义暴露给父组件的实例值useImperativeHandle(ref, () => ({focus: () => {if (inputRef.current) {inputRef.current.focus()}},clear: () => {if (inputRef.current) {inputRef.current.value = ''}}}))return <input ref={inputRef} {...props} />
}export default forwardRef(CustomInput)//别忘了包裹
十二、useLayoutEffect
useLayoutEffect
可能会影响性能。尽可能使用 useEffect
。
useLayoutEffect
是 React 中的一个 Hook,允许你在浏览器完成布局和绘制后同步执行 DOM 操作。它和 useEffect
类似,但是会在所有 DOM 变更之后同步触发。这使得它适合用来读取布局并同步触发重新渲染。
使用 useLayoutEffect
与 useEffect
的区别
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 Hookconst [state, dispatch] = useReducer(reducer, { count: 0 })return (<div><h1>Count: {state.count}</h1><button onClick={() => dispatch({ type: 'increment' })}>Increment</button><button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button><button onClick={() => dispatch({ type: 'reset' })}>Reset</button></div>)
}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}
<button onClick={() => dispatch({ type: ‘increment’ })}>Increment
<button onClick={() => dispatch({ type: ‘decrement’ })}>Decrement
<button onClick={() => dispatch({ type: ‘reset’ })}>Reset
)
}
export default Counter
## 十四、useSyncExternalStore`useSyncExternalStore` 是一个让你订阅外部 store 的 React Hook。