React 常用 Hooks

1、useState 状态管理

useState 是React Hooks中最基础的状态管理Hook,语序在函数组件中声明和管理状态。

  • 使用方法
const initCount = 0;

const [count, setCount] = useState(initCount);

setCount(2)
setCount(preCount => preCount + 1)
  • 注意
    • v16中,状态更新是同步的,但在事件回调中,会进行批处理以提升性能。然而这种事件的批处理机制在生命周期函数异步操作中并不总是生效。
    • v18中,引入了自动批处理更新,无论是状态更新发生在 事件回调、promise、setTimeOut 还是其他异步上下文中,都会自动合并,从而减少不必要的渲染次数

2、useEffect 处理副作用

useEffect 是用于处理副作用的Hook,如:数据获取、订阅、手动更改DOM操作等。它替代了类组件中的 componentDidMount componentDidUpdate componentWillUnmount 生命周期函数。

  • 使用方法:
useEffect(()=>{
    // 业务代码
    console.log("组件渲染/更新后执行")

    return () => {
        // 清理函数
        console.log(“组件卸载时执行”)
    }
}, [dependencies]) // 依赖数组 
  • 依赖数组的精准控制:
    • 空数组 [] :仅在组件挂载时执行一次(类似 componentDidMount )
    • 含依赖项 [id] :当依赖变量 id 发生更新时触发
    • 无依赖数组:每次渲染都会执行;需尽量避免使用

3、useRef 创建可变的引用对象

useRef 创建可变的引用对象,这个引用对象的 .current 属性被初始化为传入的参数。返回的对象将在组件的整个生命周期内保持不变。

1、使用方法:

const initRef = useRef(0) // initRef.current = 0

const initDom = useRef(null) // initRef.current 指向已挂载到 DOM树上的div元素

return (
    
)

        功能:可访问DOM节点

        特点:修改 ref.current 不会触发重新渲染

2、存储可变值

        uesRef 可以存储任何可变值,类似组件中的实例属性。

const initVal = useRef(null)

useEffect(() => {
    initVal.current = setInterVal(() => {
        console.log("定时器运行中")
    }, 1000)
    return () => clearInterval(initVal.current)
})

        优势:避免闭包陷阱,因为 ref 始终是最新值

        注意:修改 ref.current 不会出发重新渲染,所以不使用于存储需要渲染的状态

3、解决闭包问题(与 useEffect 配合)

function Counter() {
    const [count, setCount] = useState(0)

    const countRef = useRef(count);

    useEffect(() => {
        countRef.current = count
    })
    useEffect(() => {
        const timer = setInterval(() => {
            // 每次执行时,count 都是最新的
            console.log(count.current)  // count为当前渲染周期的count
        }, 1000)
        return () => clearInterval(timer)
    }, [])  // 空依赖 useEffect 只运行一次

    return (
{count}
) }

4、与 foreardRef 结合访问子组件DOM

// 子组件
const Child = React.forwardRef((props, ref) => (
    
))

// 父组件

function parent() {
    const childRef = useRef(null);
    
    useEffect(() => {
        childRef.current.focus(); // 访问子组件,子组件input 获得焦点
    }, [])

    return (
        <>
            
        
    )
}

4、useContext 跨组件层级传递数据

useContext 用于跨组件层级传递数据,避免繁琐的props逐层传递。

1、使用方法

import {createContext} from "react";
// 父组件

const ThemeContext = createContext("light"); // 默认值 “light”


function App() {
    return (
        // 通过 value 属性传递动态值
        
            
) } // 子组件 function Header() { // 直接获取最近的Provider的值 const theme = useContext(ThemeContext); return (
当前主题:{theme}
) }

2、常见问题与解决方案

        1、未包裹 Provider 时使用默认值

        场景:当组件不在 Provider 包裹的范围时, useContext 返回 createContext 的默认值

        解决方案

// 在创建时显示设置默认值

const AuthContext = createContext({
    user: null,
    login: () => { throw new Error("未提供Provider") }
})

        2、 Provider 值更新,但组件未重新渲染

function App() {
    // 使用 state 管理状态
    const [user, setUser] = useState({name: "Test"});

    // 错误方式:直接传递新对象字面量
    // 

    // 正确方式:传递 state 对象
    return (
        
            
        
    )
}

