【React】一篇文章带你了解常见的Hooks函数

React Hooks 详解:从基础到进阶 🚀

React Hooks 是 React 16.8 引入的重要特性,它允许开发者在函数组件中使用状态(State)和生命周期逻辑,而无需编写类组件。

Hooks 的核心思想

  • 把复杂的逻辑"封装"成可复用的函数。
  • 让函数组件像类组件一样强大,但代码更简洁!

1. useState:管理组件状态 💡

用途

useState 是最基础的 Hook,用于在函数组件中声明和更新状态变量。它返回一个包含当前状态值和更新函数的数组。

代码示例

jsx 复制代码
import React, { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0); // 声明状态变量 count,初始值为 0

  return (
    <div>
      <p>当前计数:{count}</p>
      <button onClick={() => setCount(count + 1)}>增加</button>
      <button onClick={() => setCount(count - 1)}>减少</button>
    </div>
  );
}

深入解析

  1. 状态更新是异步的

    React 可能会将多个 setCount 调用合并以提高性能。例如,连续调用 setCount(count + 1) 两次,最终只更新一次状态。

  2. 函数式更新

    如果新状态依赖于旧状态,使用函数式更新可以确保获取最新的值。例如:

    jsx 复制代码
    setCount(prevCount => prevCount + 1);
  3. 多个状态变量

    可以在同一个组件中声明多个状态变量:

    jsx 复制代码
    const [name, setName] = useState('John');
    const [age, setAge] = useState(25);
  4. 不可变更新原则

    状态更新必须通过 setState 函数,不能直接赋值(如 count = 1)。

注意事项

  • 性能优化 :如果状态更新频繁且耗时,考虑使用 useReducer 替代 useState

2. useEffect:处理副作用 ⚙️

用途

useEffect 用于在组件渲染后执行副作用操作,例如数据请求、订阅事件、DOM 操作等。它结合了类组件中的 componentDidMountcomponentDidUpdatecomponentWillUnmount 的功能。

代码示例

jsx 复制代码
import React, { useState, useEffect } from 'react';

function DataFetcher() {
  const [data, setData] = useState(null);

  useEffect(() => {
    fetch('https://jsonplaceholder.typicode.com/todos/1')
      .then(res => res.json())
      .then(json => setData(json.title));
  }, []); // 空数组表示只在组件挂载时执行一次

  return (
    <div>
      {data ? <p>获取到的数据:{data}</p> : <p>加载中...</p>}
    </div>
  );
}

深入解析

  1. 依赖数组的作用

    • 空数组 []:副作用仅在组件挂载时执行一次。
    • 包含变量的数组 [count] :只有当 count 变化时,副作用才会重新执行。
    • 省略数组:副作用会在每次渲染后执行。
  2. 清理副作用

    如果副作用需要清理(如取消订阅、移除事件监听器),返回一个清理函数:

    jsx 复制代码
    useEffect(() => {
      const timer = setInterval(() => {
        console.log('定时器触发');
      }, 1000);
      return () => clearInterval(timer); // 组件卸载时清除定时器
    }, []);
  3. 同步 vs 异步
    useEffect 中的副作用是异步执行的,不会阻塞 DOM 更新。如果需要立即操作 DOM,使用 useLayoutEffect

注意事项

  • 避免过度使用 :每个 useEffect 都会增加组件的复杂性,尽量合并相关逻辑。
  • 依赖项缺失:如果依赖数组未包含所有相关变量,可能导致副作用无法正确执行或内存泄漏。

3. useReducer:管理复杂状态逻辑 🧠

用途

useReducer 适用于复杂的状态逻辑(如表单验证、购物车计算)。它通过 reducer 函数统一管理状态更新,类似于 Redux 的模式。

代码示例

jsx 复制代码
import React, { useReducer } from 'react';

