← 返回目录 | 下一课:事件处理 →

第4课: State状态管理

组件内部状态详解

🎯 学习目标

1. State是什么?

State定义:State(状态)是组件内部的数据,用于存储组件的动态信息。当State发生变化时,React会自动重新渲染组件以反映最新的状态。

State的特点

State更新流程

初始State
用户交互
setState
重新渲染
新State

2. useState Hook基础

2.1 基本语法

// useState的基本语法
const [state, setState] = useState(initialValue);

// 解析:
// state: 当前的状态值
// setState: 更新状态的函数
// initialValue: 状态的初始值

2.2 简单计数器示例

import { useState } from 'react';

function Counter() {
    const [count, setCount] = useState(0);
    
    return (
        <div>
            <h2>计数器: {count}</h2>
            <button onClick={() => setCount(count + 1)}>
                增加
            </button>
            <button onClick={() => setCount(count - 1)}>
                减少
            </button>
            <button onClick={() => setCount(0)}>
                重置
            </button>
        </div>
    );
}

2.3 不同类型的State

function StateTypes() {
    // 数字类型
    const [count, setCount] = useState(0);
    
    // 字符串类型
    const [name, setName] = useState('张三');
    
    // 布尔类型
    const [isVisible, setIsVisible] = useState(true);
    
    // 数组类型
    const [items, setItems] = useState([1, 2, 3]);
    
    // 对象类型
    const [user, setUser] = useState({
        name: '李四',
        age: 25,
        email: 'lisi@example.com'
    });
    
    return (
        <div>
            <p>计数: {count}</p>
            <p>姓名: {name}</p>
            <p>可见: {isVisible ? '是' : '否'}</p>
            <p>项目: {items.join(', ')}</p>
            <p>用户: {user.name}, {user.age}岁</p>
        </div>
    );
}

3. State更新的正确方式

重要警告:永远不要直接修改State!必须使用setState函数来更新状态。

3.1 错误 vs 正确的更新方式

❌ 错误方式

// 直接修改State(错误)
const [count, setCount] = useState(0);

const handleIncrement = () => {
    count = count + 1;  // 错误!
};

const [items, setItems] = useState([1, 2, 3]);

const handleAdd = () => {
    items.push(4);  // 错误!直接修改数组
};

✅ 正确方式

// 使用setState(正确)
const [count, setCount] = useState(0);

const handleIncrement = () => {
    setCount(count + 1);  // 正确
};

const [items, setItems] = useState([1, 2, 3]);

const handleAdd = () => {
    setItems([...items, 4]);  // 正确
};

3.2 函数式更新

何时使用函数式更新:当新状态依赖于前一个状态时,应该使用函数式更新,避免状态更新延迟导致的问题。

function Counter() {
    const [count, setCount] = useState(0);
    
    // 普通更新(简单场景)
    const incrementSimple = () => {
        setCount(count + 1);
    };
    
    // 函数式更新(推荐)
    const incrementFunctional = () => {
        setCount(prevCount => prevCount + 1);
    };
    
    // 连续多次更新
    const incrementThreeTimes = () => {
        setCount(prevCount => prevCount + 1);
        setCount(prevCount => prevCount + 1);
        setCount(prevCount => prevCount + 1);
    };
    
    return (
        <div>
            <h2>计数: {count}</h2>
            <button onClick={incrementSimple}>
                简单增加
            </button>
            <button onClick={incrementFunctional}>
                函数式增加
            </button>
            <button onClick={incrementThreeTimes}>
                增加三次
            </button>
        </div>
    );
}

4. 复杂State管理

4.1 对象State的更新

function UserProfile() {
    const [user, setUser] = useState({
        name: '张三',
        age: 25,
        email: 'zhangsan@example.com',
        address: {
            city: '北京',
            street: '朝阳区'
        }
    });
    
    // 更新单个属性
    const updateName = () => {
        setUser({
            ...user,
            name: '李四'
        });
    };
    
    // 更新多个属性
    const updateMultiple = () => {
        setUser({
            ...user,
            name: '王五',
            age: 30
        });
    };
    
    // 更新嵌套属性
    const updateAddress = () => {
        setUser({
            ...user,
            address: {
                ...user.address,
                city: '上海'
            }
        });
    };
    
    return (
        <div>
            <h2>用户信息</h2>
            <p>姓名: {user.name}</p>
            <p>年龄: {user.age}</p>
            <p>邮箱: {user.email}</p>
            <p>城市: {user.address.city}</p>
            <button onClick={updateName}>更新姓名</button>
            <button onClick={updateMultiple}>更新多项</button>
            <button onClick={updateAddress}>更新地址</button>
        </div>
    );
}

4.2 数组State的更新

function TodoList() {
    const [todos, setTodos] = useState([
        { id: 1, text: '学习React', completed: false },
        { id: 2, text: '写代码', completed: true }
    ]);
    
    // 添加新项
    const addTodo = () => {
        const newTodo = {
            id: Date.now(),
            text: '新任务',
            completed: false
        };
        setTodos([...todos, newTodo]);
    };
    
    // 删除项
    const deleteTodo = (id) => {
        setTodos(todos.filter(todo => todo.id !== id));
    };
    
    // 更新项
    const toggleTodo = (id) => {
        setTodos(todos.map(todo =>
            todo.id === id
                ? { ...todo, completed: !todo.completed }
                : todo
        ));
    };
    
    return (
        <div>
            <h2>待办事项</h2>
            <button onClick={addTodo}>添加任务</button>
            <ul>
                {todos.map(todo => (
                    <li key={todo.id}>
                        <span 
                            style={{
                                textDecoration: todo.completed ? 'line-through' : 'none'
                            }}
                            onClick={() => toggleTodo(todo.id)}
                        >
                            {todo.text}
                        </span>
                        <button onClick={() => deleteTodo(todo.id)}>
                            删除
                        </button>
                    </li>
                ))}
            </ul>
        </div>
    );
}