3、多层嵌套 Provider 的值覆盖

        现象:内层 Provider 会覆盖外层同名Context的值

        解决方案:创建不同的 Context 对象解决命名冲突

4、配合 useReducer 管理复杂状态

const AppContext = createContext();

fucntion App() {
    // userReducer 管理状态逻辑

    const [state, dispatch] = useReducer(reducer, initialState)

    return (
        
            
        
    )
}

function Child() {
    const {state, dispatch} = useContext(AppContext)
    // 通过dispatch 更新全局张台
}

5、useReducer 复杂状态管理

useReducer 是react提供的一个状态管理Hook,其中很多概念来源于 Redux 中的reducer概念。Hook允许通过一个 reducer 函数来管理组件的状态,使得状态更新逻辑更加清晰和可预测。

1、使用方法

const [state, dispatch] = useReducer(reducer, initialArg, init?);
  • reducer:一个函数, (state, action) => newState ,根据不同的 action 类型返回新状态
function reducer(state, action) {
    // 根据 action 了行和载荷计算并返回新的状态
    // state: 当前状态值
    // action: 一个对象,通常包含 type 属性(用于区分动作类型)和可选的 payload(携带数据)

    // 注意:必须返回一个新的状态,不能直接修改原状态(遵循不可变原则)
    if (action.type === "state") {
        return {...state, value: action,value}
    }
    return state;
}
  • initialArg初始状态值 或者 出事化状态的参数,可以是任意类型的数据。
  • init(可选):一个用于惰性初始化状态的初始化函数,如果存在,则初始化状态为 init(initialArg) 
function initFn(initCount) {
    return {count: initCount || 1}
}

function reducerFn(state, action) {
    if(action.type === "edit") {
        return {...state, ...{}}
    }
    return state;
}

const initData = {count: null};

const [state, dispatch] = useReducer(reducerFn, initData, initFn)

2、核心作用

  • 管理复杂状态逻辑
  • 集中状态更新逻辑
  • 实现可预测的状态更新

3、代码示例

// formReducer: 纯函数,接收当前状态 state 和动作 action
function formReducer(state, action) {
    switch(action.type) {
        case "edit":
            return {...state, [action.field]: action.value};  // 返回一个新对像
        case "submit":
            console.log("处理数据提交逻辑")
            return state;
        default:
            return state;
    }
}

function UserForm() {
    // initData: formData 的初始状态
    const initData = {name: "", email: ""};

    const initHandle = (_initData) {
        const {name} = _initData || {};
        return {..._initData, name: name || "张三"}
    }
    // 定义 useReducer
    const [formData, dispatch] = useReducer(formReducer, initData, initHandle);
    
    // dispatch: 用于触发状态更新,传递 action 对象
    //     action: 是一个自定对象,一般包含 type字段,用于区分动作类型
    

    const handleChange = (e) => {
        dispatch({
            type: "edit",
            field: e.target.name,
            value: e.target.value
        })
    }
    return (
        
dispatch({type: "submit"})}>
) }

4、使用场景

  • 状态逻辑复杂(如多级嵌套对象)
  • 需要跟踪状态变更历史
  • 状态更新依赖钱的状态(如队列操作)

5、性能优化:结合 useContext 在小型项目中 代替 redux 实现全局状态管理

6、useCallback 性能优化(缓存函数)

useCallback 时React 用于性能优化的核心 Hook之一,它通过缓存函数引用来避免组件不必要的冲渲染

1、使用方法

const testCallbck = useCallback(() => {
    console.log("随便干点什么");
}, []) // 依赖项数组
  • 参数1:需要缓存的函数
  • 参数2:依赖项数组(类似于 useEffect )
  • 返回值:当依赖项不变时返回相同的函数引用

2、使用场景

  • 优化子组件渲染(配合 React.memo )
const Child = React.memo(({onClick}) => {
    // 仅在 props 变化时重渲染
})

function Parent() {
    const [count, setCount] = useState(0);

    // 缓存点击处理函数
    const handleClick = useCallback(() => {
        setCount(_c => _c + 1)
    }, [])

    return (
        
    )
}
  • 作为其他 Hook 的依赖项
