白白的白豆腐

  • 首页
  • 文章归档
    • WordPress
    • 玩转群晖
    • 解决方案
  • 编程相关
    • Django
    • Python
    • Redis
    • React
  • 游戏攻略
    • 骑马与砍杀
  • 外语小筑
    • English
  • 网络资源
  • 关于
该发生的总会发生,不管你是否为此焦虑
向前走,向前看,生活就这么简单
  1. 首页
  2. 编程相关
  3. React
  4. 正文

React-常用的10个Hook

2024年 12月 1日 5487点热度 0人点赞 0条评论

在 React 中,Hook 是一个强大而灵活的工具,可以让你在函数组件中使用 state 和其他 React 特性。本文将介绍十个最常用的 React Hook,并附上代码示例和解释,帮助你快速理解并应用这些 Hook。

userState - 管理状态

useState 是 React 中最基本的 Hook,用于在函数组件中声明状态。

import React, { useState } from 'react'; // 从 React 中导入 useState Hook

// 定义 Counter 组件
function Counter() {
  // 使用 useState Hook 定义状态变量 count,并设置初始值为 0
  // count: 当前计数值
  // setCount: 用于更新 count 值的函数
  const [count, setCount] = useState(0);

  return (
    <div>
      {/* 显示当前计数值 */}
      <p>Current Count: {count}</p>

      {/* 点击按钮时,调用 setCount 更新 count 的值,将其加 1 */}
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

export default Counter; // 导出 Counter 组件以供使用

在这个示例中,useState 用于定义一个 count 状态,并且提供了 setCount 函数来更新状态。

userEffect - 处理副作用

useEffect Hook 允许你在组件渲染后执行副作用,比如数据获取或订阅。

import React, { useState, useEffect } from 'react'; // 从 React 中导入 useState 和 useEffect Hook

// 定义 Timer 组件
function Timer() {
  // 使用 useState 定义状态变量 seconds,初始值为 0
  // seconds: 当前经过的秒数
  // setSeconds: 用于更新 seconds 的函数
  const [seconds, setSeconds] = useState(0);

  // 使用 useEffect Hook 设置副作用(定时器逻辑)
  useEffect(() => {
    // 定义一个定时器,每隔 1 秒执行一次
    const interval = setInterval(() => {
      // 更新 seconds 的值,将其加 1
      setSeconds(seconds => seconds + 1);
    }, 1000);

    // 返回一个清理函数,用于在组件卸载或依赖更新时清除定时器
    return () => clearInterval(interval);
  }, []); // 空依赖数组,表示只在组件挂载时执行一次

  // 渲染当前经过的秒数
  return <div>Seconds Elapsed: {seconds}</div>;
}

export default Timer; // 导出 Timer 组件以供使用

在这里,useEffect 用于创建一个计时器,并在组件卸载时清除该计时器。

在 React 的 useEffect 中,最后的 [] 是依赖数组(Dependency Array)。它的作用是告诉 React 这个 useEffect 的回调函数应该在什么时候执行。

依赖数组的含义:

无依赖数组:useEffect 在每次渲染时执行,包括首次挂载和每次更新。

空依赖数组 []:useEffect 只在组件首次挂载时执行一次,不会因更新而重新执行。

有依赖数组 [dep1, dep2]:useEffect 在组件首次挂载和依赖项发生变化时执行。

useContext - 使用上下文

useContext Hook 允许你访问由 React Context 提供的全局数据。

import React, { useContext, createContext } from 'react';

const UserContext = createContext();

function UserProfile() {
  const user = useContext(UserContext);
  return <div>Welcome, {user.name}!</div>;
}

function App() {
  return (
    <UserContext.Provider value={{ name: 'John Doe' }}>
      <UserProfile />
    </UserContext.Provider>
  );
}

在这个示例中,useContext 用于从 UserContext 中获取用户信息。

关于Context API 详细解析请看下面这篇博客

React应用状态管理-Context API

userReducer - 复杂状态管理

useReducer 是用于替代 useState 的 Hook,适合于处理更复杂的状态逻辑。

import React, { useReducer } from 'react';

// 定义 reducer 函数,用于根据 action 类型更新 state
function reducer(state, action) {
  // 使用 switch 语句处理不同的 action 类型
  switch (action.type) {
    case 'increment': // 如果 action 类型是 'increment'
      return { count: state.count + 1 }; // 返回新的 state,count 值加 1
    case 'decrement': // 如果 action 类型是 'decrement'
      return { count: state.count - 1 }; // 返回新的 state,count 值减 1
    default: // 如果 action 类型未知
      throw new Error(); // 抛出错误
  }
}

// 定义 Counter 组件
function Counter() {
  // 使用 useReducer Hook 管理组件的状态
  // reducer 是用于更新状态的函数,{ count: 0 } 是初始 state
  const [state, dispatch] = useReducer(reducer, { count: 0 });

  return (
    <div>
      {/* 显示当前 count 值 */}
      <p>Count: {state.count}</p>

      {/* 点击 "+" 按钮时,派发 'increment' 类型的 action */}
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>

      {/* 点击 "-" 按钮时,派发 'decrement' 类型的 action */}
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </div>
  );
}

export default Counter; // 导出 Counter 组件以供使用

useReducer 用于定义一个包含多个操作的复杂状态管理逻辑。

useRef - 引用 DOM 元素

useRef 可以用来直接访问 DOM 元素或保存可变的值而不触发重新渲染。

import React, { useRef } from 'react'; // 从 React 中导入 useRef Hook

// 定义 FocusInput 组件
function FocusInput() {
  // 使用 useRef Hook 创建一个引用对象 inputRef,用于获取 <input> 元素
  const inputRef = useRef(null);

  // 定义 focusInput 函数,用于让输入框获得焦点
  const focusInput = () => {
    // 通过 inputRef.current 直接访问 <input> DOM 元素,并调用其 focus 方法
    inputRef.current.focus();
    // 设置该输入框的值为空字符串,从而实现清空操作。
    // inputRef.current.value = "";
  };

  return (
    <div>
      {/* 使用 ref 属性将 inputRef 绑定到 <input> 元素 */}
      <input ref={inputRef} type="text" />
      
      {/* 点击按钮时调用 focusInput 函数 */}
      <button onClick={focusInput}>Focus Input</button>
    </div>
  );
}

export default FocusInput; // 导出组件以供使用

在这个例子中,useRef 用于获取文本框的引用,以便调用 focus 方法。

useMemo - 计算缓存

useMemo 用于缓存计算结果,避免在每次渲染时重复计算。

示例1:

import React, { useState, useMemo } from 'react';

function ExpensiveCalculation({ number }) {
  const compute = (num) => {
    console.log('Computing...');
    return num * 2;
  };

  const result = useMemo(() => compute(number), [number]);

  return <div>Result: {result}</div>;
}

示例2:

import axios from "axios"; // 引入 axios 用于发送 HTTP 请求
import { useEffect, useState, useMemo } from "react"; // 引入 React 的钩子

export default function MemoTutorial() {
  // 定义两个 state,分别存储数据和开关状态
  const [data, setData] = useState(null); // 存储从 API 获取的数据
  const [toggle, setToggle] = useState(false); // 存储按钮的切换状态

  // useEffect 钩子在组件加载时执行一次,用于获取数据
  useEffect(() => {
    axios
      .get("https://jsonplaceholder.typicode.com/comments") // 发送 GET 请求到指定 API
      .then((response) => {
        setData(response.data); // 将返回的数据保存到 state
      });
  }, []); // 空依赖数组确保只在组件首次加载时执行一次

  // 定义一个函数,用于查找评论中名字最长的 name 字段
  const findLongestName = (comments) => {
    if (!comments) return null; // 如果数据为空,返回 null

    let longestName = ""; // 初始化最长的名字为一个空字符串
    for (let i = 0; i < comments.length; i++) {
      let currentName = comments[i].name; // 获取当前评论的名字
      if (currentName.length > longestName.length) {
        longestName = currentName; // 如果当前名字更长,则更新最长名字
      }
    }

    console.log("THIS WAS COMPUTED"); // 用于调试,显示该函数何时被调用

    return longestName; // 返回找到的最长名字
  };

  // 使用 useMemo 缓存 findLongestName 的计算结果
  // 仅当 `toggle` 发生变化时重新计算
  const getLongestName = useMemo(() => findLongestName(data), [toggle]);

  return (
    <div className="App">
      {/* 显示最长名字,如果为空则显示 null */}
      <div> {getLongestName} </div>

      {/* 按钮用于切换 toggle 的状态 */}
      <button
        onClick={() => {
          setToggle(!toggle); // 切换 toggle 的值(true/false)
        }}
      >
        {" "}
        Toggle
      </button>

      {/* 根据 toggle 的值有条件地显示标题 */}
      {toggle && <h1> toggle </h1>}
    </div>
  );
}

在这里,useMemo 确保只有当 number 改变时才会重新计算结果。

useCallback - 缓存回调函数

useCallback 用于缓存函数,避免因依赖变更而导致不必要的重建。

示例1:

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

function Button({ handleClick }) {
  return <button onClick={handleClick}>Click Me</button>;
}

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

  const increment = useCallback(() => {
    setCount((c) => c + 1);
  }, []);

  return (
    <div>
      <Button handleClick={increment} />
      <p>Count: {count}</p>
    </div>
  );
}

示例2:

Child.js

import React, { useEffect } from "react";

// 子组件 `Child`,接收 `returnComment` 作为属性
function Child({ returnComment }) {
  // 使用 useEffect,当 `returnComment` 变化时执行回调
  useEffect(() => {
    console.log("FUNCTION WAS CALLED"); // 打印日志以显示函数被调用
  }, [returnComment]); // 依赖于 `returnComment`,只有该函数变化时才重新执行

  // 渲染 `returnComment` 函数的返回值,将 "Pedro" 作为参数传入
  return <div>{returnComment("Pedro")}</div>;
}

export default Child; // 导出子组件

CallBackTutorial.js

import axios from "axios"; // 引入 axios(虽然当前代码中没有使用)
import { useCallback, useState } from "react"; // 引入 React 钩子 useCallback 和 useState
import Child from "./Child"; // 引入子组件 Child

export default function CallBackTutorial() {
  // 定义 `toggle` 状态用于按钮切换
  const [toggle, setToggle] = useState(false);
  // 定义 `data` 状态用于存储一段字符串
  const [data, setData] = useState("Yo, pls sub to the channel!");

  // 使用 useCallback 缓存函数 `returnComment` 的定义
  // 仅当 `data` 变化时重新生成函数
  const returnComment = useCallback(
    (name) => {
      return data + name; // 将 `data` 和传入的名字拼接返回
    },
    [data] // 依赖 `data`
  );

  return (
    <div className="App">
      {/* 将 `returnComment` 作为属性传递给子组件 */}
      <Child returnComment={returnComment} />

      {/* 按钮用于切换 `toggle` 状态 */}
      <button
        onClick={() => {
          setToggle(!toggle); // 切换 toggle 的值
        }}
      >
        {" "}
        Toggle
      </button>

      {/* 根据 toggle 状态有条件地显示标题 */}
      {toggle && <h1> toggle </h1>}
    </div>
  );
}

使用 useCallback 可以确保 increment 函数在不需要时不会被重新创建。

useLayoutEffect - 布局处理

useLayoutEffect 在所有 DOM 变更之后同步触发,通常用于测量 DOM 或者做一些与布局相关的操作。

import { useLayoutEffect, useEffect, useRef } from "react"; // 引入 useLayoutEffect、useEffect 和 useRef Hook

function LayoutEffectTutorial() {
  // 使用 useRef 创建一个引用对象,用于获取 <input> DOM 元素
  const inputRef = useRef(null);

  // 使用 useLayoutEffect
  useLayoutEffect(() => {
    // 这是同步执行的副作用,在 DOM 更新后、浏览器绘制屏幕前执行
    // 输出输入框的初始 value 值,此时值仍然是 "PEDRO"
    console.log(inputRef.current.value);
  }, []); // 空依赖数组,表示只在组件挂载时运行一次

  // 使用 useEffect
  useEffect(() => {
    // 这是异步执行的副作用,在浏览器完成屏幕绘制后执行
    // 将输入框的值设置为 "HELLO"
    inputRef.current.value = "HELLO";
  }, []); // 空依赖数组,表示只在组件挂载时运行一次

  // 返回渲染的 JSX
  return (
    <div className="App">
      {/* 输入框绑定了 ref,以便通过 inputRef 引用访问 DOM 元素 */}
      <input
        ref={inputRef} // 将 inputRef 绑定到这个 <input> 元素
        value="PEDRO" // 输入框的初始值设置为 "PEDRO"
        style={{ width: 400, height: 40 }} // 设置输入框的宽度和高度
      />
    </div>
  );
}

export default LayoutEffectTutorial; // 导出组件以供使用

useLayoutEffect 适用于需要同步访问 DOM 的场景。

useImperativeHandle - 自定义实例值

useImperativeHandle 用于自定义暴露给父组件的实例值,通常与 forwardRef 搭配使用。

示例1:

import React, { useRef } from "react"; // 引入 React 和 useRef Hook
import Button from "./Button"; // 引入子组件 Button

// 定义父组件 ImperativeHandle
function ImperativeHandle() {
  // 创建一个 ref 对象,初始值为 null
  const buttonRef = useRef(null);

  return (
    <div>
      {/* 父组件的按钮 */}
      <button
        onClick={() => {
          // 调用子组件通过 useImperativeHandle 暴露的 alterToggle 方法
          buttonRef.current.alterToggle();
        }}
      >
        Button From Parent
      </button>

      {/* 子组件 Button,并将 ref 传递给子组件 */}
      <Button ref={buttonRef} />
    </div>
  );
}

export default ImperativeHandle; // 导出父组件供其他地方使用

示例2:

ImperativeHandle.js

import React, { useRef } from "react"; // 引入 React 和 useRef,用于创建引用
import Button from "./Button"; // 引入子组件 Button

// 定义父组件 ImperativeHandle
function ImperativeHandle() {
  // 创建一个 ref,用于获取子组件的引用
  const buttonRef = useRef(null);

  return (
    <div>
      {/* 按钮,用于调用子组件通过 ref 暴露的方法 */}
      <button
        onClick={() => {
          // 通过 ref 调用子组件的 alterToggle 方法
          buttonRef.current.alterToggle();
        }}
      >
        Button From Parent
      </button>

      {/* 渲染子组件 Button,并通过 ref 将引用传递给子组件 */}
      <Button ref={buttonRef} />
    </div>
  );
}

export default ImperativeHandle; // 导出父组件

Button.js

import React, { forwardRef, useImperativeHandle, useState } from "react";

// 定义子组件 Button,并使用 forwardRef 将 ref 传递给子组件
const Button = forwardRef((props, ref) => {
  // 定义状态 toggle,用于控制是否显示 "Toggle" 文本
  const [toggle, setToggle] = useState(false);

  // 使用 useImperativeHandle 自定义暴露给父组件的内容
  useImperativeHandle(ref, () => ({
    // 自定义方法 alterToggle,切换 toggle 的状态
    alterToggle() {
      setToggle(!toggle); // 切换 toggle 的值
    },
  }));

  // 子组件的 JSX 结构
  return (
    <>
      {/* 子组件按钮 */}
      <button>Button From Child</button>

      {/* 根据 toggle 的值决定是否显示 <span> */}
      {toggle && <span>Toggle</span>}
    </>
  );
});

export default Button; // 导出子组件 Button 供父组件使用

执行原理解析

  1. ref 的传递:
    • 父组件通过 <Button ref={buttonRef} /> 将 ref 传递到子组件。
    • 子组件使用 forwardRef 接收这个 ref,允许对子组件的内容进行自定义暴露。
  2. 自定义暴露内容:
    • 子组件通过 useImperativeHandle 定义了暴露给父组件的内容(这里是 alterToggle 方法)。
    • React 将父组件的 buttonRef.current 指向 useImperativeHandle 返回的对象,这个对象包含了 alterToggle 方法。
  3. 父组件调用:
    • 父组件通过 buttonRef.current.alterToggle() 调用子组件暴露的 alterToggle 方法。

useImperativeHandle 用于控制子组件对外暴露的功能。

useDebugValue - 自定义 Hook 调试

useDebugValue 主要用于自定义 Hook 的调试,帮助开发者更方便地在 React DevTools 中查看状态。

import React, { useState, useDebugValue } from 'react'; // 引入 React,useState 管理状态,useDebugValue 用于调试

// 定义一个自定义 Hook
function useCustomHook(initialValue) {
  // 使用 useState 管理内部状态
  const [value, setValue] = useState(initialValue);

  // 使用 useDebugValue 提供自定义的调试信息
  // 如果 `value > 5`,调试信息为 "High",否则为 "Low"
  useDebugValue(value > 5 ? 'High' : 'Low');

  // 返回当前状态值和修改状态的方法
  return [value, setValue];
}

// 定义主组件 App
function App() {
  // 使用自定义 Hook 初始化 `count` 和 `setCount`
  const [count, setCount] = useCustomHook(0);

  return (
    <div>
      {/* 显示当前计数值 */}
      <p>Count: {count}</p>

      {/* 按钮用于增加计数 */}
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

export default App; // 导出主组件

useDebugValue 为自定义 Hook 提供调试信息,在开发过程中非常有用。

useCallback 和 useMemo 的区别

两者的功能和用途相似,都是 React 用于性能优化的 Hook,但它们的核心区别在于目标是什么:

特性useCallbackuseMemo
核心目标缓存函数的引用,防止函数在每次渲染时重新创建缓存计算结果,防止每次渲染时重新计算
返回值函数(缓存后的函数)值(缓存后的计算结果)
用途用于将函数传递给子组件,避免子组件因函数引用变化而重新渲染用于缓存计算结果,优化依赖复杂的计算逻辑
依赖数组控制函数是否需要更新控制计算结果是否需要重新计算

总结

React 提供了丰富的 Hook 来管理状态、副作用和其他功能。这十个常用 Hook 能够极大提高开发效率,简化代码结构。通过这篇指南,希望你能够深入理解并有效应用这些 Hook,在项目中写出更加简洁、可维护的代码。

本作品采用 知识共享署名-非商业性使用 4.0 国际许可协议 进行许可
标签: Hook React
最后更新:2024年 12月 2日

白白的白豆腐

这个人很懒,什么都没留下

点赞
< 上一篇

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

内容 隐藏
userState - 管理状态
userEffect - 处理副作用
依赖数组的含义:
useContext - 使用上下文
userReducer - 复杂状态管理
useRef - 引用 DOM 元素
useMemo - 计算缓存
useCallback - 缓存回调函数
useLayoutEffect - 布局处理
useImperativeHandle - 自定义实例值
执行原理解析
useDebugValue - 自定义 Hook 调试
useCallback 和 useMemo 的区别
总结

COPYRIGHT © 2024 白白的白豆腐. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

辽ICP备2022008846号