学习目标
- 深入理解useState Hook
- 掌握useEffect Hook的使用
- 学会useContext Hook
- 理解自定义Hooks
- 掌握Hooks规则和最佳实践
学习时间安排
总时长:6-7小时
- useState深入:1.5小时
- useEffect详解:2小时
- useContext和useReducer:1.5小时
- 自定义Hooks:1.5小时
- 实践项目:1.5-2小时
第一部分:useState Hook深入 (1.5小时)
1.1 useState基础回顾
基本用法(详细注释版)
javascript
// src/components/UseStateBasic.js
// 导入React和useState Hook
import React, { useState } from 'react';
// 定义UseStateBasic组件
function UseStateBasic() {
// 使用useState Hook管理状态
// useState返回一个数组,包含当前状态值和更新状态的函数
const [count, setCount] = useState(0);
// 定义增加计数器的函数
const increment = () => {
// 使用setCount函数更新状态
setCount(count + 1);
};
// 定义减少计数器的函数
const decrement = () => {
setCount(count - 1);
};
// 定义重置计数器的函数
const reset = () => {
setCount(0);
};
// 返回JSX元素
return (
<div>
<h2>useState Basic Example</h2>
<p>Count: {count}</p>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
<button onClick={reset}>Reset</button>
</div>
);
}
// 导出组件
export default UseStateBasic;
1.2 函数式更新
函数式更新示例(详细注释版)
javascript
// src/components/UseStateFunctional.js
// 导入React和useState Hook
import React, { useState } from 'react';
// 定义UseStateFunctional组件
function UseStateFunctional() {
// 使用useState Hook管理状态
const [count, setCount] = useState(0);
// 使用函数式更新
// 这种方式可以确保获取到最新的状态值
const increment = () => {
// 使用函数式更新,prevCount是前一个状态值
setCount(prevCount => prevCount + 1);
};
// 连续更新示例
const incrementByTwo = () => {
// 使用函数式更新确保每次更新都基于最新状态
setCount(prevCount => prevCount + 1);
setCount(prevCount => prevCount + 1);
};
// 异步更新示例
const asyncIncrement = () => {
// 模拟异步操作
setTimeout(() => {
// 使用函数式更新确保获取最新状态
setCount(prevCount => prevCount + 1);
}, 1000);
};
// 返回JSX元素
return (
<div>
<h2>useState Functional Updates</h2>
<p>Count: {count}</p>
<button onClick={increment}>+1</button>
<button onClick={incrementByTwo}>+2</button>
<button onClick={asyncIncrement}>Async +1</button>
</div>
);
}
// 导出组件
export default UseStateFunctional;
1.3 对象状态管理
对象状态示例(详细注释版)
javascript
// src/components/UseStateObject.js
// 导入React和useState Hook
import React, { useState } from 'react';
// 定义UseStateObject组件
function UseStateObject() {
// 使用useState Hook管理对象状态
const [user, setUser] = useState({
name: '',
email: '',
age: 0,
isActive: false
});
// 更新用户信息的函数
const updateUser = (field, value) => {
// 使用展开运算符保持其他属性不变
setUser(prevUser => ({
...prevUser,
[field]: value
}));
};
// 重置用户信息
const resetUser = () => {
setUser({
name: '',
email: '',
age: 0,
isActive: false
});
};
// 返回JSX元素
return (
<div>
<h2>useState Object Management</h2>
{/* 用户信息显示 */}
<div>
<h3>User Information</h3>
<p>Name: {user.name}</p>
<p>Email: {user.email}</p>
<p>Age: {user.age}</p>
<p>Active: {user.isActive ? 'Yes' : 'No'}</p>
</div>
{/* 用户信息输入表单 */}
<div>
<h3>Update User</h3>
<input
type="text"
placeholder="Name"
value={user.name}
onChange={(e) => updateUser('name', e.target.value)}
/>
<input
type="email"
placeholder="Email"
value={user.email}
onChange={(e) => updateUser('email', e.target.value)}
/>
<input
type="number"
placeholder="Age"
value={user.age}
onChange={(e) => updateUser('age', parseInt(e.target.value) || 0)}
/>
<label>
<input
type="checkbox"
checked={user.isActive}
onChange={(e) => updateUser('isActive', e.target.checked)}
/>
Active
</label>
<button onClick={resetUser}>Reset</button>
</div>
</div>
);
}
// 导出组件
export default UseStateObject;
1.4 数组状态管理
数组状态示例(详细注释版)
javascript
// src/components/UseStateArray.js
// 导入React和useState Hook
import React, { useState } from 'react';
// 定义UseStateArray组件
function UseStateArray() {
// 使用useState Hook管理数组状态
const [items, setItems] = useState([]);
const [newItem, setNewItem] = useState('');
// 添加新项目
const addItem = () => {
if (newItem.trim()) {
// 使用展开运算符添加新项目
setItems(prevItems => [...prevItems, {
id: Date.now(),
text: newItem.trim(),
completed: false
}]);
setNewItem('');
}
};
// 删除项目
const deleteItem = (id) => {
// 使用filter方法删除指定项目
setItems(prevItems => prevItems.filter(item => item.id !== id));
};
// 切换项目完成状态
const toggleItem = (id) => {
// 使用map方法更新指定项目
setItems(prevItems => prevItems.map(item =>
item.id === id ? { ...item, completed: !item.completed } : item
));
};
// 清空所有项目
const clearAll = () => {
setItems([]);
};
// 返回JSX元素
return (
<div>
<h2>useState Array Management</h2>
{/* 添加新项目 */}
<div>
<input
type="text"
placeholder="Add new item"
value={newItem}
onChange={(e) => setNewItem(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && addItem()}
/>
<button onClick={addItem}>Add Item</button>
</div>
{/* 项目列表 */}
<div>
{items.length === 0 ? (
<p>No items yet</p>
) : (
items.map(item => (
<div key={item.id} style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<input
type="checkbox"
checked={item.completed}
onChange={() => toggleItem(item.id)}
/>
<span style={{ textDecoration: item.completed ? 'line-through' : 'none' }}>
{item.text}
</span>
<button onClick={() => deleteItem(item.id)}>Delete</button>
</div>
))
)}
</div>
{/* 操作按钮 */}
<div>
<button onClick={clearAll}>Clear All</button>
<p>Total items: {items.length}</p>
<p>Completed: {items.filter(item => item.completed).length}</p>
</div>
</div>
);
}
// 导出组件
export default UseStateArray;
第二部分:useEffect Hook详解 (2小时)
2.1 useEffect基础
基本用法(详细注释版)
javascript
// src/components/UseEffectBasic.js
// 导入React、useState和useEffect Hook
import React, { useState, useEffect } from 'react';
// 定义UseEffectBasic组件
function UseEffectBasic() {
// 使用useState Hook管理状态
const [count, setCount] = useState(0);
const [name, setName] = useState('');
// useEffect Hook - 每次渲染后都会执行
useEffect(() => {
// 这个函数在每次组件渲染后都会执行
console.log('Component rendered');
});
// useEffect Hook - 只在组件挂载时执行一次
useEffect(() => {
// 这个函数只在组件第一次挂载时执行
console.log('Component mounted');
// 清理函数,在组件卸载时执行
return () => {
console.log('Component unmounted');
};
}, []); // 空依赖数组表示只在挂载和卸载时执行
// useEffect Hook - 依赖count变化时执行
useEffect(() => {
// 这个函数在count变化时执行
console.log('Count changed:', count);
}, [count]); // 依赖数组包含count
// useEffect Hook - 依赖name变化时执行
useEffect(() => {
// 这个函数在name变化时执行
console.log('Name changed:', name);
}, [name]); // 依赖数组包含name
// 返回JSX元素
return (
<div>
<h2>useEffect Basic Example</h2>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
<input
type="text"
placeholder="Enter name"
value={name}
onChange={(e) => setName(e.target.value)}
/>
</div>
);
}
// 导出组件
export default UseEffectBasic;
2.2 数据获取
数据获取示例(详细注释版)
javascript
// src/components/UseEffectDataFetching.js
// 导入React、useState和useEffect Hook
import React, { useState, useEffect } from 'react';
// 定义UseEffectDataFetching组件
function UseEffectDataFetching() {
// 使用useState Hook管理状态
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [userId, setUserId] = useState(1);
// 获取用户列表
const fetchUsers = async () => {
setLoading(true);
setError(null);
try {
// 模拟API调用
const response = await fetch('https://jsonplaceholder.typicode.com/users');
if (!response.ok) {
throw new Error('Failed to fetch users');
}
const data = await response.json();
setUsers(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
// 获取单个用户
const fetchUser = async (id) => {
setLoading(true);
setError(null);
try {
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
if (!response.ok) {
throw new Error('Failed to fetch user');
}
const data = await response.json();
setUsers([data]);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
// useEffect Hook - 组件挂载时获取用户列表
useEffect(() => {
fetchUsers();
}, []); // 空依赖数组,只在挂载时执行
// useEffect Hook - userId变化时获取单个用户
useEffect(() => {
if (userId) {
fetchUser(userId);
}
}, [userId]); // 依赖userId
// 返回JSX元素
return (
<div>
<h2>useEffect Data Fetching</h2>
{/* 用户ID输入 */}
<div>
<input
type="number"
placeholder="User ID"
value={userId}
onChange={(e) => setUserId(parseInt(e.target.value) || 1)}
/>
<button onClick={() => fetchUsers()}>Fetch All Users</button>
</div>
{/* 加载状态 */}
{loading && <p>Loading...</p>}
{/* 错误状态 */}
{error && <p style={{ color: 'red' }}>Error: {error}</p>}
{/* 用户列表 */}
{users.length > 0 && (
<div>
<h3>Users:</h3>
{users.map(user => (
<div key={user.id} style={{ border: '1px solid #ccc', padding: '10px', margin: '5px' }}>
<h4>{user.name}</h4>
<p>Email: {user.email}</p>
<p>Phone: {user.phone}</p>
<p>Website: {user.website}</p>
</div>
))}
</div>
)}
</div>
);
}
// 导出组件
export default UseEffectDataFetching;
2.3 清理函数
清理函数示例(详细注释版)
javascript
// src/components/UseEffectCleanup.js
// 导入React、useState和useEffect Hook
import React, { useState, useEffect } from 'react';
// 定义UseEffectCleanup组件
function UseEffectCleanup() {
// 使用useState Hook管理状态
const [count, setCount] = useState(0);
const [isVisible, setIsVisible] = useState(true);
// useEffect Hook - 设置定时器
useEffect(() => {
// 创建定时器
const timer = setInterval(() => {
setCount(prevCount => prevCount + 1);
}, 1000);
// 清理函数,在组件卸载或依赖变化时执行
return () => {
console.log('Cleaning up timer');
clearInterval(timer);
};
}, []); // 空依赖数组,只在挂载时执行
// useEffect Hook - 监听窗口大小变化
useEffect(() => {
// 定义窗口大小变化处理函数
const handleResize = () => {
console.log('Window resized');
};
// 添加事件监听器
window.addEventListener('resize', handleResize);
// 清理函数,移除事件监听器
return () => {
console.log('Removing resize listener');
window.removeEventListener('resize', handleResize);
};
}, []); // 空依赖数组,只在挂载时执行
// useEffect Hook - 条件执行
useEffect(() => {
if (isVisible) {
console.log('Component is visible');
// 返回清理函数
return () => {
console.log('Component is no longer visible');
};
}
}, [isVisible]); // 依赖isVisible
// 返回JSX元素
return (
<div>
<h2>useEffect Cleanup Example</h2>
<p>Count: {count}</p>
<p>Component is {isVisible ? 'visible' : 'hidden'}</p>
<button onClick={() => setIsVisible(!isVisible)}>
Toggle Visibility
</button>
</div>
);
}
// 导出组件
export default UseEffectCleanup;
2.4 依赖数组详解
依赖数组示例(详细注释版)
javascript
// src/components/UseEffectDependencies.js
// 导入React、useState和useEffect Hook
import React, { useState, useEffect } from 'react';
// 定义UseEffectDependencies组件
function UseEffectDependencies() {
// 使用useState Hook管理状态
const [count, setCount] = useState(0);
const [name, setName] = useState('');
const [users, setUsers] = useState([]);
// useEffect Hook - 无依赖数组,每次渲染都执行
useEffect(() => {
console.log('Effect without dependencies - runs on every render');
});
// useEffect Hook - 空依赖数组,只在挂载时执行
useEffect(() => {
console.log('Effect with empty dependencies - runs only on mount');
}, []);
// useEffect Hook - 依赖count,count变化时执行
useEffect(() => {
console.log('Effect with count dependency - runs when count changes');
}, [count]);
// useEffect Hook - 依赖name,name变化时执行
useEffect(() => {
console.log('Effect with name dependency - runs when name changes');
}, [name]);
// useEffect Hook - 依赖多个值,任一值变化时执行
useEffect(() => {
console.log('Effect with multiple dependencies - runs when count or name changes');
}, [count, name]);
// useEffect Hook - 复杂依赖
useEffect(() => {
// 模拟API调用
const fetchData = async () => {
try {
const response = await fetch(`https://jsonplaceholder.typicode.com/users?_limit=${count}`);
const data = await response.json();
setUsers(data);
} catch (error) {
console.error('Error fetching data:', error);
}
};
if (count > 0) {
fetchData();
}
}, [count]); // 依赖count
// 返回JSX元素
return (
<div>
<h2>useEffect Dependencies Example</h2>
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment Count</button>
</div>
<div>
<input
type="text"
placeholder="Enter name"
value={name}
onChange={(e) => setName(e.target.value)}
/>
</div>
<div>
<h3>Users ({users.length}):</h3>
{users.map(user => (
<div key={user.id}>
<p>{user.name} - {user.email}</p>
</div>
))}
</div>
</div>
);
}
// 导出组件
export default UseEffectDependencies;
第三部分:useContext和useReducer (1.5小时)
3.1 useContext Hook
创建Context(详细注释版)
javascript
// src/context/ThemeContext.js
// 导入React和createContext
import React, { createContext, useState } from 'react';
// 创建ThemeContext
// createContext创建一个上下文对象,用于在组件树中共享数据
export const ThemeContext = createContext();
// 定义ThemeProvider组件
// 这个组件提供主题数据给所有子组件
export function ThemeProvider({ children }) {
// 使用useState Hook管理主题状态
const [theme, setTheme] = useState('light');
// 切换主题的函数
const toggleTheme = () => {
setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light');
};
// 定义上下文值
// 这个对象包含所有需要共享的数据和函数
const contextValue = {
theme,
toggleTheme,
isDark: theme === 'dark'
};
// 返回ThemeContext.Provider
// 所有子组件都可以通过useContext访问这些数据
return (
<ThemeContext.Provider value={contextValue}>
{children}
</ThemeContext.Provider>
);
}
使用Context(详细注释版)
javascript
// src/components/ThemeConsumer.js
// 导入React和useContext Hook
import React, { useContext } from 'react';
// 导入ThemeContext
import { ThemeContext } from '../context/ThemeContext';
// 定义ThemeConsumer组件
function ThemeConsumer() {
// 使用useContext Hook获取主题数据
// useContext接收一个上下文对象,返回该上下文的当前值
const { theme, toggleTheme, isDark } = useContext(ThemeContext);
// 定义样式对象
const styles = {
backgroundColor: isDark ? '#333' : '#fff',
color: isDark ? '#fff' : '#333',
padding: '20px',
border: '1px solid #ccc',
borderRadius: '8px'
};
// 返回JSX元素
return (
<div style={styles}>
<h2>Theme Consumer</h2>
<p>Current theme: {theme}</p>
<p>Is dark theme: {isDark ? 'Yes' : 'No'}</p>
<button onClick={toggleTheme}>
Switch to {isDark ? 'Light' : 'Dark'} Theme
</button>
</div>
);
}
// 导出组件
export default ThemeConsumer;
3.2 useReducer Hook
useReducer基础(详细注释版)
javascript
// src/components/UseReducerBasic.js
// 导入React和useReducer Hook
import React, { useReducer } from 'react';
// 定义初始状态
const initialState = {
count: 0,
name: '',
items: []
};
// 定义reducer函数
// reducer函数接收当前状态和动作,返回新状态
function counterReducer(state, action) {
// 使用switch语句处理不同的动作类型
switch (action.type) {
case 'INCREMENT':
return {
...state,
count: state.count + 1
};
case 'DECREMENT':
return {
...state,
count: state.count - 1
};
case 'RESET':
return {
...state,
count: 0
};
case 'SET_NAME':
return {
...state,
name: action.payload
};
case 'ADD_ITEM':
return {
...state,
items: [...state.items, action.payload]
};
case 'REMOVE_ITEM':
return {
...state,
items: state.items.filter(item => item.id !== action.payload)
};
default:
// 如果动作类型不匹配,返回当前状态
return state;
}
}
// 定义UseReducerBasic组件
function UseReducerBasic() {
// 使用useReducer Hook
// useReducer接收reducer函数和初始状态,返回当前状态和dispatch函数
const [state, dispatch] = useReducer(counterReducer, initialState);
// 定义动作函数
const increment = () => {
dispatch({ type: 'INCREMENT' });
};
const decrement = () => {
dispatch({ type: 'DECREMENT' });
};
const reset = () => {
dispatch({ type: 'RESET' });
};
const setName = (name) => {
dispatch({ type: 'SET_NAME', payload: name });
};
const addItem = (item) => {
dispatch({ type: 'ADD_ITEM', payload: item });
};
const removeItem = (id) => {
dispatch({ type: 'REMOVE_ITEM', payload: id });
};
// 返回JSX元素
return (
<div>
<h2>useReducer Basic Example</h2>
<div>
<p>Count: {state.count}</p>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
<button onClick={reset}>Reset</button>
</div>
<div>
<input
type="text"
placeholder="Enter name"
value={state.name}
onChange={(e) => setName(e.target.value)}
/>
<p>Name: {state.name}</p>
</div>
<div>
<p>Items: {state.items.length}</p>
{state.items.map(item => (
<div key={item.id}>
<span>{item.text}</span>
<button onClick={() => removeItem(item.id)}>Remove</button>
</div>
))}
</div>
</div>
);
}
// 导出组件
export default UseReducerBasic;
第四部分:自定义Hooks (1.5小时)
4.1 自定义Hook基础
自定义useCounter Hook(详细注释版)
javascript
// src/hooks/useCounter.js
// 导入React和useState Hook
import { useState } from 'react';
// 定义自定义useCounter Hook
// 自定义Hook是一个以"use"开头的函数,可以调用其他Hook
function useCounter(initialValue = 0) {
// 使用useState Hook管理计数状态
const [count, setCount] = useState(initialValue);
// 定义增加计数的函数
const increment = () => {
setCount(prevCount => prevCount + 1);
};
// 定义减少计数的函数
const decrement = () => {
setCount(prevCount => prevCount - 1);
};
// 定义重置计数的函数
const reset = () => {
setCount(initialValue);
};
// 定义设置计数的函数
const setCountValue = (value) => {
setCount(value);
};
// 返回状态和函数
// 自定义Hook可以返回任何值
return {
count,
increment,
decrement,
reset,
setCount: setCountValue
};
}
// 导出自定义Hook
export default useCounter;
使用自定义Hook(详细注释版)
javascript
// src/components/CustomHookExample.js
// 导入React
import React from 'react';
// 导入自定义Hook
import useCounter from '../hooks/useCounter';
// 定义CustomHookExample组件
function CustomHookExample() {
// 使用自定义useCounter Hook
// 自定义Hook的使用方式与内置Hook相同
const counter1 = useCounter(0);
const counter2 = useCounter(10);
// 返回JSX元素
return (
<div>
<h2>Custom Hook Example</h2>
<div>
<h3>Counter 1</h3>
<p>Count: {counter1.count}</p>
<button onClick={counter1.increment}>+</button>
<button onClick={counter1.decrement}>-</button>
<button onClick={counter1.reset}>Reset</button>
</div>
<div>
<h3>Counter 2</h3>
<p>Count: {counter2.count}</p>
<button onClick={counter2.increment}>+</button>
<button onClick={counter2.decrement}>-</button>
<button onClick={counter2.reset}>Reset</button>
</div>
</div>
);
}
// 导出组件
export default CustomHookExample;
4.2 自定义useFetch Hook
useFetch Hook(详细注释版)
javascript
// src/hooks/useFetch.js
// 导入React、useState和useEffect Hook
import { useState, useEffect } from 'react';
// 定义自定义useFetch Hook
function useFetch(url, options = {}) {
// 使用useState Hook管理状态
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
// 使用useEffect Hook处理数据获取
useEffect(() => {
// 定义获取数据的异步函数
const fetchData = async () => {
try {
// 设置加载状态
setLoading(true);
setError(null);
// 发起HTTP请求
const response = await fetch(url, options);
// 检查响应状态
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
// 解析响应数据
const result = await response.json();
setData(result);
} catch (err) {
// 处理错误
setError(err.message);
} finally {
// 设置加载完成
setLoading(false);
}
};
// 调用获取数据函数
fetchData();
}, [url, JSON.stringify(options)]); // 依赖url和options
// 返回状态和函数
return { data, loading, error };
}
// 导出自定义Hook
export default useFetch;
使用useFetch Hook(详细注释版)
javascript
// src/components/UseFetchExample.js
// 导入React
import React, { useState } from 'react';
// 导入自定义Hook
import useFetch from '../hooks/useFetch';
// 定义UseFetchExample组件
function UseFetchExample() {
// 使用useState Hook管理URL状态
const [url, setUrl] = useState('https://jsonplaceholder.typicode.com/users');
const [userId, setUserId] = useState(1);
// 使用自定义useFetch Hook
const { data, loading, error } = useFetch(url);
// 获取单个用户
const fetchUser = (id) => {
setUrl(`https://jsonplaceholder.typicode.com/users/${id}`);
};
// 获取所有用户
const fetchAllUsers = () => {
setUrl('https://jsonplaceholder.typicode.com/users');
};
// 返回JSX元素
return (
<div>
<h2>useFetch Hook Example</h2>
{/* 控制按钮 */}
<div>
<input
type="number"
placeholder="User ID"
value={userId}
onChange={(e) => setUserId(parseInt(e.target.value) || 1)}
/>
<button onClick={() => fetchUser(userId)}>Fetch User</button>
<button onClick={fetchAllUsers}>Fetch All Users</button>
</div>
{/* 加载状态 */}
{loading && <p>Loading...</p>}
{/* 错误状态 */}
{error && <p style={{ color: 'red' }}>Error: {error}</p>}
{/* 数据显示 */}
{data && (
<div>
<h3>Data:</h3>
{Array.isArray(data) ? (
<div>
<p>Found {data.length} users</p>
{data.map(user => (
<div key={user.id} style={{ border: '1px solid #ccc', padding: '10px', margin: '5px' }}>
<h4>{user.name}</h4>
<p>Email: {user.email}</p>
<p>Phone: {user.phone}</p>
</div>
))}
</div>
) : (
<div style={{ border: '1px solid #ccc', padding: '10px' }}>
<h4>{data.name}</h4>
<p>Email: {data.email}</p>
<p>Phone: {data.phone}</p>
<p>Website: {data.website}</p>
</div>
)}
</div>
)}
</div>
);
}
// 导出组件
export default UseFetchExample;
4.3 自定义useLocalStorage Hook
useLocalStorage Hook(详细注释版)
javascript
// src/hooks/useLocalStorage.js
// 导入React和useState Hook
import { useState, useEffect } from 'react';
// 定义自定义useLocalStorage Hook
function useLocalStorage(key, initialValue) {
// 使用useState Hook管理状态
const [storedValue, setStoredValue] = useState(() => {
try {
// 尝试从localStorage获取值
const item = window.localStorage.getItem(key);
// 如果存在,解析并返回
return item ? JSON.parse(item) : initialValue;
} catch (error) {
// 如果出错,返回初始值
console.error(`Error reading localStorage key "${key}":`, error);
return initialValue;
}
});
// 定义设置值的函数
const setValue = (value) => {
try {
// 允许值是一个函数,这样我们可以使用函数式更新
const valueToStore = value instanceof Function ? value(storedValue) : value;
// 保存状态
setStoredValue(valueToStore);
// 保存到localStorage
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
// 如果出错,记录错误
console.error(`Error setting localStorage key "${key}":`, error);
}
};
// 定义清除值的函数
const removeValue = () => {
try {
// 从localStorage移除
window.localStorage.removeItem(key);
// 重置为初始值
setStoredValue(initialValue);
} catch (error) {
console.error(`Error removing localStorage key "${key}":`, error);
}
};
// 返回状态和函数
return [storedValue, setValue, removeValue];
}
// 导出自定义Hook
export default useLocalStorage;
使用useLocalStorage Hook(详细注释版)
javascript
// src/components/UseLocalStorageExample.js
// 导入React
import React from 'react';
// 导入自定义Hook
import useLocalStorage from '../hooks/useLocalStorage';
// 定义UseLocalStorageExample组件
function UseLocalStorageExample() {
// 使用自定义useLocalStorage Hook
const [name, setName, removeName] = useLocalStorage('name', '');
const [age, setAge, removeAge] = useLocalStorage('age', 0);
const [preferences, setPreferences, removePreferences] = useLocalStorage('preferences', {
theme: 'light',
language: 'en'
});
// 返回JSX元素
return (
<div>
<h2>useLocalStorage Hook Example</h2>
{/* 姓名输入 */}
<div>
<label>
Name:
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
</label>
<button onClick={removeName}>Clear Name</button>
</div>
{/* 年龄输入 */}
<div>
<label>
Age:
<input
type="number"
value={age}
onChange={(e) => setAge(parseInt(e.target.value) || 0)}
/>
</label>
<button onClick={removeAge}>Clear Age</button>
</div>
{/* 偏好设置 */}
<div>
<h3>Preferences:</h3>
<label>
Theme:
<select
value={preferences.theme}
onChange={(e) => setPreferences(prev => ({ ...prev, theme: e.target.value }))}
>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
</label>
<label>
Language:
<select
value={preferences.language}
onChange={(e) => setPreferences(prev => ({ ...prev, language: e.target.value }))}
>
<option value="en">English</option>
<option value="es">Spanish</option>
<option value="fr">French</option>
</select>
</label>
<button onClick={removePreferences}>Clear Preferences</button>
</div>
{/* 显示当前值 */}
<div>
<h3>Current Values:</h3>
<p>Name: {name}</p>
<p>Age: {age}</p>
<p>Theme: {preferences.theme}</p>
<p>Language: {preferences.language}</p>
</div>
</div>
);
}
// 导出组件
export default UseLocalStorageExample;
第五部分:实践项目(详细注释版)
项目:个人任务管理器
主应用组件(详细注释版)
javascript
// src/App.js
// 导入React和useState Hook
import React, { useState } from 'react';
// 导入自定义Hook
import useLocalStorage from './hooks/useLocalStorage';
// 导入组件
import TaskList from './components/TaskList';
import TaskForm from './components/TaskForm';
import TaskStats from './components/TaskStats';
// 导入样式
import './App.css';
// 定义主应用组件
function App() {
// 使用自定义useLocalStorage Hook管理任务列表
const [tasks, setTasks, clearTasks] = useLocalStorage('tasks', []);
// 使用useState Hook管理表单状态
const [showForm, setShowForm] = useState(false);
const [editingTask, setEditingTask] = useState(null);
// 添加新任务的函数
const addTask = (taskData) => {
const newTask = {
id: Date.now(),
...taskData,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
};
setTasks(prevTasks => [...prevTasks, newTask]);
setShowForm(false);
};
// 更新任务的函数
const updateTask = (id, updatedData) => {
setTasks(prevTasks =>
prevTasks.map(task =>
task.id === id
? { ...task, ...updatedData, updatedAt: new Date().toISOString() }
: task
)
);
setEditingTask(null);
setShowForm(false);
};
// 删除任务的函数
const deleteTask = (id) => {
setTasks(prevTasks => prevTasks.filter(task => task.id !== id));
};
// 切换任务完成状态的函数
const toggleTask = (id) => {
setTasks(prevTasks =>
prevTasks.map(task =>
task.id === id
? { ...task, completed: !task.completed, updatedAt: new Date().toISOString() }
: task
)
);
};
// 编辑任务的函数
const editTask = (task) => {
setEditingTask(task);
setShowForm(true);
};
// 取消编辑的函数
const cancelEdit = () => {
setEditingTask(null);
setShowForm(false);
};
// 返回JSX元素
return (
<div className="App">
<header className="App-header">
<h1>Personal Task Manager</h1>
<button
className="add-task-btn"
onClick={() => setShowForm(true)}
>
Add New Task
</button>
</header>
<main>
{/* 任务统计 */}
<TaskStats tasks={tasks} />
{/* 任务表单 */}
{showForm && (
<TaskForm
task={editingTask}
onSubmit={editingTask ?
(data) => updateTask(editingTask.id, data) :
addTask
}
onCancel={cancelEdit}
/>
)}
{/* 任务列表 */}
<TaskList
tasks={tasks}
onEdit={editTask}
onDelete={deleteTask}
onToggle={toggleTask}
/>
{/* 清除所有任务按钮 */}
{tasks.length > 0 && (
<button
className="clear-all-btn"
onClick={clearTasks}
>
Clear All Tasks
</button>
)}
</main>
</div>
);
}
// 导出App组件
export default App;
任务列表组件(详细注释版)
javascript
// src/components/TaskList.js
// 导入React和useState Hook
import React, { useState } from 'react';
// 导入TaskItem组件
import TaskItem from './TaskItem';
// 定义TaskList组件
function TaskList({ tasks, onEdit, onDelete, onToggle }) {
// 使用useState Hook管理过滤状态
const [filter, setFilter] = useState('all');
const [sortBy, setSortBy] = useState('createdAt');
// 过滤任务
const filteredTasks = tasks.filter(task => {
switch (filter) {
case 'completed':
return task.completed;
case 'pending':
return !task.completed;
case 'high':
return task.priority === 'high';
case 'medium':
return task.priority === 'medium';
case 'low':
return task.priority === 'low';
default:
return true;
}
});
// 排序任务
const sortedTasks = [...filteredTasks].sort((a, b) => {
switch (sortBy) {
case 'title':
return a.title.localeCompare(b.title);
case 'priority':
const priorityOrder = { high: 3, medium: 2, low: 1 };
return priorityOrder[b.priority] - priorityOrder[a.priority];
case 'createdAt':
return new Date(b.createdAt) - new Date(a.createdAt);
case 'updatedAt':
return new Date(b.updatedAt) - new Date(a.updatedAt);
default:
return 0;
}
});
// 返回JSX元素
return (
<div className="task-list">
<h2>Tasks ({tasks.length})</h2>
{/* 过滤和排序控制 */}
<div className="task-controls">
<div className="filter-controls">
<label>
Filter:
<select value={filter} onChange={(e) => setFilter(e.target.value)}>
<option value="all">All Tasks</option>
<option value="pending">Pending</option>
<option value="completed">Completed</option>
<option value="high">High Priority</option>
<option value="medium">Medium Priority</option>
<option value="low">Low Priority</option>
</select>
</label>
</div>
<div className="sort-controls">
<label>
Sort by:
<select value={sortBy} onChange={(e) => setSortBy(e.target.value)}>
<option value="createdAt">Created Date</option>
<option value="updatedAt">Updated Date</option>
<option value="title">Title</option>
<option value="priority">Priority</option>
</select>
</label>
</div>
</div>
{/* 任务列表 */}
<div className="tasks">
{sortedTasks.length === 0 ? (
<p>No tasks found</p>
) : (
sortedTasks.map(task => (
<TaskItem
key={task.id}
task={task}
onEdit={onEdit}
onDelete={onDelete}
onToggle={onToggle}
/>
))
)}
</div>
</div>
);
}
// 导出TaskList组件
export default TaskList;
任务项组件(详细注释版)
javascript
// src/components/TaskItem.js
// 导入React
import React from 'react';
// 定义TaskItem组件
function TaskItem({ task, onEdit, onDelete, onToggle }) {
// 获取任务属性
const { id, title, description, priority, completed, createdAt, updatedAt } = task;
// 格式化日期
const formatDate = (dateString) => {
return new Date(dateString).toLocaleDateString();
};
// 获取优先级样式
const getPriorityClass = (priority) => {
switch (priority) {
case 'high':
return 'priority-high';
case 'medium':
return 'priority-medium';
case 'low':
return 'priority-low';
default:
return '';
}
};
// 返回JSX元素
return (
<div className={`task-item ${completed ? 'completed' : ''} ${getPriorityClass(priority)}`}>
{/* 任务头部 */}
<div className="task-header">
<div className="task-title">
<input
type="checkbox"
checked={completed}
onChange={() => onToggle(id)}
/>
<h3>{title}</h3>
</div>
<div className="task-actions">
<button
className="edit-btn"
onClick={() => onEdit(task)}
>
Edit
</button>
<button
className="delete-btn"
onClick={() => onDelete(id)}
>
Delete
</button>
</div>
</div>
{/* 任务描述 */}
{description && (
<div className="task-description">
<p>{description}</p>
</div>
)}
{/* 任务元数据 */}
<div className="task-meta">
<span className={`priority priority-${priority}`}>
{priority.toUpperCase()}
</span>
<span className="created-date">
Created: {formatDate(createdAt)}
</span>
{updatedAt !== createdAt && (
<span className="updated-date">
Updated: {formatDate(updatedAt)}
</span>
)}
</div>
</div>
);
}
// 导出TaskItem组件
export default TaskItem;
任务表单组件(详细注释版)
javascript
// src/components/TaskForm.js
// 导入React和useState Hook
import React, { useState, useEffect } from 'react';
// 定义TaskForm组件
function TaskForm({ task, onSubmit, onCancel }) {
// 使用useState Hook管理表单状态
const [formData, setFormData] = useState({
title: '',
description: '',
priority: 'medium',
dueDate: ''
});
// 当编辑任务时,更新表单数据
useEffect(() => {
if (task) {
setFormData({
title: task.title || '',
description: task.description || '',
priority: task.priority || 'medium',
dueDate: task.dueDate || ''
});
}
}, [task]);
// 处理输入变化
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
};
// 处理表单提交
const handleSubmit = (e) => {
e.preventDefault();
// 验证表单
if (!formData.title.trim()) {
alert('Please enter a task title');
return;
}
// 提交表单数据
onSubmit(formData);
};
// 返回JSX元素
return (
<div className="task-form-overlay">
<div className="task-form">
<h2>{task ? 'Edit Task' : 'Add New Task'}</h2>
<form onSubmit={handleSubmit}>
{/* 任务标题 */}
<div className="form-group">
<label htmlFor="title">Title *</label>
<input
type="text"
id="title"
name="title"
value={formData.title}
onChange={handleChange}
required
placeholder="Enter task title"
/>
</div>
{/* 任务描述 */}
<div className="form-group">
<label htmlFor="description">Description</label>
<textarea
id="description"
name="description"
value={formData.description}
onChange={handleChange}
placeholder="Enter task description"
rows="3"
/>
</div>
{/* 优先级 */}
<div className="form-group">
<label htmlFor="priority">Priority</label>
<select
id="priority"
name="priority"
value={formData.priority}
onChange={handleChange}
>
<option value="low">Low</option>
<option value="medium">Medium</option>
<option value="high">High</option>
</select>
</div>
{/* 截止日期 */}
<div className="form-group">
<label htmlFor="dueDate">Due Date</label>
<input
type="date"
id="dueDate"
name="dueDate"
value={formData.dueDate}
onChange={handleChange}
/>
</div>
{/* 表单按钮 */}
<div className="form-actions">
<button type="submit" className="submit-btn">
{task ? 'Update Task' : 'Add Task'}
</button>
<button type="button" onClick={onCancel} className="cancel-btn">
Cancel
</button>
</div>
</form>
</div>
</div>
);
}
// 导出TaskForm组件
export default TaskForm;
任务统计组件(详细注释版)
javascript
// src/components/TaskStats.js
// 导入React
import React from 'react';
// 定义TaskStats组件
function TaskStats({ tasks }) {
// 计算统计数据
const totalTasks = tasks.length;
const completedTasks = tasks.filter(task => task.completed).length;
const pendingTasks = totalTasks - completedTasks;
const highPriorityTasks = tasks.filter(task => task.priority === 'high').length;
const mediumPriorityTasks = tasks.filter(task => task.priority === 'medium').length;
const lowPriorityTasks = tasks.filter(task => task.priority === 'low').length;
// 计算完成率
const completionRate = totalTasks > 0 ? Math.round((completedTasks / totalTasks) * 100) : 0;
// 返回JSX元素
return (
<div className="task-stats">
<h2>Task Statistics</h2>
<div className="stats-grid">
<div className="stat-item">
<h3>Total Tasks</h3>
<p className="stat-number">{totalTasks}</p>
</div>
<div className="stat-item">
<h3>Completed</h3>
<p className="stat-number">{completedTasks}</p>
</div>
<div className="stat-item">
<h3>Pending</h3>
<p className="stat-number">{pendingTasks}</p>
</div>
<div className="stat-item">
<h3>Completion Rate</h3>
<p className="stat-number">{completionRate}%</p>
</div>
</div>
<div className="priority-stats">
<h3>Priority Distribution</h3>
<div className="priority-bars">
<div className="priority-bar">
<span className="priority-label">High</span>
<div className="priority-bar-fill" style={{ width: `${totalTasks > 0 ? (highPriorityTasks / totalTasks) * 100 : 0}%` }}></div>
<span className="priority-count">{highPriorityTasks}</span>
</div>
<div className="priority-bar">
<span className="priority-label">Medium</span>
<div className="priority-bar-fill" style={{ width: `${totalTasks > 0 ? (mediumPriorityTasks / totalTasks) * 100 : 0}%` }}></div>
<span className="priority-count">{mediumPriorityTasks}</span>
</div>
<div className="priority-bar">
<span className="priority-label">Low</span>
<div className="priority-bar-fill" style={{ width: `${totalTasks > 0 ? (lowPriorityTasks / totalTasks) * 100 : 0}%` }}></div>
<span className="priority-count">{lowPriorityTasks}</span>
</div>
</div>
</div>
</div>
);
}
// 导出TaskStats组件
export default TaskStats;
样式文件(详细注释版)
css
/* src/App.css */
/* 应用主容器样式 */
.App {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
font-family: Arial, sans-serif;
}
/* 应用头部样式 */
.App-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 2px solid #e9ecef;
}
.App-header h1 {
color: #333;
margin: 0;
}
.add-task-btn {
background-color: #007bff;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
}
.add-task-btn:hover {
background-color: #0056b3;
}
/* 任务统计样式 */
.task-stats {
background-color: #f8f9fa;
padding: 20px;
border-radius: 8px;
margin-bottom: 30px;
}
.task-stats h2 {
margin-top: 0;
color: #333;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
.stat-item {
text-align: center;
padding: 15px;
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.stat-item h3 {
margin: 0 0 10px 0;
color: #666;
font-size: 14px;
}
.stat-number {
font-size: 24px;
font-weight: bold;
color: #007bff;
margin: 0;
}
.priority-stats h3 {
margin-bottom: 15px;
color: #333;
}
.priority-bars {
display: flex;
flex-direction: column;
gap: 10px;
}
.priority-bar {
display: flex;
align-items: center;
gap: 10px;
}
.priority-label {
width: 60px;
font-weight: bold;
color: #333;
}
.priority-bar-fill {
height: 20px;
background-color: #007bff;
border-radius: 10px;
transition: width 0.3s ease;
}
.priority-count {
width: 30px;
text-align: center;
font-weight: bold;
color: #333;
}
/* 任务列表样式 */
.task-list {
margin-bottom: 30px;
}
.task-list h2 {
color: #333;
margin-bottom: 20px;
}
.task-controls {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
flex-wrap: wrap;
gap: 15px;
}
.filter-controls select,
.sort-controls select {
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
margin-left: 10px;
}
.tasks {
display: flex;
flex-direction: column;
gap: 15px;
}
/* 任务项样式 */
.task-item {
background-color: white;
border: 1px solid #ddd;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
transition: all 0.3s ease;
}
.task-item:hover {
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
.task-item.completed {
opacity: 0.7;
background-color: #f8f9fa;
}
.task-item.completed .task-title h3 {
text-decoration: line-through;
color: #6c757d;
}
.task-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.task-title {
display: flex;
align-items: center;
gap: 10px;
flex: 1;
}
.task-title input[type="checkbox"] {
width: 18px;
height: 18px;
cursor: pointer;
}
.task-title h3 {
margin: 0;
color: #333;
}
.task-actions {
display: flex;
gap: 10px;
}
.edit-btn {
background-color: #ffc107;
color: #212529;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
.edit-btn:hover {
background-color: #e0a800;
}
.delete-btn {
background-color: #dc3545;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
.delete-btn:hover {
background-color: #c82333;
}
.task-description {
margin-bottom: 15px;
}
.task-description p {
margin: 0;
color: #666;
line-height: 1.5;
}
.task-meta {
display: flex;
align-items: center;
gap: 15px;
font-size: 14px;
color: #666;
}
.priority {
padding: 4px 8px;
border-radius: 4px;
font-weight: bold;
font-size: 12px;
}
.priority-high {
background-color: #f8d7da;
color: #721c24;
}
.priority-medium {
background-color: #fff3cd;
color: #856404;
}
.priority-low {
background-color: #d1ecf1;
color: #0c5460;
}
.created-date,
.updated-date {
font-size: 12px;
color: #999;
}
/* 任务表单样式 */
.task-form-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.task-form {
background-color: white;
padding: 30px;
border-radius: 8px;
max-width: 500px;
width: 90%;
max-height: 80vh;
overflow-y: auto;
}
.task-form h2 {
margin-top: 0;
color: #333;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: #333;
}
.form-group input,
.form-group textarea,
.form-group select {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
box-sizing: border-box;
}
.form-group textarea {
resize: vertical;
min-height: 80px;
}
.form-actions {
display: flex;
gap: 10px;
justify-content: center;
margin-top: 30px;
}
.submit-btn {
background-color: #28a745;
color: white;
border: none;
padding: 12px 24px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
.submit-btn:hover {
background-color: #218838;
}
.cancel-btn {
background-color: #6c757d;
color: white;
border: none;
padding: 12px 24px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
.cancel-btn:hover {
background-color: #5a6268;
}
/* 清除所有任务按钮样式 */
.clear-all-btn {
background-color: #dc3545;
color: white;
border: none;
padding: 12px 24px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
margin-top: 20px;
}
.clear-all-btn:hover {
background-color: #c82333;
}
/* 响应式设计 */
@media (max-width: 768px) {
.App {
padding: 10px;
}
.App-header {
flex-direction: column;
gap: 15px;
text-align: center;
}
.task-controls {
flex-direction: column;
align-items: stretch;
}
.task-header {
flex-direction: column;
align-items: stretch;
gap: 10px;
}
.task-actions {
justify-content: center;
}
.task-meta {
flex-direction: column;
align-items: flex-start;
gap: 5px;
}
}
练习题目
基础练习
- useState练习
javascript
// 练习1:创建一个计数器组件,支持增加、减少、重置功能
function Counter() {
// 使用useState Hook管理计数状态
// 实现增加、减少、重置功能
}
// 练习2:创建一个表单组件,管理用户输入
function UserForm() {
// 使用useState Hook管理表单状态
// 实现表单验证和提交
}
- useEffect练习
javascript
// 练习3:创建一个组件,在挂载时获取数据
function DataFetcher() {
// 使用useEffect Hook获取数据
// 处理加载状态和错误状态
}
// 练习4:创建一个组件,监听窗口大小变化
function WindowSize() {
// 使用useEffect Hook监听窗口大小
// 显示当前窗口尺寸
}
进阶练习
- 自定义Hook练习
javascript
// 练习5:创建一个自定义useToggle Hook
function useToggle(initialValue = false) {
// 实现切换功能
// 返回当前状态和切换函数
}
// 练习6:创建一个自定义useDebounce Hook
function useDebounce(value, delay) {
// 实现防抖功能
// 返回防抖后的值
}
- 综合应用练习
javascript
// 练习7:创建一个完整的博客系统
// 包含:文章列表、文章详情、文章编辑、文章删除
// 使用:useState、useEffect、自定义Hook
学习检查点
完成标准
- 理解useState Hook的使用
- 掌握useEffect Hook的使用
- 学会useContext和useReducer
- 能够创建自定义Hook
- 完成所有练习题目
自我测试
- useState Hook的作用是什么?
- useEffect Hook的依赖数组有什么作用?
- 如何创建自定义Hook?
- useContext Hook的使用场景是什么?
扩展阅读
推荐资源
明日预告
明天我们将学习:
- 状态管理深入
- Redux基础
- Context API
- 组件通信
- 为状态管理学习做准备