React Hooks 是 React 16.8 版本引入的新特性,它允许你在不编写类组件的情况下使用状态和其他 React 特性。Hooks 的出现极大地简化了 React 组件的编写和理解,成为现代 React 开发的标准方式。
为什么使用 Hooks
代码复用
Hooks 允许你将组件逻辑提取到可重用的函数中,避免了高阶组件和渲染属性等复杂模式。
更简洁的代码
使用 Hooks 可以编写更简洁、更易于理解的代码,减少模板代码的数量。
更好的类型支持
Hooks 与 TypeScript 有很好的兼容性,提供了更好的类型推断和检查。
改进的性能
Hooks 可以帮助你优化组件性能,避免不必要的重新渲染。
常用的 React Hooks
1. useState
useState钩子允许你在函数组件中使用状态:
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
<button onClick={() => setCount(count - 1)}>Decrement</button>
</div>
);
}
2. useEffect
useEffect钩子允许你在函数组件中执行副作用操作,如数据获取、订阅或手动 DOM 操作:
import { useState, useEffect } from "react";
function DataFetcher() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// 数据获取操作
fetch("https://api.example.com/data")
.then((response) => response.json())
.then((data) => {
setData(data);
setLoading(false);
})
.catch((error) => {
console.error("Error fetching data:", error);
setLoading(false);
});
}, []); // 空依赖数组表示只在组件挂载时执行一次
if (loading) {
return <div>Loading...</div>;
}
return <div>Data: {JSON.stringify(data)}</div>;
}
3. useContext
useContext钩子允许你访问 React 上下文,避免了通过 props 层层传递数据:
import { createContext, useContext } from "react";
// 创建上下文
const ThemeContext = createContext("light");
// 使用上下文
function ThemeButton() {
const theme = useContext(ThemeContext);
return (
<button
style={{
backgroundColor: theme === "dark" ? "#333" : "#fff",
color: theme === "dark" ? "#fff" : "#333",
}}
>
{theme} Theme
</button>
);
}
// 提供上下文
function App() {
return (
<ThemeContext.Provider value="dark">
<ThemeButton />
</ThemeContext.Provider>
);
}
4. useReducer
useReducer钩子是useState的替代方案,用于管理复杂的状态逻辑:
import { 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 };
case "RESET":
return { count: 0 };
default:
return state;
}
}
function Counter() {
// 使用useReducer
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>
<button onClick={() => dispatch({ type: "RESET" })}>Reset</button>
</div>
);
}
5. useCallback
useCallback钩子返回一个记忆化的回调函数,避免不必要的重新渲染:
import { useState, useCallback } from "react";
function ParentComponent() {
const [count, setCount] = useState(0);
// 使用useCallback记忆化回调函数
const handleClick = useCallback(() => {
console.log("Button clicked");
}, []); // 空依赖数组表示回调函数不会变化
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
<ChildComponent onClick={handleClick} />
</div>
);
}
function ChildComponent({ onClick }) {
return <button onClick={onClick}>Child Button</button>;
}
6. useMemo
useMemo钩子返回一个记忆化的值,避免不必要的重复计算:
import { useState, useMemo } from "react";
function ExpensiveComponent() {
const [count, setCount] = useState(0);
const [items, setItems] = useState([1, 2, 3, 4, 5]);
// 使用useMemo记忆化计算结果
const sum = useMemo(() => {
console.log("Calculating sum...");
return items.reduce((total, item) => total + item, 0);
}, [items]); // 只有当items变化时才重新计算
return (
<div>
<p>Count: {count}</p>
<p>Sum: {sum}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
<button onClick={() => setItems([...items, items.length + 1])}>
Add Item
</button>
</div>
);
}
7. useRef
useRef钩子返回一个可变的 ref 对象,其.current 属性可以保存任何值:
import { useRef, useEffect } from "react";
function FocusInput() {
const inputRef = useRef(null);
useEffect(() => {
// 组件挂载后自动聚焦输入框
inputRef.current.focus();
}, []);
return (
<div>
<input ref={inputRef} type="text" placeholder="Enter text..." />
</div>
);
}
8. useImperativeHandle
useImperativeHandle钩子允许你自定义暴露给父组件的实例值:
import { forwardRef, useImperativeHandle } from "react";
const CustomInput = forwardRef((props, ref) => {
const internalRef = useRef(null);
// 自定义暴露给父组件的方法
useImperativeHandle(ref, () => ({
focus: () => {
internalRef.current.focus();
},
clear: () => {
internalRef.current.value = "";
},
}));
return <input ref={internalRef} {...props} />;
});
function ParentComponent() {
const inputRef = useRef(null);
return (
<div>
<CustomInput ref={inputRef} placeholder="Enter text..." />
<button onClick={() => inputRef.current.focus()}>Focus</button>
<button onClick={() => inputRef.current.clear()}>Clear</button>
</div>
);
}
9. useLayoutEffect
useLayoutEffect钩子与useEffect类似,但它会在所有 DOM 变更之后同步执行,用于读取 DOM 布局并同步触发重渲染:
import { useState, useLayoutEffect } from "react";
function MeasuredComponent() {
const [width, setWidth] = useState(0);
const divRef = useRef(null);
useLayoutEffect(() => {
// 读取DOM布局
const { offsetWidth } = divRef.current;
setWidth(offsetWidth);
}, []);
return (
<div ref={divRef}>
<p>Width: {width}px</p>
</div>
);
}
10. useDebugValue
useDebugValue钩子用于在 React 开发者工具中显示自定义 Hook 的标签:
import { useState, useDebugValue } from "react";
function useCustomHook(initialValue) {
const [value, setValue] = useState(initialValue);
// 在React开发者工具中显示自定义Hook的标签
useDebugValue(value > 0 ? "Positive" : "Negative");
return [value, setValue];
}
Hooks 的规则
使用 Hooks 时需要遵循以下规则:
1. 只在顶层调用 Hooks
不要在循环、条件或嵌套函数中调用 Hooks:
// 错误的做法
if (count > 0) {
useState(count);
}
// 正确的做法
const [value, setValue] = useState(count > 0 ? count : 0);
2. 只在 React 函数中调用 Hooks
Hooks 只能在函数组件或自定义 Hook 中使用:
// 错误的做法
function regularFunction() {
useState("value");
}
// 正确的做法
function FunctionComponent() {
const [value, setValue] = useState("value");
return <div>{value}</div>;
}
3. 使用 ESLint 插件
使用eslint-plugin-react-hooks插件可以帮助你遵守 Hooks 的规则:
npm install eslint-plugin-react-hooks --save-dev
在.eslintrc.js中配置:
module.exports = {
plugins: ["react-hooks"],
rules: {
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn",
},
};
自定义 Hooks
你可以创建自己的自定义 Hooks 来封装可重用的组件逻辑:
import { useState, useEffect } from "react";
// 自定义Hook:useFetch
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
const response = await fetch(url);
if (!response.ok) {
throw new Error("Network response was not ok");
}
const result = await response.json();
setData(result);
setError(null);
} catch (error) {
setError(error.message);
setData(null);
} finally {
setLoading(false);
}
};
fetchData();
}, [url]);
return { data, loading, error };
}
// 使用自定义Hook
function DataComponent() {
const { data, loading, error } = useFetch("https://api.example.com/data");
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return <div>Data: {JSON.stringify(data)}</div>;
}
Hooks 与类组件的比较
| 特性 | 类组件 | 函数组件 + Hooks |
|---|---|---|
| 状态管理 | this.state 和 this.setState | useState |
| 生命周期方法 | componentDidMount, componentDidUpdate, componentWillUnmount | useEffect |
| 上下文 | this.context | useContext |
| 引用 | createRef | useRef |
| 代码复用 | 高阶组件、渲染属性 | 自定义 Hooks |
| 性能优化 | shouldComponentUpdate, PureComponent | useCallback, useMemo, React.memo |
总结
React Hooks 是 React 开发的重要里程碑,它简化了组件的编写和理解,提供了更好的代码复用和性能优化能力。通过学习和掌握常用的 React Hooks,以及自定义 Hooks 的创建和使用,你可以编写更加简洁、高效和可维护的 React 组件。
随着 React 生态系统的不断发展,Hooks 已经成为现代 React 开发的标准方式,建议你在新项目中使用 Hooks,并逐步将旧项目迁移到 Hooks。