React常用Hooks使用详解
大家都知道React中Hooks是非常重要的特性,它能让函数组件拥有了更多强大的功能。今天就来详细介绍一下React中那些常用的Hooks,帮助大家更好地理解和使用它们。
一、useState:给函数组件添加状态
在React里,函数组件本身没有状态,而useState
这个Hook就解决了这个问题,它能让函数组件像类组件一样拥有自己的状态。
import React, { useState } from 'react'; function Example() { // 这里通过useState初始化状态count,传入一个函数来模拟复杂计算作为初始值 const [count, setCount] = useState(() => { const initialValue = 2 * 3; return initialValue; }); // 尝试更新状态,这里代码有误,setCount是函数,不能直接和数字相加,正确应为setCount(oldValue => oldValue + 1) setCount((oldValue) => { return oldValue + 1; }) return ( <div> <p>初始值为: {count}</p> </div> ); } export default Example;
useState
接收一个参数作为状态的初始值,这个参数有两种形式:
- 静态值:直接传入具体的值,比如字符串、数字等,像
useState(0)
,0就是初始值。 - 动态值:传入一个计算函数,函数的返回值会作为状态的初始值。
setCount
是用来更新当前状态的函数,它的参数也有两种形式:
- 静态值:传入一个值,直接替换原状态的值。
- 动态值:传入一个函数,该函数接收旧的状态值,返回值用来替换原状态值。
二、useEffect:处理副作用
useEffect
主要用于处理组件渲染过程中的副作用操作,比如数据获取、事件监听等。它有几种不同的写法:
- 写法1:useEffect(callback):组件第一次渲染完成后,会执行
callback
函数,这和类组件中的componentDidMount
类似。而且每次组件更新完成后,也会执行这个callback
,类似于componentDidUpdate
。 - 写法2:useEffect(callback, []):只有在组件第一次渲染完成后,
callback
函数才会执行,之后组件更新时都不会再执行,和componentDidMount
的功能很相似。 - 写法3:useEffect(callback, [依赖状态1,依赖状态2,…]):组件第一次渲染完成后执行
callback
。当依赖数组中的某一个或多个状态发生变化时,callback
会再次执行;如果组件更新了,但依赖状态都没有变化,callback
就不会执行。 - 写法4:
let [state, setSate] = useState(0) useEffect(() => { // 返回的内部函数,会在组件卸载的时候执行 // 最常见的用法就是在这个函数里清除副作用 return () => { console.log(state); // 这里打印的state是组件更新之前的状态值 } })
这种写法和写法1的执行时机一样,但返回的内部函数会在组件卸载时执行,常用于清除副作用,比如清除定时器、取消事件订阅等。
(一)useLayoutEffect与useEffect的区别
useLayoutEffect
是同步执行的,它会在DOM更新完成后,但浏览器绘制之前执行。这个Hook适合做获取DOM的宽高、修改样式等操作,可以防止页面出现闪烁、抖动,提升用户体验。不过,由于它会阻塞页面绘制,使用时需要谨慎。useEffect
是异步执行的,不会阻塞浏览器的渲染。用户能先看到页面的更新,然后useEffect
的回调函数才开始执行。所以它更适合处理一些不影响页面渲染的副作用,像请求数据等操作。
三、useMemo:缓存复杂计算结果
useMemo
的作用是缓存复杂计算的结果。只要依赖项没有发生变化,计算结果就不会重新计算,从而提高性能。
const memoizedValue = useMemo(() => { // 进行一些复杂的计算 return computedValue; }, [dependency1, dependency2, ...]);
- 参数:
- 参数1:是一个回调函数,组件初次渲染时会调用这个函数,当依赖项发生变化时也会重新调用,它的返回值会作为
useMemo
的返回结果被缓存起来。 - 参数2:是一个数组,数组里的元素就是依赖项。当任意一个依赖项发生改变时,
useMemo
就会重新调用回调函数计算结果;如果传入空数组[]
,useMemo
的回调函数只会在组件初次渲染时调用一次;要是不传递第二个参数,组件每次渲染都会执行回调函数,这样就失去了用useMemo
优化性能的意义。
- 参数1:是一个回调函数,组件初次渲染时会调用这个函数,当依赖项发生变化时也会重新调用,它的返回值会作为
- 返回值:
useMemo
返回的是回调函数的返回值,并把这个值缓存起来。
(二)useMemo与useEffect的区别
- 参数:
useMemo
与useEffect
的参数形式一样。 - 返回值:
useMemo
返回传入回调函数的返回值并缓存,避免不必要的重复计算;useEffect
可以返回一个清理函数,在组件卸载或下一次副作用执行之前调用,用于清除副作用,比如清除定时器、取消事件订阅等操作。 - 执行时机:
useEffect
在渲染之后异步执行,也就是组件渲染完成,页面更新到屏幕上之后,useEffect
的回调函数才会执行,这样可以避免副作用阻塞页面的渲染过程;useMemo
在渲染期间同步执行,当组件渲染时,useMemo
会立即检查依赖项是否发生变化,如果没有变化,就直接返回之前的结果,如果变化了,就重新计算结果并更新缓存。 - 应用场景:
useEffect
用于处理副作用操作,像获取数据、事件订阅、设置定时器、修改DOM等;useMemo
则用于处理复杂计算,并缓存结果来提高性能。
四、useCallback:避免函数重复创建
在函数组件中,每次重新渲染时,内部定义的函数都会被重新创建。如果这个函数传递给子组件,可能会导致子组件不必要的更新,即便函数的逻辑并没有改变。useCallback
就可以解决这个问题,它能记住函数的引用,只有当依赖项发生变化时,才会重新创建函数,从而避免子组件不必要的渲染。
import React, { useState, useCallback } from 'react'; // 子组件,使用React.memo包裹,避免不必要的渲染 const ChildComponent = React.memo(({ onClick }) => { return <button onClick={onClick}>点击我</button>; }); function ParentComponent() { const [count, setCount] = useState(0); // 使用useCallback记忆化函数,只有count变化时,handleClick才会重新创建 const handleClick = useCallback(() => { setCount(count + 1); }, [count]); return ( <div> <p>计数: {count}</p> <ChildComponent onClick={handleClick} /> </div> ); } export default ParentComponent;
(三)useCallback与useMemo对比
- 参数:
useCallback
与useMemo
的参数相同。 - 作用:
useCallback
用来记忆函数的引用,useMemo
用来缓存计算结果,它们都能优化性能。
五、useContext:共享数据
在React应用中,通常通过props
来传递数据。但如果组件层级很深,传递数据就会变得很繁琐。useContext
结合React.createContext
,可以让数据在任意下级组件中共享,无需层层传递。
- 创建context:
import React from 'react'; // 创建一个上下文对象,初始值设为null const MyContext = React.createContext(null); export default MyContext;
- 提供上下文数据:
import React from 'react'; import MyContext from './MyContext'; const ParentComponent = () => { const sharedData = { message: 'Hello from context', count: 10 }; return ( <MyContext.Provider value={sharedData}> {/* 子组件 */} <ChildComponent /> </MyContext.Provider> ); }; export default ParentComponent;
- 子组件中使用useContext:
import React, { useContext } from 'react'; import MyContext from './MyContext'; const ChildComponent = () => { // 使用useContext获取上下文数据 const contextData = useContext(MyContext); return ( <div> <p>{contextData.message}</p> <p>Count: {contextData.count}</p> </div> ); }; export default ChildComponent;
六、useReducer:管理复杂状态
useReducer
是useState
的替代方案,适合管理复杂的状态逻辑。
const [state, dispatch] = useReducer(reducer, initialArg, init);
- 参数:
- reducer:是一个纯函数,接收当前状态
state
和一个动作action
作为参数,返回一个新的状态,形式通常是(state, action) => newState
。 - initialArg:初始状态值,或者用于初始化状态的参数。
- init(可选):是一个初始化函数,用于惰性初始化状态。如果提供了
init
函数,初始状态将是init(initialArg)
的返回值。
- reducer:是一个纯函数,接收当前状态
- 返回值:
- state:当前状态值。
- dispatch:是一个函数,用于触发状态更新,接收一个动作
action
作为参数。
- 使用示例:
import React, { useReducer } from 'react'; // 定义reducer函数,根据不同的action来更新状态 const counterReducer = (state, action) => { switch (action.type) { case 'increment': return { count: state.count + 1 }; case 'decrement': return { count: state.count - 1 }; default: return state; } }; function Counter() { // 使用useReducer初始化状态,初始值为{count: 0} const [state, dispatch] = useReducer(counterReducer, { count: 0 }); return ( <div> <p>Count: {state.count}</p> <button onClick={() => dispatch({ type: 'increment' })}>Increment</button> <button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button> </div> ); } export default Counter;
七、useRef:保存可变值与获取DOM节点
useRef
有两个主要用途:
- 保存可变值:它可以创建一个可变的对象,在组件的整个生命周期内保持不变。每次组件重新渲染时,
useRef
返回的对象都是同一个。可以通过修改该对象的current
属性来存储和读取数据,而且不会触发组件的重新渲染。 - 获取DOM节点:借助
useRef
可以引用DOM元素,这样就能在代码中直接操作该DOM元素,比如获取元素的尺寸、滚动位置等。
import React, { useRef } from 'react'; function Example() { const inputRef = useRef(null); const focusInput = () => { // 通过ref获取DOM节点并调用其focus方法,使输入框获得焦点 inputRef.current.focus(); }; return ( <div> <input ref={inputRef} type="text" /> <button onClick={focusInput}>聚焦输入框</button> </div> ); } export default Example;
八、forwardRef与useImperativeHandle:灵活操作组件实例
- forwardRef:是React提供的一个高阶组件,用于在组件之间转发
ref
。在React中,默认情况下组件不能直接将ref
传递给子组件,而forwardRef
打破了这个限制,允许父组件把ref
传递给子组件,这样父组件就能访问子组件的DOM节点或调用子组件的方法。 - useImperativeHandle:通常与
forwardRef
一起使用,用于自定义通过ref
暴露给父组件的实例值。默认情况下,使用ref
引用一个组件时,父组件可以访问该组件的所有实例属性和方法。但有时我们可能只想暴露部分特定的属性或方法,这时useImperativeHandle
就能派上用场了。
通过对这些常用React Hooks的详细介绍,希望大家对它们有了更深入的理解。在实际开发中,合理运用这些Hooks,能够让我们的代码更简洁、高效,提升开发体验和应用性能,你学会了吗。