const [count, setCount] = useState(0)

const testCallback = useCallback(() => {
    conole.log("测试数据源:", count)
}, [count])

useEffect(() => {
    fetchData(testCallback)
}, [testCallback])  // 稳定依赖项

3、常见的问题及解决方案

  • 依赖项缺失导致过期闭包
const [count, setCount] = useState(0);

// 错误方式:缺少 count 依赖
const bedCallback = useCallback(() => {
    console.log(count)  // 函数不会重建,总是输出初始值0
}, [])

// 正确方式:添加依赖项

const goodCallback = useCallback(() => {
    console.log(count)  // 随着count变化,输出实时count值
}, [count])
  • 不必要的缓存导致性能下降
const renderCallback = useCallback(() => {
    console.log("没有依赖项,没有必要缓存函数,不如直接使用函数声明")
}, [])

// 正确场景:仅当需要稳定引用时使用
  • 依赖项引用变化问题
const [config, setConfig] = useState({count: 1000});

const testCallback = useCallback(() => {
    console.log("对象依赖会导致 useCallback 频繁重建")
}, [config])

// 解决方案
// 1、解构基本类型值
const {count} = config || {};
const test1Callback = useCallback(() => {
    console.log("解构基本类型值")
}, [count])

// 2、使用 useMemo 缓存对象
const test2Callback = useMemo(()=> config, [config])
  • 过度优化问题
// 不必要的 useCallback

const testCallback = useCallback(() => {}, []);

// 空依赖仅在以下情况使用
// - 函数作为 useEffect 依赖
// - 函数传给优化组件 React.memo
// - 自定义Hook返回值

4、配合 React.memo 优化渲染流程

import React, {useState, useCallback} from "react";

// 子组件:用 React.memo 包裹,浅比较 props
const ChildComp = React.memo(({onClick, label})=> {
    console.log("子组件渲染了")  // 仅当props变化时输出
    return ()
})

// 父组件
function ParentComp() {
    const [count, setCount] = useState(0);
    const [text, setText] = sueState("");

    // 使用 useCallback 缓存函数,依赖count(仅当count变化时重建)
    const increment = useCallback(() => {
        setCount(c => c + 1)
    }, [count])

    // 未优化的函数,每次渲染重建,会导致子组件不必要的渲染
    // const increment = () => setCount(c => c + 1);

    return (
        
// text 数据变化时会重绘父组件 setText(e.target,value)} />
) }
  • 何时使用
    • 配合 React.memo 适用于父组件频繁渲染,切传递函数给子组件(如事件处理)
    • 避免过度优化:旨在性能瓶颈时测厚使用,不必要的缓存可能增加内存开销
  • 依赖数组管理
    • 在依赖数组中包含所有的外部变量(state,props)。如果依赖数组为空,函数将永不重建,可能会导致闭包问题(如:访问过时的 state )
    • 示例错误: useCallback = (() => setCount(c + 1), []) ,依赖缺失,count 不会更新
  • 性能平衡
    • React.memo props浅比较,如果props为复杂对象,需自定义比较函数( React.memo(Component, areEqual) ).
  • 黄金法则
    • 按需优化:优先编写清晰代码,后用性能工具(如:Chrome DevTools)验证
    • 结合工具:
      • React.memo + useCallback + useMemo 覆盖渲染、计算和函数引用优化。
    • 监控效果
      • 优化后,帧率或响应时间应有显著提升

7、useMemo 性能优化(缓存计算结果)

useMomo 是 React 中用于性能优化的核心钩子,它通过记忆化(memoization)技术避免不必要的重复计算,特别适用于优化高开销的计算操作。

1、使用方法

const memoizedValue = useMemo(() => sumFn(a,b), [a, b]);
  • 参数1:返回计算值的函数
  • 参数2:依赖项数组(当依赖变化时重新计算)
  • 返回值:记忆化的计算结果

2、使用场景

  • 高开销计算优化:当组件中有复杂计算(如数据转换、数据运算)