// 定义 reducer 函数
function 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() {
  const [state, dispatch] = useReducer(counterReducer, { count: 0 });

  return (
    <div>
      <p>当前计数:{state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>增加</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>减少</button>
    </div>
  );
}

深入解析

  1. 纯函数原则
    reducer 必须是一个纯函数,不能直接修改 state,而是返回新对象。

  2. 嵌套状态更新

    对于嵌套对象的状态更新,推荐使用 immer 库简化代码。例如:

    jsx 复制代码
    import produce from 'immer';
    
    function reducer(state, action) {
      return produce(state, draft => {
        draft.nested.value = action.value;
      });
    }
  3. 初始化状态

    可以通过第三个参数 init 提供初始化函数:

    jsx 复制代码
    const [state, dispatch] = useReducer(reducer, initialState, init);

注意事项

  • 避免过度设计 :对于简单的状态逻辑(如计数器),useState 更直观。
  • 调试困难 :复杂的 reducer 可能导致调试困难,建议拆分为多个小函数。

4. useRef:访问 DOM 或存储可变值 🎯

用途

useRef 返回一个可变对象(.current 属性),常用于:

  1. 访问 DOM 元素
  2. 存储不随渲染变化的值(如定时器 ID、表单输入值)

代码示例

jsx 复制代码
import React, { useRef } from 'react';

function InputFocus() {
  const inputRef = useRef(null);

  const focusInput = () => {
    inputRef.current.focus(); // 直接操作 DOM
  };

  return (
    <div>
      <input ref={inputRef} type="text" placeholder="点击按钮聚焦" />
      <button onClick={focusInput}>聚焦输入框</button>
    </div>
  );
}

深入解析

  1. 不会触发重新渲染

    修改 .current 的值不会导致组件重新渲染。

  2. 跨渲染保持值
    useRef 可以存储任何可变值(如 counttimeoutId),适合需要跨渲染保持的场景。

  3. useState 的对比

    • useRef 适合存储不需要触发渲染的值。
    • useState 适合存储需要触发渲染的状态。

注意事项

  • 避免滥用 :如果值需要触发渲染,优先使用 useState
  • 内存泄漏风险:存储的 DOM 元素或事件监听器需在组件卸载时手动清理。

5. useContext:跨组件共享状态 🌐

用途

useContext 用于在组件树中共享状态,避免通过 props 一层层传递(即"props drilling"问题)。

代码示例

jsx 复制代码
import React, { createContext, useContext } from 'react';

// 创建 Context
const ThemeContext = createContext('light');

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Page />
    </ThemeContext.Provider>
  );
}

function Page() {
  const theme = useContext(ThemeContext);
  return <p>当前主题:{theme}</p>;
}

深入解析

  1. 创建 Context

    使用 createContext 创建一个 Context 对象,提供默认值。

  2. 提供 Context 值

    使用 Provider 组件包裹子组件树,并通过 value 属性传递共享的状态。

  3. 消费 Context 值

    使用 useContext 钩子获取 Context 的值,无需逐层传递 props

注意事项

  • 性能影响 :如果 Context 的值频繁变化,所有消费组件都会重新渲染。可以通过 useMemouseCallback 优化。
  • 避免过度使用 :对于局部状态(如组件内部状态),优先使用 useState

6. useLayoutEffect:同步 DOM 更新 ⚡️

用途

useLayoutEffectuseEffect 类似,但会在 DOM 更新之前同步执行,适合需要立即操作 DOM 的场景(如测量 DOM 宽度/高度)。

代码示例

jsx 复制代码
import React, { useLayoutEffect, useRef } from 'react';

function MeasureBox() {
  const boxRef = useRef(null);

  useLayoutEffect(() => {
    const width = boxRef.current.offsetWidth;
    console.log('盒子宽度:', width);
  }, []);

  return <div ref={boxRef} style={{ width: '200px', height: '100px' }} />;
}

深入解析

  1. 同步执行
    useLayoutEffect 是同步的,会阻塞浏览器的绘制,直到所有 DOM 更新完成。

  2. 适用场景

    • 需要测量 DOM 尺寸(如弹窗定位)。
    • 需要同步更新样式(避免页面闪烁)。
  3. 清理副作用

    同样支持返回清理函数,用于卸载时的资源释放。

