Appearance
React Hooks 核心
React Hooks 是 React 16.8+ 引入的新特性,它允许我们在函数组件中使用状态和其他 React 特性。本章节将介绍 React Hooks 的核心概念和使用方法。
8.1 什么是Hooks?
Hooks 的定义
Hooks 是一些特殊的函数,它们允许我们在函数组件中使用 React 的状态和生命周期特性。
解决的问题
- 类组件的复杂性:类组件中 this 的指向问题,生命周期方法的拆分
- 逻辑复用:在类组件中,逻辑复用通常需要使用高阶组件或 render props,代码复杂
- 函数组件的限制:在 React 16.8 之前,函数组件无法使用状态和生命周期方法
Hooks 的优势
- 代码更简洁:使用函数组件,避免了类组件的复杂性
- 逻辑复用更简单:可以使用自定义 Hooks 来复用逻辑
- 更好的类型推断:函数组件在 TypeScript 中更容易进行类型推断
- 更好的性能:减少了类组件的开销
8.2 常用Hooks详解
useState:状态管理
基本使用:
jsx
import { useState } from 'react';
function Counter() {
// 声明一个名为 count 的状态变量,初始值为 0
// setCount 是更新 count 的函数
const [count, setCount] = useState(0);
return (
<div>
<p>计数:{count}</p>
<button onClick={() => setCount(count + 1)}>增加</button>
</div>
);
}复杂状态:
jsx
import { useState } from 'react';
function UserProfile() {
const [user, setUser] = useState({
name: '张三',
age: 20,
email: 'zhangsan@example.com'
});
function updateName(name) {
setUser(prevUser => ({
...prevUser,
name
}));
}
return (
<div>
<h1>{user.name}</h1>
<input
type="text"
value={user.name}
onChange={e => updateName(e.target.value)}
/>
</div>
);
}useEffect:副作用处理
基本使用:
jsx
import { useState, useEffect } from 'react';
function Timer() {
const [count, setCount] = useState(0);
// 组件挂载时执行一次,相当于 componentDidMount
useEffect(() => {
console.log('组件挂载了');
}, []);
// 组件更新时执行,相当于 componentDidUpdate
useEffect(() => {
console.log('组件更新了,count:', count);
}, [count]);
// 组件卸载时执行,相当于 componentWillUnmount
useEffect(() => {
return () => {
console.log('组件卸载了');
};
}, []);
return (
<div>
<p>计数:{count}</p>
<button onClick={() => setCount(count + 1)}>增加</button>
</div>
);
}依赖项:
- 空数组
[]:只在组件挂载和卸载时执行 - 包含依赖项:当依赖项变化时执行
- 无依赖项:每次组件渲染时都执行
清除副作用:
jsx
import { useState, useEffect } from 'react';
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
// 设置定时器
const timer = setInterval(() => {
setCount(prevCount => prevCount + 1);
}, 1000);
// 清除定时器
return () => {
clearInterval(timer);
};
}, []);
return <p>计数:{count}</p>;
}常见场景:
- 数据请求
- 订阅事件
- 操作 DOM
- 设置定时器
useRef:获取DOM元素、保存持久化值
获取 DOM 元素:
jsx
import { useRef } from 'react';
function FocusInput() {
const inputRef = useRef(null);
function focusInput() {
inputRef.current.focus();
}
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>聚焦输入框</button>
</div>
);
}保存持久化值:
jsx
import { useState, useEffect, useRef } from 'react';
function Timer() {
const [count, setCount] = useState(0);
const prevCountRef = useRef(count);
useEffect(() => {
prevCountRef.current = count;
});
return (
<div>
<p>当前计数:{count}</p>
<p>之前计数:{prevCountRef.current}</p>
<button onClick={() => setCount(count + 1)}>增加</button>
</div>
);
}useContext:跨组件通信
创建 Context:
jsx
// src/contexts/ThemeContext.jsx
import { createContext, useState, useContext } from 'react';
// 创建 Context
const ThemeContext = createContext();
// 提供者组件
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
// 自定义 Hook
export function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within a ThemeProvider');
}
return context;
}使用 Context:
jsx
import { ThemeProvider, useTheme } from './contexts/ThemeContext';
function ThemedButton() {
const { theme, setTheme } = useTheme();
return (
<div>
<p>当前主题:{theme}</p>
<button
onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
style={{
backgroundColor: theme === 'light' ? '#fff' : '#333',
color: theme === 'light' ? '#333' : '#fff'
}}
>
切换主题
</button>
</div>
);
}
function App() {
return (
<ThemeProvider>
<ThemedButton />
</ThemeProvider>
);
}8.3 Hooks 使用规则
1. 只在函数最顶层使用 Hooks
- 不要在循环、条件或嵌套函数中使用 Hooks
- 确保 Hooks 在每次渲染时都以相同的顺序调用
2. 只在 React 函数中使用 Hooks
- 在函数组件中使用
- 在自定义 Hooks 中使用
- 不要在普通 JavaScript 函数中使用
3. 依赖项数组要正确设置
- 不要遗漏依赖项
- 不要包含不必要的依赖项
- 对于复杂对象,要考虑使用 useMemo 或 useCallback
4. 自定义 Hooks 命名规范
- 自定义 Hooks 必须以
use开头 - 这样 React 才能识别它们是 Hooks
8.4 自定义Hooks封装
基本结构
jsx
import { useState, useEffect } from 'react';
function useCustomHook(initialValue) {
const [state, setState] = useState(initialValue);
// 逻辑
return [state, setState];
}
export default useCustomHook;示例1:useRequest
jsx
import { useState, useEffect } from 'react';
function useRequest(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let cancelled = false;
async function fetchData() {
setLoading(true);
setError(null);
try {
const response = await fetch(url);
const json = await response.json();
if (!cancelled) {
setData(json);
}
} catch (err) {
if (!cancelled) {
setError(err);
}
} finally {
if (!cancelled) {
setLoading(false);
}
}
}
fetchData();
return () => {
cancelled = true;
};
}, [url]);
return { data, loading, error };
}
// 使用
function DataDisplay() {
const { data, loading, error } = useRequest('https://api.example.com/data');
if (loading) return <p>加载中...</p>;
if (error) return <p>错误:{error.message}</p>;
return <div>{JSON.stringify(data)}</div>;
}示例2:useLocalStorage
jsx
import { useState, useEffect } from 'react';
function useLocalStorage(key, initialValue) {
// 从 localStorage 中获取初始值
const [value, setValue] = useState(() => {
try {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error(error);
return initialValue;
}
});
// 当值变化时,更新 localStorage
useEffect(() => {
try {
localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.error(error);
}
}, [key, value]);
return [value, setValue];
}
// 使用
function TodoList() {
const [todos, setTodos] = useLocalStorage('todos', []);
function addTodo(todo) {
setTodos(prevTodos => [...prevTodos, todo]);
}
return (
<div>
<ul>
{todos.map((todo, index) => (
<li key={index}>{todo}</li>
))}
</ul>
<button onClick={() => addTodo('新任务')}>添加任务</button>
</div>
);
}实战练习
练习1:useCounter
jsx
import { useState, useCallback } from 'react';
function useCounter(initialValue = 0) {
const [count, setCount] = useState(initialValue);
const increment = useCallback(() => setCount(prev => prev + 1), []);
const decrement = useCallback(() => setCount(prev => prev - 1), []);
const reset = useCallback(() => setCount(initialValue), [initialValue]);
return { count, increment, decrement, reset };
}
// 使用
function Counter() {
const { count, increment, decrement, reset } = useCounter(10);
return (
<div>
<p>计数:{count}</p>
<button onClick={increment}>增加</button>
<button onClick={decrement}>减少</button>
<button onClick={reset}>重置</button>
</div>
);
}练习2:useMousePosition
jsx
import { useState, useEffect } from 'react';
function useMousePosition() {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
function handleMouseMove(e) {
setPosition({ x: e.clientX, y: e.clientY });
}
window.addEventListener('mousemove', handleMouseMove);
return () => {
window.removeEventListener('mousemove', handleMouseMove);
};
}, []);
return position;
}
// 使用
function MouseTracker() {
const { x, y } = useMousePosition();
return (
<div>
<p>鼠标位置:x = {x}, y = {y}</p>
</div>
);
}练习3:useForm
jsx
import { useState } from 'react';
function useForm(initialValues) {
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState({});
function handleChange(e) {
const { name, value, type, checked } = e.target;
setValues(prev => ({
...prev,
[name]: type === 'checkbox' ? checked : value
}));
}
function handleSubmit(onSubmit) {
return function(e) {
e.preventDefault();
onSubmit(values);
};
}
function setFieldValue(name, value) {
setValues(prev => ({
...prev,
[name]: value
}));
}
return {
values,
errors,
handleChange,
handleSubmit,
setFieldValue
};
}
// 使用
function LoginForm() {
const { values, handleChange, handleSubmit } = useForm({
email: '',
password: ''
});
function onSubmit(data) {
console.log('登录数据:', data);
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<label>邮箱:</label>
<input
type="email"
name="email"
value={values.email}
onChange={handleChange}
/>
</div>
<div>
<label>密码:</label>
<input
type="password"
name="password"
value={values.password}
onChange={handleChange}
/>
</div>
<button type="submit">登录</button>
</form>
);
}通过本章节的学习,你已经掌握了 React Hooks 的核心概念和使用方法。Hooks 是 React 16.8+ 的重要特性,它使得函数组件更加强大和灵活,是现代 React 开发的基础。
