4、组件状态管理:useState Hook详解、状态提升、状态与props的区别、不可变数据原则

好,咱们进入第四讲。这一章可以说是 React 组件化思维的核心中的核心——状态管理。

很多新手写 React,写着写着就乱了。为什么?因为对状态的理解不够深。我见过太多人把状态当全局变量用,或者把 props 当状态改。今天咱们就把这些坑一个一个填上。

4.1 useState Hook 详解

先说说 useState。这是 React 16.8 引入的 Hook,说白了就是让函数组件也能拥有自己的状态。

用法很简单:

const [count, setCount] = useState(0);

左边是状态值和更新函数,右边是初始值。嗯,这里要注意:初始值只在首次渲染时生效

我举个例子:

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>你点击了 {count} 次</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
}

每次点击按钮,setCount 会触发重新渲染。React 会记住最新的 count 值,而不是重新初始化。

个人习惯:我写 useState 时,喜欢把相关的状态放在一起。比如表单字段,我会用一个对象管理,而不是拆成五六个独立的 useState。

4.2 状态与 props 的区别

这个问题我面试时必问。很多人答不上来,或者答得模棱两可。

其实很简单:

  • props 是父组件传进来的,组件内部不能改
  • state 是组件自己管理的,可以随时更新

打个比方:props 就像你从家里带的盒饭,你不能在食堂里把它变成汉堡。state 就像你到食堂后点的菜,想吃啥点啥,还能加辣。

看个实际例子:

function UserCard({ name, age }) {
  // name 和 age 是 props,不能改
  const [isEditing, setIsEditing] = useState(false);
  // isEditing 是 state,可以改

  return (
    <div>
      <p>{name} - {age}岁</p>
      <button onClick={() => setIsEditing(!isEditing)}>
        {isEditing ? '保存' : '编辑'}
      </button>
    </div>
  );
}
我曾经踩过的坑:有次我把 props 直接赋值给了 state,然后发现子组件永远不更新。为什么?因为 props 变了,但 state 还是旧值。记住:不要用 props 初始化 state,除非你真的知道自己在做什么。

4.3 状态提升

这是 React 里一个非常重要的模式。说白了就是:把多个组件共享的状态,提到它们最近的共同父组件里

为什么要这么做?

你想想看,两个兄弟组件要共享数据怎么办?总不能一个改另一个的 state 吧?那太乱了。正确的做法是把状态放到父组件,然后通过 props 往下传。

来看个经典例子——温度转换器:

function TemperatureInput({ scale, temperature, onTemperatureChange }) {
  return (
    <fieldset>
      <legend>{scale === 'c' ? '摄氏' : '华氏'}温度</legend>
      <input
        value={temperature}
        onChange={(e) => onTemperatureChange(e.target.value)}
      />
    </fieldset>
  );
}

function Calculator() {
  const [temperature, setTemperature] = useState('');
  const [scale, setScale] = useState('c');

  const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
  const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;

  function handleCelsiusChange(value) {
    setScale('c');
    setTemperature(value);
  }

  function handleFahrenheitChange(value) {
    setScale('f');
    setTemperature(value);
  }

  return (
    <div>
      <TemperatureInput
        scale="c"
        temperature={celsius}
        onTemperatureChange={handleCelsiusChange}
      />
      <TemperatureInput
        scale="f"
        temperature={fahrenheit}
        onTemperatureChange={handleFahrenheitChange}
      />
    </div>
  );
}

看到没?温度状态放在 Calculator 里,两个 TemperatureInput 通过 props 接收和修改。这就是状态提升。

核心原则:状态尽量往下放,但共享状态要往上提。找到那个「不多不少」的层级,就是最佳实践。

4.4 不可变数据原则

这个原则太重要了。我刚开始写 React 时,经常直接修改 state 里的对象或数组,结果界面死活不更新。

为什么?因为 React 用的是浅比较。你直接修改对象属性,引用没变,React 就认为没变化。

正确的做法是:每次更新都返回一个新的对象或数组

看几个例子:

// ❌ 错误:直接修改 state
const [user, setUser] = useState({ name: '张三', age: 25 });
user.age = 26;  // 这样不行!
setUser(user);  // 界面不会更新

// ✅ 正确:创建新对象
setUser({
  ...user,
  age: 26
});

// 数组也一样
const [items, setItems] = useState([1, 2, 3]);

// ❌ 错误
items.push(4);
setItems(items);

// ✅ 正确
setItems([...items, 4]);

// 删除元素
setItems(items.filter(item => item !== 2));

// 更新元素
setItems(items.map(item => 
  item === 2 ? 200 : item
));
我的建议:如果数据结构比较复杂,可以考虑用 Immer 这个库。它让你可以「看似」直接修改,实际上帮你生成了不可变数据。不过小项目用展开运算符就够了。

4.5 总结一下

这一章咱们聊了四个核心概念:

  • useState:函数组件的状态管理工具,简单但强大
  • 状态 vs props:一个可变一个不可变,别搞混了
  • 状态提升:共享状态往上提,各组件各司其职
  • 不可变数据:永远不要直接修改 state,创建新值才是正道

这些概念看起来简单,但真正用好需要大量实践。我建议你从现在开始,每次写组件时都问自己三个问题:

  1. 这个数据应该是 state 还是 props?
  2. 这个状态应该放在哪个组件?
  3. 我更新状态时,有没有创建新对象?

养成这个习惯,你的 React 代码会越来越清晰。下一章咱们聊 useEffect 和副作用管理,到时候见。