5.React 为什么如此强调 Props 的不可变性? 副本 来源:https://my.feishu.cn/docx/NUTJdTxupogzEvxJEu9cqvKJnHd 采集状态:已完成正文采集;每次约0.55屏连续滚动到文末,按block id和代码行号累积;代码行号连续。 在 React 的世界里,有一个原则我们很早就被告知:**Props 是只读的,绝不能直接修改**。这就像一条不容逾越的红线,贯穿于我们组件设计的始终。 但我们是否曾停下来思考过,这背后的深层原因是什么?为什么 React 如此“固执”地坚持这一原则?它仅仅是一条编码规范,还是支撑起整个 React 设计哲学的基石? 实际上,Props 的不可变性并非一条孤立的规则,它与 React 的核心理念——单向数据流、性能优化和可预测性紧密相连。理解了这一点,我们才能真正理解 React 的工作方式。 1. 维护单向数据流:让应用状态可预测 我们可以将 React 组件看作一个纯函数,其哲学可以概括为 UI = f(state, props)。给定相同的输入(state 和 props),它总是能渲染出相同的输出(UI)。 Props 是父组件传递给子组件的数据,构成了自上而下的数据流。如果子组件可以随意修改从父组件接收到的 props,会发生什么? 设想一下,一个父组件将一个数据对象 user 传给了三个子组件:Avatar、UserName 和 EditProfileButton。如果 EditProfileButton 组件直接修改了 user.name,那么 UserName 组件也会在用户未做任何操作的情况下突然更新。更糟糕的是,父组件和其他兄弟组件对此毫不知情。 这种从子组件“逆流而上”的意外修改,会彻底破坏数据的单一来源(Single Source of Truth),让整个应用的数据流变得混乱且无法追踪。当应用出现问题时,我们很难定位是哪个组件在何时何处修改了数据。 通过强制 Props 的不可变性,React 确保了数据总是自上而下地单向流动。**子组件不持有数据的“所有权”,只拥有“使用权”**。这让整个应用的状态变化变得清晰、可预测且易于管理。 2. 性能优化的基石:从“比较”到“判断” React 的核心工作之一是在数据变更时,高效地更新 DOM。为了避免不必要的渲染,React 需要判断一个组件的 props 或 state 是否真的发生了变化。 • 当 props 可变时:如果允许直接修改 props 对象内部的属性(例如 props.user.name = 'New Name'),props 对象本身的引用地址并未改变。为了检测到这个变化,React 将被迫进行“深比较”(Deep Comparison),即递归地遍历对象的所有属性,逐一对比新旧值的差异。这是一个非常耗费性能的操作,尤其是在 props 结构复杂时。 • 当 props 不可变时:当我们遵循不可变原则时,任何数据的变更都不会在原对象上进行,而是会创建一个全新的对象。 // 错误的做法:直接修改 props.user.name = 'New Name'; // 引用地址不变 // 正确的做法:创建新对象 const newUser = { ...props.user, name: 'New Name' }; // 引用地址改变 在这种模式下,React 的优化策略变得异常简单高效。它不再需要进行昂贵的深比较,只需通过 === 运算符进行“浅比较”(Shallow Comparison),判断 props 对象的引用地址是否变更即可。 如果引用地址没有变,React 就自信地认为数据没有变,从而跳过整个组件的渲染过程。 React.memo 和 PureComponent 等性能优化工具,其工作原理正是建立在对 props 的浅比较之上。可以说,Props 的不可变性是 React 高性能更新策略的根本前提。 3. 如果确实需要“修改”:状态(State)登场 当然,实际开发中我们确实会遇到需要基于传入的 props 进行后续修改的场景,比如一个表单组件,用 props 来接收初始值,然后允许用户编辑。 对于这种情况,React 提供了明确的解决方案:将 props 的值作为组件内部状态(State)的初始值。 一个组件应该这样管理自己的数据: • Props: 用于接收来自父组件的、自身无法改变的数据。 • State: 用于管理组件内部的、随时间变化、可由用户交互等引起改变的数据。 让我们看一个例子: function UserProfile({ initialName }) { // ✅ 正确做法:将 prop 作为 state 的初始值 const [name, setName] = useState(initialName); const handleNameChange = (event) => { setName(event.target.value); }; const handleSave = () => { // ❌ 错误做法:永远不要直接修改 props // initialName = name; // 在这里,我们可以调用一个从父组件传来的函数, // 将更新后的 name “通知”给父组件 // onSave(name); }; return (