目录

第六章:Props 和 State

6.1 理解 State

State(状态)是组件内部管理的可变数据。与 props 不同,state 是私有的,完全受控于组件本身。

State 的特点

什么时候用 State

什么时候不用 State

6.2 类组件中的 State

初始化 State

class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
      name: '计数器'
    };
  }
 
  // 或者使用类属性语法
  state = {
    count: 0,
    name: '计数器'
  };
}

读取 State

class Counter extends React.Component {
  state = { count: 0 };
 
  render() {
    return <div>{this.state.count}</div>;
  }
}

更新 State

class Counter extends React.Component {
  state = { count: 0 };
 
  increment = () => {
    this.setState({ count: this.state.count + 1 });
  };
 
  render() {
    return (
      <div>
        <p>{this.state.count}</p>
        <button onClick={this.increment}>+1</button>
      </div>
    );
  }
}

State 更新的合并

setState 会将提供的对象浅合并到当前 state。

// state = { count: 0, name: 'test' }
 
this.setState({ count: 1 });
// state 变为 { count: 1, name: 'test' }
// name 没有被改变

基于前一个 State 更新

由于 setState 可能是异步的,依赖前一个 state 时应该使用函数形式。

// 可能不正确
this.setState({ count: this.state.count + 1 });
 
// 正确
this.setState((prevState) => ({
  count: prevState.count + 1
}));
 
// 使用第二个参数 props
this.setState((prevState, props) => ({
  count: prevState.count + props.increment
}));

State 更新后的回调

setState 的第二个参数是回调函数,在 state 更新并重新渲染后执行。

this.setState(
  { count: this.state.count + 1 },
  () => {
    console.log('Count updated:', this.state.count);
  }
);

6.3 函数组件中的 State(Hooks)

useState Hook 让函数组件也能使用 state。

基本用法

import { useState } from 'react';
 
function Counter() {
  const [count, setCount] = useState(0);
 
  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
}

useState 返回数组

初始值

useState 的参数是初始值,只在首次渲染时使用。

// 简单初始值
const [count, setCount] = useState(0);
 
// 函数式初始值(适用于复杂计算)
const [state, setState] = useState(() => {
  const initialState = someExpensiveComputation();
  return initialState;
});

更新 State

// 直接设置新值
setCount(5);
 
// 基于前一个 state 更新
setCount(prevCount => prevCount + 1);
 
// 更新对象
const [user, setUser] = useState({ name: '张三', age: 25 });
 
// 错误:直接修改
user.age = 26;
setUser(user); // 不会触发重新渲染
 
// 正确:创建新对象
setUser({ ...user, age: 26 });
 
// 使用函数式更新
setUser(prevUser => ({ ...prevUser, age: prevUser.age + 1 }));

多个 State

function Form() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [age, setAge] = useState(0);
 
  // 或者使用一个对象
  const [form, setForm] = useState({
    name: '',
    email: '',
    age: 0
  });
}

6.4 State 的不可变性

React 中的 state 应该被视为不可变的。

为什么不可变性很重要

处理数组

const [items, setItems] = useState(['a', 'b', 'c']);
 
// 添加元素
setItems([...items, 'd']);
 
// 插入元素
setItems([...items.slice(0, 1), 'x', ...items.slice(1)]);
 
// 删除元素
setItems(items.filter((_, index) => index !== 1));
 
// 修改元素
setItems(items.map((item, index) => 
  index === 1 ? 'modified' : item
));
 
// 排序(先复制)
setItems([...items].sort());

处理对象

const [user, setUser] = useState({
  name: '张三',
  address: {
    city: '北京',
    street: '长安街'
  }
});
 
// 更新顶层属性
setUser({ ...user, name: '李四' });
 
// 更新嵌套对象(注意深拷贝问题)
setUser({
  ...user,
  address: { ...user.address, city: '上海' }
});
 
// 或者使用 immer
import produce from 'immer';
 
setUser(produce(user, draft => {
  draft.address.city = '上海';
}));

6.5 Props vs State

特性 Props State
————-——-
来源 父组件传递 组件内部管理
可读性 只读 可读写
修改 不能修改 通过 setState 修改
作用域 父子组件通信 组件内部
触发渲染 props 变化触发重新渲染 state 变化触发重新渲染

数据流

6.6 状态提升

当多个组件需要共享状态时,将状态提升到它们的最近公共祖先组件。

// 温度转换器示例
 
function BoilingVerdict({ celsius }) {
  if (celsius >= 100) {
    return <p>水会沸腾</p>;
  }
  return <p>水不会沸腾</p>;
}
 
class Calculator extends React.Component {
  state = { temperature: '' };
 
  handleChange = (e) => {
    this.setState({ temperature: e.target.value });
  };
 
  render() {
    const { temperature } = this.state;
    return (
      <fieldset>
        <legend>输入温度:</legend>
        <input value={temperature} onChange={this.handleChange} />
        <BoilingVerdict celsius={parseFloat(temperature)} />
      </fieldset>
    );
  }
}

状态提升的步骤

1. 找到需要共享 state 的组件
2. 找到它们的最近公共祖先
3. 将 state 定义在祖先组件中
4. 通过 props 将 state 传递给子组件
5. 通过回调函数让子组件修改 state

6.7 派生 State

某些情况下,state 可以从 props 派生出来。

反模式:复制 props 到 state

// 不要这样做
class EmailInput extends React.Component {
  state = {
    email: this.props.email // 复制 props 到 state
  };
 
  // 问题:props 更新时 state 不会更新
}

正确使用派生 state

// 方法 1:完全受控组件
function EmailInput({ email, onChange }) {
  return <input value={email} onChange={onChange} />;
}
 
// 方法 2:使用 key 强制重新挂载
<EmailInput key={userId} defaultEmail={user.email} />
 
// 方法 3:只在 prop 真正变化时更新 state
class EmailInput extends React.Component {
  state = {
    email: this.props.defaultEmail
  };
 
  componentDidUpdate(prevProps) {
    if (prevProps.defaultEmail !== this.props.defaultEmail) {
      this.setState({ email: this.props.defaultEmail });
    }
  }
}

6.8 批量更新

React 18 引入了自动批处理,多个 state 更新会被合并为一次重新渲染。

function App() {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);
 
  function handleClick() {
    setCount(c => c + 1); // 不会立即重新渲染
    setFlag(f => !f);     // 不会立即重新渲染
    // React 会等到所有 state 更新完成后再重新渲染
  }
 
  console.log('Render'); // 只会打印一次
 
  return (
    <div>
      <button onClick={handleClick}>点击</button>
    </div>
  );
}

flushSync

如果需要强制同步更新,可以使用 flushSync(不推荐常规使用):

import { flushSync } from 'react-dom';
 
function handleClick() {
  flushSync(() => {
    setCount(c => c + 1);
  });
  // 到这里 DOM 已经更新了
  flushSync(() => {
    setFlag(f => !f);
  });
}

6.9 总结

本章深入讲解了 Props 和 State:

理解 state 是 React 开发的核心,下一章我们将学习事件处理。