在 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 详细解析请看下面这篇博客
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 供父组件使用
执行原理解析
ref
的传递:- 父组件通过
<Button ref={buttonRef} />
将ref
传递到子组件。 - 子组件使用
forwardRef
接收这个ref
,允许对子组件的内容进行自定义暴露。
- 父组件通过
- 自定义暴露内容:
- 子组件通过
useImperativeHandle
定义了暴露给父组件的内容(这里是alterToggle
方法)。 - React 将父组件的
buttonRef.current
指向useImperativeHandle
返回的对象,这个对象包含了alterToggle
方法。
- 子组件通过
- 父组件调用:
- 父组件通过
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,但它们的核心区别在于目标是什么:
特性 | useCallback | useMemo |
---|---|---|
核心目标 | 缓存函数的引用,防止函数在每次渲染时重新创建 | 缓存计算结果,防止每次渲染时重新计算 |
返回值 | 函数(缓存后的函数) | 值(缓存后的计算结果) |
用途 | 用于将函数传递给子组件,避免子组件因函数引用变化而重新渲染 | 用于缓存计算结果,优化依赖复杂的计算逻辑 |
依赖数组 | 控制函数是否需要更新 | 控制计算结果是否需要重新计算 |
总结
React 提供了丰富的 Hook 来管理状态、副作用和其他功能。这十个常用 Hook 能够极大提高开发效率,简化代码结构。通过这篇指南,希望你能够深入理解并有效应用这些 Hook,在项目中写出更加简洁、可维护的代码。
文章评论