const sortedList = useMemo(() => {
    return hugeArray.sort((a, b) => a.value - b.value)
}, [hugeArray])  // 仅当 hugeArray 变化时重新排序
  • 引用稳定性:避免因对象/数组引用变化触发子组件重新渲染
const config = useMemo(() => ({
    color: theme === "dark" ? "#fff" : "#000",
    size: "large"
}), [theme])  // 主题不变时保持相同引用
  • 避免重复渲染:配合 React.memo 优化子组件
const Child = React.memo(({data}) => {...})

function Parent() {
    const [rowData, setRowData] = useState([1,2,3])

    const proData = useMemo(() => transform(rawData), [rowData]);

    return ()
}

3、关键优化技巧

  • 精准依赖项控制
// 正确方式:包含松油依赖项
const _error = usememo(() => a + b, [a, b])

// 错误方式:缺少依赖可能导致过时数据
const _success = useMemo(() => a + b, [])
  • 避免过度使:简单计算(如字符串拼接、基本数学运算)可能比 useMemo 本身的开销更小,此时无需使用。
  • 组合使用

        1、与 useCallback 搭配保持函数引用稳定

const sortArray = [1,2,3,4]
const memoizedData = useMemo(() => {...}, [sortArray])
const onClick = useCallback(() => handleItem(memoizedData), [memoizedData])

        2、与 useReducer 配合处理复杂状态逻辑

  • 性能测量验证:使用 React DevTools Profiler 验证优化效果,避免过早优化

4、错误示例

// 错误:在 useMemo 内执行副作用
useMemo(() => {
    fatch(); // 副作用应放在 useEffect 中
}, [url])

// 危险:依赖项不全
useMemo(() => {
    returh a + b;  // 若 b 变化不会被更新
}, [a])

注意:何时避免使用

  • 计算开销极低( <1ms )
  • 返回 JSX 元素(应使用 React.memo )
  • 需执行副作用(应使用 useEffect )

8、useLayoutEffect 同步执行DOM操作

useLayoutEffect 是React 提供的特殊钩子,用于 同步执行DOM操作,确保在浏览器回之前完成关键更新。与 useEffect 的核心区别在于执行时机。

  • useLayoutEffect:DOM更新后、浏览器绘制前,同步执行。会阻塞渲染
  • useEffect:DOM更新后、浏览器绘制后 异步执行。不会阻塞渲染

1、使用方法

useLayoutEffect(() => {}, [])

2、使用场景

  • 避免视觉闪烁:需要同步秀嘎DOM样式时,防止用户看到中间状态。
useLayoutEffect(() => {
    const _el = document.getElementById("app");
    // 在绘制前计算并设置位置
    _el.style.top = `${postion.y}px`;
    _el.style.left = `${position.x}px`;
}, [position])
  • 精确测量DOM元素:获取渲染后的元素尺寸
const [dependencies, setDependencies] = setState({width:0,height:0})
const domRef = useRef(null)
useLayoutEffect(() => {
    const {width, height} = domRef.crrent.getBoundingClientRect();
    setDependencies({width, height}) // 更新状态用于后续渲染
}, [dependencies])

return (
)
  • 同步状态与DOM:当状态变化需立即反映到DOM时
useLayoutEffect(() => {
    if (isEditing) {
        inputRef.current.focus();  // 直接聚焦到输入框
    }
}, [isEditing])

3、完整案例

/* 场景:动态调整元素高度避免内容跳动 */ 
function ResizableBox() {
    const ref = useRef(null);
    const [height, setHeight] = useState(0);

    useLayoutEffect(() => {
        // 测量内容实际高度
        const contentHeight = ref.current.scrollHeight;
        // 在绘制前设置精确高度
        ref.current.style.height = `${contentHeight}px`;
        setHeight(contentHeight)
    }, [content])  // 内容变化时重新测量

    return (
        
{content}
) } /* 效果:用户不会看到高度调整过程中的闪烁或内容跳动 */

4、注意事项

  • 性能风险:同步执行会阻塞浏览器渲染进程,过度使用可能导致页面卡顿
// 避免在循环中执行的重型操作
useLayoutCallback(() => {
    heavyCalation(); // 重型操作可能会造成界面冻结
}, [])
  • 服务端渲染:SSR中无法访问DOM,需添加条件判断
