State和Props区别和左右(自学用)

其中哪些是 state 呢?标记出那些不是的:

  • 随着时间推移 保持不变?如此,便不是 state。
  • 通过 props 从父组件传递?如此,便不是 state。
  • 是否可以基于已存在于组件中的 state 或者 props 进行计算?如此,它肯定不是state!

在 React 中有两种"模型"数据:props 和 state。下面是它们的不同之处:

  • props像是传递的参数 。它们使父组件可以传递数据给子组件,定制它们的展示。举个例子,Form 可以传递 color prop 至 Button
  • state像是组件的内存。它使组件可以对一些信息保持追踪,并根据交互来改变。举个例子,Button 可以保持对 isHovered state 的追踪。

一、先建立核心认知:state 和 props 的本质定义

在拆解底层前,先明确两者的本质,这是理解一切的基础:

  • props(Properties) :组件的 "外部输入",是父组件传递给子组件的只读数据(可以是基本类型、对象、函数),本质是组件间通信的 "桥梁"。
  • state(State) :组件的 "内部状态",是组件自身管理的可变数据,触发更新时会驱动组件重新渲染,本质是组件内部的 "动态数据源"。

二、底层原理:React 如何处理 state 和 props

1. props 的底层机制

props 的核心是单向数据流,其底层逻辑可以拆解为 3 个关键点:

  • 只读性:React 设计上强制 props 只读(Immutable),组件内部直接修改 props 会报错。这是为了保证数据流向可追溯(父 → 子),避免 "数据溯源混乱"。
  • 浅对比(Shallow Compare) :当父组件重新渲染时,React 会对新旧 props 做浅对比 (只比较引用 / 基本类型值,不深查对象内部):
    • 如果 props 没变化,子组件会被 React 优化(跳过重渲染);
    • 如果 props 变化(比如引用变了),子组件会触发重渲染。
  • 传递链路:props 只能沿组件树 "向下传递"(父 → 子 → 孙),无法反向传递(子 → 父),若需反向通信,需通过 "父传子函数" 实现(子调用父的函数,修改父的 state)。

2. state 的底层机制

state 是 React 组件 "响应式" 的核心,其底层逻辑围绕状态更新队列重渲染机制展开:

  • 状态更新的异步性setState(类组件)/ setXxx(useState)并非立即更新状态,而是将更新请求加入 React 的更新队列,等当前事件循环结束后,React 会批量处理更新队列,再触发组件重渲染。
tsx 复制代码
// 示例:异步更新的表现
function Counter() {
  const [count, setCount] = useState(0);
  const handleClick = () => {
    setCount(count + 1);
    console.log(count); // 输出旧值,因为 setCount 是异步的
  };
  return <button onClick={handleClick}>点击 {count}</button>;
}
  • 状态的不可变更新(Immutable Update) :React 要求更新 state 时不能直接修改原数据(比如直接改 state 里的对象 / 数组),必须返回新的引用,否则 React 无法检测到状态变化,会跳过重渲染:
tsx 复制代码
const [user, setUser] = useState({ name: "张三" });

// ❌ 错误:直接修改原对象,React 检测不到变化
const handleChange = () => {
  user.name = "李四"; // 直接修改原对象
  setUser(user); // 引用没变,组件不会重渲染
};

// ✅ 正确:返回新对象,React 检测到引用变化
const handleChange = () => {
  setUser({ ...user, name: "李四" }); // 解构生成新对象
};
  • 重渲染触发条件 :只有通过 React 提供的更新函数(setState/setXxx)修改 state,才会触发组件重渲染;直接修改 state 变量(比如 count = 1)不会触发重渲染,因为 React 无法监听普通变量的变化。

3. state 和 props 的底层关联

  • 当父组件的 state 变化触发重渲染时,会重新计算子组件的 props,若 props 变化则子组件重渲染;
  • 子组件的 state 变化仅影响自身及子组件,不会反向影响父组件的 props(单向数据流);
  • React 组件的渲染本质是:UI = f(props, state)(UI 是 props 和 state 的纯函数),props 和 state 是驱动 UI 变化的唯一数据源。

三、核心区别(全维度对比)