注意事项

  • 性能开销useLayoutEffect 会阻塞渲染,谨慎使用以避免性能问题。
  • useEffect 的选择 :优先使用 useEffect,仅在需要同步操作 DOM 时使用 useLayoutEffect

7. useMemo:缓存计算结果 🧮

用途

useMemo 用于缓存计算结果,避免在每次组件渲染时重复执行昂贵的计算操作。它类似于类组件中的 shouldComponentUpdate,但更灵活。

代码示例

jsx 复制代码
import React, { useState, useMemo } from 'react';

function ExpensiveComponent() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('');

  // 使用 useMemo 缓存计算结果
  const expensiveValue = useMemo(() => {
    console.log('计算中...');
    let result = 0;
    for (let i = 0; i < 100000000; i++) {
      result += i;
    }
    return result;
  }, [count]); // 仅在 count 变化时重新计算

  return (
    <div>
      <p>计数:{count}</p>
      <button onClick={() => setCount(count + 1)}>增加计数</button>
      <p>文本:{text}</p>
      <input value={text} onChange={(e) => setText(e.target.value)} />
      <p>计算结果:{expensiveValue}</p>
    </div>
  );
}

深入解析

  1. 缓存机制
    useMemo 会在依赖项([count])发生变化时重新执行计算,并缓存结果。如果依赖项未变化,则直接返回缓存值。

  2. 适用场景

    • 昂贵的计算:如大数据排序、复杂算法、频繁的字符串拼接等。
    • 避免重复渲染:当某个值仅在特定条件下需要更新时。
  3. 注意事项

    • 避免滥用 :如果计算逻辑简单,直接计算比使用 useMemo 更高效。
    • 依赖项管理:确保依赖数组正确,否则可能导致缓存失效或内存泄漏。

总结

Hook 用途 适合场景
useState 管理简单状态 计数器、表单输入
useEffect 处理副作用 数据请求、事件监听
useReducer 管理复杂状态逻辑 表单验证、购物车
useRef 访问 DOM 或存储可变值 输入框聚焦、定时器 ID
useContext 跨组件共享状态 主题切换、用户登录状态
useLayoutEffect 同步 DOM 更新 测量 DOM 尺寸、同步样式更新
useMemo 缓存计算结果 昂贵的计算、避免重复渲染

🚀 最后的小贴士

  • 多写代码:动手实践是掌握 Hooks 的唯一途径。
  • 查文档:遇到问题时,先看官方文档,再翻社区文章。
  • 看案例:通过真实项目理解技术如何落地。

🎉 学习就像练武功,坚持就是胜利!

相关推荐
啃火龙果的兔子2 小时前
修改 Lucide-React 图标样式的方法
前端·react.js·前端框架
前端 贾公子2 小时前
为何在 Vue 的 v-model 指令中不能使用可选链(Optional Chaining)?
前端·javascript·vue.js
潘多拉的面2 小时前
Vue的ubus emit/on使用
前端·javascript·vue.js
遗憾随她而去.2 小时前
js面试题 高频(1-11题)
开发语言·前端·javascript
hqxstudying5 小时前
J2EE模式---前端控制器模式
java·前端·设计模式·java-ee·状态模式·代码规范·前端控制器模式
开开心心就好6 小时前
Excel数据合并工具:零门槛快速整理
运维·服务器·前端·智能手机·pdf·bash·excel
im_AMBER6 小时前
Web开发 05
前端·javascript·react.js
Au_ust6 小时前
HTML整理
前端·javascript·html
安心不心安7 小时前
npm全局安装后,依然不是内部或外部命令,也不是可运行的程序或批处理文件
前端·npm·node.js
迷曳8 小时前
28、鸿蒙Harmony Next开发:不依赖UI组件的全局气泡提示 (openPopup)和不依赖UI组件的全局菜单 (openMenu)、Toast
前端·ui·harmonyos·鸿蒙