useLayoutEffect(() => {
    if(typeof window !== "undefind") {
        // 通过判读是否含有window对象,保证仅在客户端执行
    }
}, [])
  • 执行顺序:统一组件中按声明顺序同步执行
useLayoutEffect(() => { console.log(1) }, [])
useLayoutEffect(() => { console.log(2) }, []);

// 打印结果: 1  2

5、最佳实践原则

  • 优先使用 useEffect ,除非遇到视觉不一致现象需要解决,否则一般不用
  • 严格限制使用范围
    • DOM测量(offsetWidth/scrollHeight等)
    • 同步样式修改(避免闪烁)
    • 焦点管理等及时交互
  • 配合 useRef 使用:应该通过 useRef 访问DOM
const ref = useRef(null);

useLayoutEffect(() => {
    const {offsetWidth} = ref.current;
    console.log(offsetWidth)
}, [])

return (
...
)

9、useId 生成唯一ID

useId 是React 一个内置 Hook,用于生成唯一的稳定的标识符(ID)。主要解决在 React 应用中需要唯一ID的场景,如 表单元素可访问属性(如 aria-describedby )或自定义组件,以避免ID冲突问题,特别是在 服务器端渲染并发模式 下, useId 能确保生成的ID在服务器和客户端之间保持一致,从而提升应用的健壮性和用户体验。

1、使用方法

import {useId} from "react";

fucntion LoginForm() {
    // 生成唯一 ID
    const idPhone = useId();
    const idPwd = useId();

    return (
        
) }

2、核心概念

  • 用户标识符:是指在UI中为元素或组件分配到的唯一ID,用于区分不同实例。
  • useId 会生成一个字符串ID,如 “:r1:“ ,它在组件的整个生命周期内保持稳定,且在不同的渲染环境中保持一致。这避免了手动生成ID时可能出现的重复或不一致问题。

3、优势

  • 避免冲突:在大型项目中,手动设置ID(如 id="user")容易导致重复,尤其在组件复用或动态渲染时。
  • 支持并发模式:React 18 引入并发特性后,组件可能被中断和恢复, useId 确保ID生成过程时确定性的,不会因宣番顺序变化而改变。
  • 提升可访问性:唯一ID时Web可访问性标准(如:WAI-ARIA)的基础,例如连接

4、注意事项

  • 不适用于列表键:列表渲染的 key 属性,需要基于数据内容而非稳定性,建议使用数据中的唯一值作为 key 
  • 性能优化 轻量级 ,适合在函数组件中使用,不会引起额外的渲染开销
  • 兼容性:仅适用于 React V18 及以上版本

10、useTransition 优化高开销渲染操作的交互体验

useTransition React 18 引入的并发特性Hook,用于优化高开销渲染操作的交互体验。它通过将耗时更新标记为 “非阻塞” ,避免界面卡顿,提升用户感知性能。

1、使用方法

import {useTransition} from "react";

function Component() {
    // 结构两个关键值
    const [isPending, startTransition] = useTransition();

    // isPadding:boolean,表示过渡任务是否进行中
    // startTransition:Function,用于包裹高开销操作
}

2、核心作用

  • 延迟渲染:将高开销操作(如大数据过滤,复杂计算)标记为"可中断"任务,确保用户操作优先响应。
  • 状态跟踪:返回 isPadding 标志,用于在后台任务运行时显示加载状态(如骨架屏、loading)。
  • 并发模式支持:在 React 并发渲染机制下协调任务优先级,避免界面冻结

3、应用场景

  • 大数据搜索过滤:避免输入卡顿,优先响应用户输入
// 优势:用户输入无延迟,过滤任务在后台执行,避免界面卡顿
fucntion Search({data}) {
    const [inputVal, setInputVal] = useState("");
    const [filTeredData, setFilteredData] = ueState(data);
    const [isPending, startTransition] = useTransition();

    const handleSearch = (e) => {
        const {value} = e.target || {};
        setInputVal(value); // 立即更新输入框数据(高优先级)
        
        startTransition(() => {  // 延迟执行过滤(低优先级)
            const _filter = data.filter(item => item.name.includes(value))
            setFilteredData(_filter)
        })
    }

    return (
        
{ isPadding? ( // 过滤期间显示加载loading
Loading...
): (
    {filTeredData.map(item=> (
  • {item.name}
  • ))}
) }
) }
  • 路由切换动画:实现页面切换时的平滑过渡效果
