State

下面,我们来系统的梳理关于 State 的基本知识点:


一、State 核心概念

1.1 什么是 State?

State 是 React 组件内部管理动态数据的机制,具有以下关键特性:

  • 组件私有:只能由组件自身修改
  • 驱动渲染:state 变化触发组件重新渲染
  • 局部性:每个组件实例拥有独立 state
  • 异步更新:React 会批量处理 state 更新

1.2 State 与 Props 的区别

特性StateProps
所有权组件内部父组件传入
可变性可修改 (setState)只读
作用范围组件内部父子组件间通信
更新触发setState 调用父组件重新渲染

二、State 基础使用

2.1 类组件 State

class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 }; // 初始化state
    
    // 方法绑定
    this.increment = this.increment.bind(this);
  }

  increment() {
    this.setState({ count: this.state.count + 1 });
  }

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.increment}>+</button>
      </div>
    );
  }
}

2.2 函数组件 State (Hooks)

import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0); // 初始化state
  
  const increment = () => {
    setCount(count + 1); // 更新state
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>+</button>
    </div>
  );
}

三、State 更新机制

3.1 异步批量更新

// 错误:连续调用不会累加
setCount(count + 1);
setCount(count + 1);

// 正确:使用函数式更新
setCount(prevCount => prevCount + 1);
setCount(prevCount => prevCount + 1);

3.2 合并更新(类组件)

// React 会自动合并对象更新
this.setState({ count: 1 });
this.setState({ flag: true });

// 等价于
this.setState({ count: 1, flag: true });

3.3 强制同步更新

// 在React事件处理中默认是批处理的
function handleClick() {
  setCount(c => c + 1);
  setFlag(f => !f);
  // 这里只会触发一次渲染
}

// 强制同步更新(不推荐)
flushSync(() => {
  setCount(c => c + 1);
});
// 这里会立即触发渲染

四、State 设计原则

4.1 最小化 State

// 反例:冗余state
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [fullName, setFullName] = useState('');

// 正例:派生状态
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const fullName = `${firstName} ${lastName}`;

4.2 避免深层嵌套

// 反例:深层嵌套state
const [user, setUser] = useState({
  profile: {
    name: '',
    address: {
      city: '',
      street: ''
    }
  }
});

// 正例:扁平化state
const [name, setName] = useState('');
const [city, setCity] = useState('');
const [street, setStreet] = useState('');

4.3 不可变更新

// 错误:直接修改state
const [todos, setTodos] = useState([...]);
todos[0].completed = true; // 错误!

// 正确:创建新对象/数组
setTodos(prevTodos => 
  prevTodos.map(todo => 
    todo.id === id ? { ...todo, completed: true } : todo
  )
);

五、高级 State 模式

5.1 状态提升

function Parent() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <ChildA count={count} />
      <ChildB onIncrement={() => setCount(c => c + 1)} />
    </div>
  );
}

5.2 状态机模式

function TrafficLight() {
  const [state, setState] = useState('red');
  
  const transitions = {
    red: { next: 'green' },
    green: { next: 'yellow' },
    yellow: { next: 'red' }
  };

  const nextLight = () => {
    setState(transitions[state].next);
  };

  return (
    <div>
      <Light color="red" active={state === 'red'} />
      <Light color="yellow" active={state === 'yellow'} />
      <Light color="green" active={state === 'green'} />
      <button onClick={nextLight}>Next</button>
    </div>
  );
}

5.3 使用 useReducer

const initialState = { count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  
  return (
    <div>
      Count: {state.count}
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </div>
  );
}

六、State 性能优化

6.1 避免不必要渲染

// 使用 React.memo 优化子组件
const Child = React.memo(function Child({ data }) {
  // 只有当props变化时才会重新渲染
});

// 使用 useMemo 避免重复计算
const expensiveValue = useMemo(() => {
  return computeExpensiveValue(a, b);
}, [a, b]);

6.2 惰性初始化 State

// 避免每次渲染都执行初始化函数
const [state, setState] = useState(() => {
  const initialState = computeExpensiveValue();
  return initialState;
});

6.3 状态分片

// 将大状态对象拆分为多个小状态
const [userInfo, setUserInfo] = useState({ ... });
// 拆分为:
const [name, setName] = useState('');
const [email, setEmail] = useState('');
// 这样能更精准控制更新范围

七、State 与副作用

7.1 数据获取

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  useEffect(() => {
    setLoading(true);
    fetchUser(userId)
      .then(data => {
        setUser(data);
        setError(null);
      })
      .catch(err => setError(err))
      .finally(() => setLoading(false));
  }, [userId]);

  if (loading) return <Spinner />;
  if (error) return <Error message={error.message} />;
  
  return <Profile user={user} />;
}

7.2 订阅/取消订阅

function OnlineStatus({ userId }) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    const subscription = subscribeToStatus(userId, status => {
      setIsOnline(status);
    });

    return () => {
      unsubscribeFromStatus(subscription);
    };
  }, [userId]);

  // ...
}

八、常见问题与解决方案

8.1 Stale State 问题

// 问题:闭包中的旧state值
function Counter() {
  const [count, setCount] = useState(0);
  
  const increment = () => {
    setTimeout(() => {
      // 这里获取的是点击时的count值
      setCount(count + 1);
    }, 1000);
  };
  
  // 解决:使用函数式更新
  const incrementCorrect = () => {
    setTimeout(() => {
      setCount(prev => prev + 1);
    }, 1000);
  };
}

8.2 循环依赖

// 问题:state更新触发effect,effect又更新state
const [count, setCount] = useState(0);

useEffect(() => {
  if (count < 10) {
    setCount(count + 1); // 无限循环
  }
}, [count]);

// 解决:检查是否需要更新
useEffect(() => {
  if (count < 10 && someCondition) {
    setCount(count + 1);
  }
}, [count, someCondition]);

8.3 复杂状态管理

// 当组件状态过于复杂时:
// 方案1:拆分为多个小组件
// 方案2:使用 useReducer
// 方案3:考虑状态管理库(Redux, Zustand等)

九、实战案例

9.1 表单状态管理

function SignupForm() {
  const [form, setForm] = useState({
    username: '',
    email: '',
    password: '',
    agreeTerms: false
  });

  const handleChange = (e) => {
    const { name, value, type, checked } = e.target;
    setForm(prev => ({
      ...prev,
      [name]: type === 'checkbox' ? checked : value
    }));
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    // 提交逻辑...
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        name="username"
        value={form.username}
        onChange={handleChange}
      />
      {/* 其他表单项... */}
    </form>
  );
}

9.2 购物车状态

function Cart() {
  const [items, setItems] = useState([]);
  const [coupon, setCoupon] = useState('');

  const addItem = (product) => {
    setItems(prev => {
      const existing = prev.find(item => item.id === product.id);
      return existing
        ? prev.map(item => 
            item.id === product.id 
              ? { ...item, qty: item.qty + 1 } 
              : item
          )
        : [...prev, { ...product, qty: 1 }];
    });
  };

  const total = items.reduce((sum, item) => 
    sum + (item.price * item.qty), 0);

  // ...
}

十、最佳实践总结

  1. 单一数据源:每个状态只保存在一个组件中
  2. 最小化原则:只存储必要数据,派生数据可即时计算
  3. 不可变性:总是通过创建新对象/数组来更新状态
  4. 合理拆分:复杂状态考虑拆分为多个useState或使用useReducer
  5. 性能意识:避免不必要的状态更新和组件重渲染
  6. 类型安全:使用TypeScript定义状态类型
  7. 测试友好:保持状态逻辑纯净,便于单元测试
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

前端岳大宝

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值