一、useState基础概念
1、什么是状态(State)?
状态是组件内部的数据,当状态发生变化时,组件会重新渲染以反映这些变化。
2、useState的基本语法
js
import { useState } from 'react';
function MyComponent() {
const [state, setState] = useState(initialState);
// state: 当前的状态值
// setState: 更新状态的函数
// initialState: 状态的初始值
}
二、useState的基本用法
1、基本类型状态
js
function Counter() {
const [count, setCount] = useState(0); // 数字
const [name, setName] = useState(''); // 字符串
const [isActive, setIsActive] = useState(false); // 布尔值
return (
<div>
<p>计数: {count}</p>
<button onClick={() => setCount(count + 1)}>
增加
</button>
</div>
);
}
2、对象类型状态
js
function UserForm() {
const [user, setUser] = useState({
name: '',
email: '',
age: 0
});
const handleChange = (e) => {
// 更新对象状态时需要展开旧状态
setUser({
...user,
[e.target.name]: e.target.value
});
};
return (
<form>
<input
name="name"
value={user.name}
onChange={handleChange}
placeholder="姓名"
/>
<input
name="email"
value={user.email}
onChange={handleChange}
placeholder="邮箱"
/>
</form>
);
}
3、数组类型状态
js
function TodoList() {
const [todos, setTodos] = useState([]);
const [inputValue, setInputValue] = useState('');
const addTodo = () => {
if (inputValue.trim()) {
setTodos([...todos, {
id: Date.now(),
text: inputValue,
completed: false
}]);
setInputValue('');
}
};
const toggleTodo = (id) => {
setTodos(todos.map(todo =>
todo.id === id
? { ...todo, completed: !todo.completed }
: todo
));
};
return (
<div>
<input
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="添加待办事项"
/>
<button onClick={addTodo}>添加</button>
<ul>
{todos.map(todo => (
<li
key={todo.id}
onClick={() => toggleTodo(todo.id)}
style={{
textDecoration: todo.completed ? 'line-through' : 'none'
}}
>
{todo.text}
</li>
))}
</ul>
</div>
);
}
三、useState的高级用法
1、函数式更新
js
function Counter() {
const [count, setCount] = useState(0);
// 批量更新时使用函数式更新确保正确性
const incrementMultiple = () => {
setCount(prevCount => prevCount + 1);
setCount(prevCount => prevCount + 1);
setCount(prevCount => prevCount + 1);
};
return (
<div>
<p>计数: {count}</p>
<button onClick={incrementMultiple}>
增加3 (使用函数式更新)
</button>
<button onClick={() => setCount(count + 1)}>
增加1 (直接更新)
</button>
</div>
);
}
2、惰性初始状态
js
function ExensiveInitialState() {
// 使用函数进行惰性初始化,避免每次渲染都执行昂贵计算
const [data, setData] = useState(() => {
// 这个函数只在初始渲染时执行一次
const expensiveValue = calculateExpensiveValue();
return expensiveValue;
});
return <div>数据: {data}</div>
}
function calculateExpensiveValue() {
// 模拟昂贵计算
let result = 0;
for (let i = 0; i < 1000000000; i++) {
result += i;
}
return result;
}
3、使用前一个状态
js
function ToggleExample() {
const [toggles, setToggles] = useState({
featureA: false,
featureB: false,
featureC: true
});
const toggleFeature = (featureName) => {
setToggles(prevToggles => ({
...prevToggles,
[featureName]: !prevToggles[featureName]
}));
};
return (
<div>
{Object.entries(toggles).map(([feature, isEnabled]) => (
<div key={feature}>
<label>
<input
type="checkbox"
checked={isEnabled}
onChange={() => toggleFeature(feature)}
/>
{feature}
</label>
</div>
))}
</div>
);
}
四、useState 的工作原理
1、状态存储机制
React 使用单向链表来存储Hooks的状态。每个组件实例都有对应的Hook状态链表:
js
// 简化的React内部实现
let hookStates = [];
let hookIndex = 0;
function useState(initialValue) {
// 价差是否已有该Hook的状态
if(hookStates[hookIndex] === undefined) {
// 初始渲染,设置初始值
hookStates[hookIndex] = typeof initialValue === 'function'
? initialValue()
: initialValue;
}
// 保存当前索引
const currentIndex = hookIndex;
// 设置状态函数
const setState = (newValue) => {
// 如果新值是函数,调用它获取新状态
if(typeof newValue === 'function') {
hookStates[currentIndex] = newValue(hookStates[currentIndex]);
}else {
hookStates[currentIndex] = newValue;
}
// 触发重新渲染
scheduleRerender();
};
// 返回当前状态和设置函数,然后移动到下一个Hook
return [hookStates[hookIndex ++], setState];
}
// 组件渲染前重置索引
function renderComponent() {
hookIndex = 0;
// ...渲染逻辑
}
2、批量更新机制
React 会对状态更新进行批量处理以提高性能:
js
function BatchUpdateExamle() {
const [count, setCount] = useState(0);
const [text, setText] = useState('');
const handleClick = () => {
// 这些更新会被批量处理
setCount(count + 1);
setText('更新了');
setCount(count + 2);
// 注意:此时的count还是旧值
console.log('当前 count:', count); // 输出旧值
};
// 组件只会在事件处理函数执行完成后重新渲染一次
return <button onClick={handleClick}>点击</button>;
}
五、常见使用模式
1、表单处理
js
function ContactForm() {
const [formData, setFormData] = useState({
name: '',
email: '',
message: '',
subscribe: false
});
const handleChange = (e) => {
const { name, value, type, checked } = e.target;
setFormData(prev => ({
...prev,
[name]: type === 'checkbox' ? checked : value
}));
};
const handleSubmit = (e) => {
e.preventDefault();
console.log('提交的数据:', formData);
};
return (
<form onSubmit={handleSubmit}>
<input
name="name"
value={formData.name}
onChange={handleChange}
placeholder="姓名"
/>
<input
name="email"
type="email"
value={formData.email}
onChange={handleChange}
placeholder="邮箱"
/>
<textarea
name="message"
value={formData.message}
onChange={handleChange}
placeholder="消息"
/>
<label>
<input
name="subscribe"
type="checkbox"
checked={formData.subscribe}
onChange={handleChange}
/>
订阅 newsletter
</label>
<button type="submit">提交</button>
</form>
);
}
2、自定义 Hook 封装状态逻辑
js
// 自定义 Hook:使用 localStorage 持久化状态
function useLocalStorage(key, initialValue) {
// 惰性初始状态:从 localStorage 读取初始值
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error('读取 localStorage 失败:', error);
return initialValue;
}
});
// 更新状态并保存到 localStorage
const setValue = (value) => {
try {
// 允许值是一个函数
const valueToStore = value instanceof Function
? value(storedValue)
: value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.error('保存到 localStorage 失败:', error);
}
};
return [storedValue, setValue];
}
// 使用自定义 Hook
function Settings() {
const [theme, setTheme] = useLocalStorage('theme', 'light');
const [language, setLanguage] = useLocalStorage('language', 'zh-CN');
return (
<div>
<select value={theme} onChange={(e) => setTheme(e.target.value)}>
<option value="light">浅色</option>
<option value="dark">深色</option>
</select>
<select value={language} onChange={(e) => setLanguage(e.target.value)}>
<option value="zh-CN">中文</option>
<option value="en-US">英文</option>
</select>
</div>
);
}
六、性能优化技巧
1、避免不必要的重新渲染
js
function ExpensiveComponent({ data }) {
const [filters, setFilters] = useState({});
// 使用 useMemo 避免昂贵的重复计算
const filteredData = useMemo(() => {
return data.filter(item => {
// 昂贵的过滤逻辑
return matchesFilters(item, filters);
});
}, [data, filters]); // 只有当 data 或 filters 变化时重新计算
return (
<div>
{/* 渲染过滤后的数据 */}
{filteredData.map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
}
2、状态提升和状态下沉
js
// 状态提升:将状态移动到共同父组件
function ParentComponent() {
const [sharedState, setSharedState] = useState('');
return (
<div>
<ChildA value={sharedState} onChange={setSharedState} />
<ChildB value={sharedState} />
</div>
);
}
// 状态下沉:将状态移动到更接近使用的地方
function ComplexComponent() {
// 只在需要的地方使用状态
return (
<div>
<Header />
<MainContent />
<Footer />
</div>
);
}
function MainContent() {
// 状态下沉到这里
const [content, setContent] = useState('');
return <div>{content}</div>;
}
3、使用useReducer处理复杂状态
js
function complexStateReducer(state, action) {
switch (action.type) {
case 'ADD_ITEM':
return {
...state,
items: [...state.items, action.payload],
count: state.count + 1
};
case 'REMOVE_ITEM':
return {
...state,
items: state.items.filter(item => item.id !== action.payload),
count: state.count - 1
};
case 'UPDATE_FILTER':
return {
...state,
filter: action.payload
};
default:
return state;
}
}
function ComplexComponent() {
const [state, dispatch] = useReducer(complexStateReducer, {
items: [],
count: 0,
filter: 'all'
});
const addItem = (item) => {
dispatch({ type: 'ADD_ITEM', payload: item });
};
return (
<div>
<p>总数: {state.count}</p>
{/* 渲染逻辑 */}
</div>
);
}
七、常见问题与解决方案
1、状态更新不同步
js
function AsyncIssue() {
const [count, setCount] = useState(0);
const increment = () => {
// ❌ 问题:连续调用 setCount 不会立即更新
setCount(count + 1);
setCount(count + 1); // 仍然基于旧的 count 值
// ✅ 解决方案:使用函数式更新
setCount(prev => prev + 1);
setCount(prev => prev + 1); // 基于最新的状态值
};
return (
<div>
<p>计数: {count}</p>
<button onClick={increment}>增加两次</button>
</div>
);
}
2、对象和数组状态更新
js
function ObjectUpdateIssue() {
const [user, setUser] = useState({ name: 'John', age: 30 });
const updateAge = () => {
// ❌ 错误:直接修改原对象
user.age = 31; // 不会触发重新渲染
// ✅ 正确:创建新对象
setUser({ ...user, age: 31 });
// ✅ 更好:使用函数式更新
setUser(prevUser => ({ ...prevUser, age: 31 }));
};
return (
<div>
<p>姓名: {user.name}, 年龄: {user.age}</p>
<button onClick={updateAge}>增加年龄</button>
</div>
);
}
3、初始化函数执行多次
js
function InitializationIssue() {
// ❌ 问题:昂贵的初始化在每次渲染都会执行
const [data, setData] = useState(calculateExpensiveValue());
// ✅ 解决:使用惰性初始化,函数只执行一次
const [data, setData] = useState(() => calculateExpensiveValue());
return <div>数据: {data}</div>;
}
八、最佳实践总结
1、保持状态最小化: 只存储必要的状态,其他数据可以在渲染时计算
2、使用正确的更新方式: 对象和数组要创建新的引用
3、使用函数式更新: 当新状态依赖于旧状态时
4、惰性初始化: 对于昂贵的初始化计算使用函数形式
5、合理拆分状态: 将相关的状态组织在一起,不相关的状态分开
6、使用自定义Hook: 封装可复用的状态逻辑
7、性能优化: 使用useMemo和useCallback避免不必要的计算和渲染
总结:
useState 是React函数组件的核心Hook,它使得函数组件能够拥有和管理自己的状态。
记住,useState 的核心思想是让组建的状态变得可预测和可管理。正确的状态管理是构建复杂React应用的基础,它直接影响到组件的性能、可维护性和用户体验。