// 优势:旧页面保持交互性,直到新页面准备就绪,避免点击无响应
function App() {
    const [iPadding, startTransition] = useTransition()
    const [page,setPage] = useState("home");

    const navigate = (targetPage) => {
        startTransition(() => {
            setPage(targetPage); // 延迟渲染新页面
        })
    }

    return (
        
{page === "home" ? () : ()}
) }

4、注意事项

  • 适用场景
    • 数据量大的列表操作(过滤、排序)
    • 复杂状态更新(如富文本编辑器)
    • 路由切换、标签页切换
    • 需要显示加载状态的异步任务
  • 不适用场景
    • 紧急更新(如按钮点击反馈)
    • 微小计算( useMemo 更合适)
    • 非React 控制的异步操作(如 setTimeout )

11、useDeferredValue 延迟更新某些非关键部分的UI

useDeferredValue React 18 引入的并发特性之一,延迟更新某些非关键部分的UI,特别适合处理高开销渲染场景。它通过智能调度优先级,确保用户交互操作(如输入)始终有限响应,同时避免不必要的渲染阻塞。

1、使用方法

import {useDeferredValue} from "react";

function Component() {
    const [value, setValue] = useState("");
    const deferredValue = useDeferredValue(value);  // 延迟获取值

    // 使用 deferredValue 派生计算结果
    const memoizedResult = useMemo(() => {
        return computeExpensiveValue(deferredValue)
    }, [deferredValue])

    return (
...
) }

2、核心作用与原理

  • 优先级调度:将非紧急更新(如搜索结果渲染)标记为低优先级,确保用户交互即时效应
  • 自动批处理:自动合并多次状态变更,减少渲染次数(类似防抖但是更智能)
  • 与并发模式协同:在 React 的并发渲染机制下,允许高优先级任务中断低优先级任务

3、使用场景与代码示例

  • 大型列表搜索过滤(结合 useMemo )
function Searchlist({data}) {
    const [query, setQuery] = useState("");
    const deferredQuery = useDeferredValue(query);

    // 用延迟值计算过滤结果(避免阻塞输入)
    const filteredQuery = useMemo(() => {
        return data.filter(item => {
            return item.name.toLowerCase.includes(deferredQuery.toLowerCase())
        })
    }, [data, deferredQuery])

    return (
        
setQuery(e.target.value)} /> 使用延迟结果
) }
  • 图表渲染优化(避免频繁重绘)
fucntion Dashboard() {
    const [data, setData] = useState(rawData);
    const deferredData = useDefferedValue(data);  // 延迟数据更新

    return (
        
// 实时更新状态 // 使用延迟数据渲染
) }

4、最佳实践

  • 必须结合 useMemo 使用:延迟值通常用于派生状态,需用 useMemo 缓存计算结果
const deferredValue = useDeferredValue(value);

const memoValue = useMemo(() => {
    computeExpensiveValue(deferredValue)
}, [deferredValue])
  • 优先用于只读操作:适合渲染派生数据(如过滤列表),避免直接修改延迟值。
  • useTransition 协同优化
    • 手动更新时,用 useTransition 包裹;
    • 自动派生值时用 useDeferredValue 
// 用户触发的更新
const [isPending, startTransition] = useTransition();

const handleClick = () => {
    startTransition(() => setState(newState))
}

// 自动延迟派生值
从身体deferredValue = useDeferredValue(state)

5、注意事项

  • 适用场景
    • 大型列表/表格渲染
    • 图表等复杂组件
    • 实时搜索过滤
    • 非关键 UI 更新(如辅助信息面板)
  • 不适用场景
    • 紧急视觉反馈(如按钮点击状态)
    • 微笑状态更新(直接使用 useState 更高效)
    • 服务端渲染(SSR无优先级调度)
  • 值类型限制:延迟值应为 原始类型 或 可序列化对象避免传递函数等非稳定值

你可能感兴趣的:(React 常用 Hooks)