维度 state (状态) props (属性)
所有权 组件自身拥有(内部生成) 父组件 / 外部传入(组件无所有权)
可修改性 可通过 setState /useState 更新(间接修改) 只读(组件内部不可改,只能由外部更新)
数据类型 任意类型(但推荐不可变类型:对象 / 数组用新引用) 任意类型(基本类型、对象、函数、JSX 元素)
触发重渲染 调用更新函数会触发自身及子组件重渲染 父组件传递的 props 变化会触发自身重渲染
作用域 仅当前组件实例有效(隔离) 可跨组件传递(父 → 子)
初始化方式 组件内部初始化(useState(初始值) 父组件传递(无默认值则为 undefined)
默认值处理 初始化时直接指定 可通过 defaultProps 或解构赋值指定

四、典型使用场景(附实战示例)

1. 只用 state 的场景(组件私有动态数据)

适用于组件自己管理、无需和其他组件共享的状态:

  • 输入框的实时输入内容

  • 弹窗的显示 / 隐藏状态

  • 单选框 / 复选框的选中状态

  • 独立计数器的数值

tsx 复制代码
// 示例:弹窗的显示/隐藏(私有 state)
function Modal() {
  // 私有状态:控制弹窗显示
  const [isOpen, setIsOpen] = useState(false);

  return (
    <div>
      <button onClick={() => setIsOpen(true)}>打开弹窗</button>
      {isOpen && (
        <div className="modal">
          <p>这是弹窗内容</p>
          <button onClick={() => setIsOpen(false)}>关闭</button>
        </div>
      )}
    </div>
  );
}

2. 只用 props 的场景(纯展示 / 接收外部配置)

适用于组件仅展示数据、无内部逻辑,所有数据 / 行为由外部传入:

  • 通用按钮组件(接收文字、点击事件)

  • 列表项组件(接收展示数据)

  • 卡片组件(接收标题、内容、样式配置)

tsx 复制代码
// 示例:通用按钮组件(纯 props 接收)
function Button({ text, size = "middle", onClick }) {
  // 无 state,仅使用 props 渲染
  const sizeClass = `btn-${size}`;
  return (
    <button className={sizeClass} onClick={onClick}>
      {text}
    </button>
  );
}

// 使用:父组件传递 props
function Parent() {
  return <Button text="提交" size="large" onClick={() => alert("提交成功")} />;
}

3. state + props 结合的场景(跨组件共享状态)

这是最常见的场景:父组件用 state 管理共享状态,通过 props 传递给子组件,子组件通过 props 接收的函数修改父组件的 state

典型场景:共享计数器、表单提交、列表筛选等。

tsx 复制代码
// 示例:共享计数器(父组件 state + 子组件 props)
import { useState } from "react";

// 父组件:管理共享 state
function Parent() {
  const [total, setTotal] = useState(0);

  // 定义修改 state 的函数,通过 props 传给子组件
  const increment = () => setTotal(total + 1);
  const decrement = () => setTotal(total - 1);

  return (
    <div>
      <h2>总次数:{total}</h2>
      {/* 传递数据和函数给子组件 */}
      <CounterButton text="加1" onClick={increment} />
      <CounterButton text="减1" onClick={decrement} />
    </div>
  );
}

// 子组件:仅使用 props,无自己的 state
function CounterButton({ text, onClick }) {
  return <button onClick={onClick}>{text}</button>;
}

4. 易错场景 & 避坑指南

  • 坑 1:子组件想修改 props
    • ❌ 错误:props.count = 1(直接修改 props);
    • ✅ 正确:父组件传递更新函数,子组件调用函数修改父组件的 state。
  • 坑 2:直接修改 state 里的对象 / 数组
    • ❌ 错误:state.list.push(1)(直接改原数组);
    • ✅ 正确:setList([...state.list, 1])(生成新数组)。
  • 坑 3:依赖异步 state 更新
    • ❌ 错误:连续调用 setCount(count + 1) 多次,只生效一次;
    • ✅ 正确:使用函数式更新 setCount(prev => prev + 1)

五、进阶扩展:state 管理的演进(超出基础但实用)

当项目规模变大,单纯的组件内 state 无法满足需求时,会用到更进阶的状态管理方案,本质是对 state 作用域的扩展:

  1. useContext + useState:跨多层组件共享 state(避免 props 层层传递,即 "props 钻取");
  2. Redux/MobX/Zustand:全局状态管理(适用于跨页面、跨组件的全局数据,比如用户信息、购物车);
  3. React Query/SWR:异步状态管理(专门处理 API 请求的加载、缓存、更新)。

这些方案的核心依然是遵循 React 的单向数据流,只是把 state 的作用域从 "单个组件" 扩展到 "全局 / 跨组件"。

总结

  1. 底层核心:props 是只读的单向数据流(父→子),state 是组件内部的可变状态,更新需遵循不可变原则,且更新是异步的;
  2. 使用原则:私有动态数据用 state,外部传入 / 展示数据用 props,跨组件共享状态需提升 state 或用进阶方案;
  3. 避坑关键:不直接修改 props/state 原数据,依赖 state 最新值时用函数式更新,遵循单向数据流。

记住核心公式 UI = f(props, state),就能理解:React 组件的所有动态变化,本质都是 props 和 state 的变化驱动 UI 重新渲染。

相关推荐
西部荒野子2 小时前
1. 建立源码地图
前端
西部荒野子2 小时前
3.RCTRootView 加载 Bundle 流程
前端
西部荒野子2 小时前
2.iOS 启动到 RCTRootView
前端
scan7242 小时前
SystemMessage,HumanMessage,AIMessage,ToolMessage
开发语言·前端·javascript
AI_零食2 小时前
鸿蒙PC Electron跨平台应用开发:辗转相除法计算器实现详解
前端·学习·华为·electron·开源·鸿蒙·鸿蒙系统
rising start2 小时前
二、Vue3 核心基础:API 对比、Setup 与响应式详解
前端·javascript·vue.js
ofoxcoding2 小时前
MiniMax M3 实测手记:踩完坑之后,我总结了报错处理和省 token 的几个办法
java·前端·人工智能·ai
YG亲测源码屋3 小时前
html表白代码大全可复制免费 html表白网页制作源码
前端·html
夜雪闻竹3 小时前
React Query + REST API 最佳实践
前端·react.js·前端框架