5. State更新的异步特性

重要提示:setState是异步的,React会批量处理多个setState调用以提高性能。这意味着在setState之后立即读取state可能得到的是旧值。

function AsyncStateExample() {
    const [count, setCount] = useState(0);
    
    const handleClick = () => {
        console.log('点击前:', count);
        
        setCount(count + 1);
        console.log('setState后:', count);
        
        // 这里的count仍然是旧值!
        // 因为setState是异步的
    };
    
    const handleClickCorrect = () => {
        setCount(prevCount => {
            const newCount = prevCount + 1;
            console.log('新值:', newCount);
            return newCount;
        });
    };
    
    return (
        <div>
            <h2>计数: {count}</h2>
            <button onClick={handleClick}>
                点击我(查看控制台)
            </button>
            <button onClick={handleClickCorrect}>
                正确方式
            </button>
        </div>
    );
}

6. 多个State vs 单个对象State

多个独立State(推荐)

function MultipleStates() {
    const [name, setName] = useState('');
    const [age, setAge] = useState(0);
    const [email, setEmail] = useState('');
    
    // 每个状态独立更新
    const handleNameChange = (e) => {
        setName(e.target.value);
    };
    
    return (
        <form>
            <input 
                value={name}
                onChange={handleNameChange}
            />
            <!-- 其他输入框 -->
        </form>
    );
}

单个对象State

function SingleState() {
    const [formData, setFormData] = useState({
        name: '',
        age: 0,
        email: ''
    });
    
    // 更新时需要展开对象
    const handleChange = (e) => {
        const { name, value } = e.target;
        setFormData({
            ...formData,
            [name]: value
        });
    };
    
    return (
        <form>
            <input 
                name="name"
                value={formData.name}
                onChange={handleChange}
            />
            <!-- 其他输入框 -->
        </form>
    );
}

选择建议:当状态之间没有关联时,使用多个独立的State;当状态之间有紧密关联时,可以使用单个对象State。

7. State初始化的惰性计算

性能优化:如果初始状态需要通过复杂计算得出,可以使用函数作为useState的参数,这样函数只在组件首次渲染时执行一次。

// ❌ 不推荐:每次渲染都会计算
function ExpensiveInitialization() {
    const [data, setData] = useState(
        calculateExpensiveData()  // 每次渲染都执行
    );
    // ...
}

// ✅ 推荐:只在首次渲染时计算
function LazyInitialization() {
    const [data, setData] = useState(() => 
        calculateExpensiveData()  // 只执行一次
    );
    // ...
}

// 示例:从localStorage读取初始值
function LocalStorageExample() {
    const [user, setUser] = useState(() => {
        const savedUser = localStorage.getItem('user');
        return savedUser ? JSON.parse(savedUser) : null;
    });
    // ...
}

💡 动手练习

任务:创建一个购物车组件

要求:

  • 使用State管理商品列表和购物车
  • 实现添加商品到购物车功能
  • 实现从购物车移除商品功能
  • 实现修改商品数量功能
  • 显示购物车总价
// 参考答案
function ShoppingCart() {
    const [products] = useState([
        { id: 1, name: 'iPhone', price: 5999 },
        { id: 2, name: 'MacBook', price: 9999 },
        { id: 3, name: 'iPad', price: 3299 }
    ]);
    
    const [cart, setCart] = useState([]);
    
    const addToCart = (product) => {
        setCart(prevCart => {
            const existingItem = prevCart.find(item => item.id === product.id);
            if (existingItem) {
                return prevCart.map(item =>
                    item.id === product.id
                        ? { ...item, quantity: item.quantity + 1 }
                        : item
                );
            }
            return [...prevCart, { ...product, quantity: 1 }];
        });
    };
    
    const removeFromCart = (productId) => {
        setCart(cart.filter(item => item.id !== productId));
    };
    
    const updateQuantity = (productId, quantity) => {
        if (quantity <= 0) {
            removeFromCart(productId);
            return;
        }
        setCart(cart.map(item =>
            item.id === productId
                ? { ...item, quantity }
                : item
        ));
    };
    
    const totalPrice = cart.reduce(
        (sum, item) => sum + item.price * item.quantity,
        0
    );
    
    return (
        <div>
            <h2>商品列表</h2>
            {products.map(product => (
                <div key={product.id}>
                    <span>{product.name} - ¥{product.price}</span>
                    <button onClick={() => addToCart(product)}>
                        添加到购物车
                    </button>
                </div>
            ))}
            
            <h2>购物车</h2>
            {cart.map(item => (
                <div key={item.id}>
                    <span>{item.name} - ¥{item.price}</span>
                    <input 
                        type="number"
                        value={item.quantity}
                        onChange={(e) => updateQuantity(item.id, parseInt(e.target.value))}
                        min="0"
                    />
                    <button onClick={() => removeFromCart(item.id)}>
                        移除
                    </button>
                </div>
            ))}
            <h3>总价: ¥{totalPrice}</h3>
        </div>
    );
}

📝 关键概念总结

  • State:组件内部的数据,变化会触发重新渲染
  • useState:用于在函数组件中添加State
  • 不可变性:不能直接修改State,必须使用setState
  • 函数式更新:当新状态依赖旧状态时使用
  • 异步更新:setState是异步的,批量处理
  • 复杂状态:使用展开运算符处理对象和数组