4、React状态管理:useState Hook、状态提升、单向数据流、受控组件与非受控组件

好,咱们今天聊聊React的状态管理。说实话,这块是很多新手最头疼的地方。我刚学React那会儿,也被各种概念绕得晕头转向。但别怕,咱们一个一个拆开讲。

4.1 useState Hook:最基础的状态管理

先说说 useState。这是React里最常用的Hook,没有之一。它的作用很简单——让函数组件拥有自己的状态。

看个例子:

import React, { useState } from 'react';

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

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

这里 useState(0) 返回一个数组。第一个值是当前状态,第二个是更新状态的函数。我个人习惯用解构赋值来拿这两个值,代码看起来更清爽。

小提示: 更新状态时,React会重新渲染组件。但注意,状态更新是异步的。如果你连续调用两次 setCount(count + 1),结果可能不是你想的那样。我建议用函数式更新:setCount(prev => prev + 1)

4.2 状态提升:让兄弟组件共享数据

你想想看,如果两个兄弟组件需要共享同一个状态,怎么办?

答案就是——状态提升。把共享的状态放到它们共同的父组件里,然后通过props传下去。

我在项目中遇到过这样一个场景:一个搜索框和一个搜索结果列表,它们需要共享搜索关键词。我一开始把状态分别放在两个组件里,结果数据不同步,改起来特别痛苦。后来把状态提升到父组件,问题就解决了。

function Parent() {
  const [keyword, setKeyword] = useState('');

  return (
    <div>
      <SearchBox keyword={keyword} onSearch={setKeyword} />
      <ResultList keyword={keyword} />
    </div>
  );
}

这样,keyword 状态只存在于 Parent 中,两个子组件通过props获取或修改它。数据流清晰,维护起来也方便。

注意: 状态提升虽然好用,但别滥用。如果层级太深,props传递会变得很繁琐。这时候可以考虑用Context或状态管理库。

4.3 单向数据流:React的核心理念

React的数据流是单向的。什么意思?说白了,数据从父组件流向子组件,子组件不能直接修改父组件的状态。

为什么这样设计?我刚开始也觉得麻烦,但后来发现,单向数据流让数据变化变得可预测。你想想看,如果子组件能随便改父组件的状态,那调试起来得多崩溃?

举个例子:

// 父组件
function App() {
  const [name, setName] = useState('张三');

  return <Child name={name} onChangeName={setName} />;
}

// 子组件
function Child({ name, onChangeName }) {
  return (
    <div>
      <p>我的名字是:{name}</p>
      <button onClick={() => onChangeName('李四')}>改名</button>
    </div>
  );
}

子组件通过 onChangeName 回调来通知父组件修改状态,而不是直接改。这就是单向数据流的精髓。

4.4 受控组件与非受控组件

这块是表单处理的核心。很多新手搞不清楚,其实很简单。

受控组件

受控组件,就是表单元素的值由React状态控制。每次输入变化,都会触发 onChange 事件,更新状态,然后React重新渲染,把新值赋给表单元素。

function ControlledForm() {
  const [inputValue, setInputValue] = useState('');

  return (
    <input
      type="text"
      value={inputValue}
      onChange={(e) => setInputValue(e.target.value)}
    />
  );
}

这样做的好处是,你可以随时获取表单的值,做校验、格式化等操作。我习惯用受控组件,因为数据完全在React掌控之中。

非受控组件

非受控组件,就是表单元素的值由DOM自己管理。你通过 ref 来获取它的值。

function UncontrolledForm() {
  const inputRef = useRef(null);

  const handleSubmit = () => {
    alert(inputRef.current.value);
  };

  return (
    <div>
      <input type="text" ref={inputRef} />
      <button onClick={handleSubmit}>提交</button>
    </div>
  );
}

非受控组件适合那些你不需要实时监控值的场景,比如文件上传。我曾经在一个旧项目里用过非受控组件,因为表单字段太多,全部受控的话性能有点扛不住。

核心区别: 受控组件的值由React状态驱动,非受控组件的值由DOM自身维护。大多数情况下,我推荐用受控组件,数据更可控。

4.5 实战中的选择建议

说了这么多,到底怎么选?我总结一下:

场景 推荐方式 原因
简单表单(1-3个字段) 受控组件 数据可控,方便校验
复杂表单(大量字段) 受控组件 + 状态管理库 避免props层层传递
文件上传 非受控组件 文件对象由DOM管理更合适
第三方UI库集成 视情况而定 有些库强制要求受控或非受控
避坑指南: 我曾经在一个项目中,把受控组件的 value 设成了 undefined,结果输入框变成了非受控模式,导致数据不同步。嗯,这里要注意:value 要么不传,要么传一个有效的值,别传 undefined

好了,这一章的内容就这些。记住,状态管理是React的基石,多练练,慢慢就熟练了。下一章咱们聊聊React的副作用处理——useEffect,那又是一个让人又爱又恨的东西。