React 10

1 修改组件纯粹的练习

javascript 复制代码
export default function StoryTray({ stories }) {
  stories.push({
    id: 'create',
    label: 'Create Story'
  });

  return (
    <ul>
      {stories.map(story => (
        <li key={story.id}>
          {story.label}
        </li>
      ))}
    </ul>
  );
}

这个 StoryTray 组件不是纯粹的 ,核心问题在于它直接修改了传入的 props.stories 数组,违反了 React 中 "props 是只读的" 原则,会产生不可预测的副作用。

一、组件不纯粹的关键原因

1. 直接修改 props,产生副作用

stories.push(...) 这行代码是在直接修改父组件传入的 props.stories 数组push 方法会改变原数组)。React 明确规定:props 是只读的,组件绝不能修改自身接收的 props。原因是:

  • props 本质上是父组件传递给子组件的数据,子组件修改 props 会导致 "父组件状态与子组件操作不一致"(比如父组件不知道子组件偷偷改了数据)。
  • 若父组件因其他原因重新渲染,传入的 stories 可能被重置,导致子组件的修改丢失;反之,子组件的修改也可能意外影响父组件的其他逻辑(比如父组件中其他使用 stories 的地方)。
2. 同输入可能不同输出,违反确定性

假设父组件传入的 stories[{ id: '1', label: 'Story 1' }]

  • 第一次渲染时,stories.push(...) 会给原数组增加一个元素,组件返回包含 2 个元素的列表。
  • 若父组件重新渲染(比如其他状态变化),再次传入相同的初始 storiespush 会再次执行,数组会变成 3 个元素,导致输出结果不同。

这种 "相同输入却产生不同输出" 的情况,完全违背了纯函数的确定性原则。

二、优化方案:创建新数组,不修改原 props

解决办法是基于原 props.stories 创建一个新数组 ,在新数组上添加元素,避免修改原 props。优化后的代码如下:

javascript 复制代码
export default function StoryTray({ stories }) {
  // 创建新数组:复制原 stories 中的元素,再添加新元素(不修改原 props)
  const allStories = [
    ...stories,  // 复制原数组的所有元素
    { id: 'create', label: 'Create Story' }  // 添加新元素
  ];

  return (
    <ul>
      {allStories.map(story => (  // 使用新数组渲染
        <li key={story.id}>
          {story.label}
        </li>
      ))}
    </ul>
  );
}

三、优化后组件的纯粹性验证

  1. 无副作用 :不再修改原 props.stories,而是通过扩展运算符(...)复制原数组并创建新数组,保证了 props 的只读性,没有影响外部状态。

  2. 输入输出确定 :只要传入的 stories 相同,allStories 的计算结果就完全相同,渲染的列表也必然一致,符合 "相同输入 → 相同输出" 的纯函数特性。

  3. 数据流向清晰 :子组件仅使用父组件传入的 props,不修改它,避免了父子组件状态冲突,逻辑可预测。

四、关键结论

React 中,props 的只读性是保证组件纯粹性和数据流清晰的核心原则。修改 props 就像 "偷偷修改别人借给你的东西",会导致整个应用状态混乱。正确的做法是:对于需要基于 props 加工的数据,始终创建新的副本(如新数组、新对象),而非直接修改原 props。这一点在处理数组和对象类型的 props 时尤其重要(因为它们是引用类型,修改会影响原数据)。

2 Javascript的push方法

Array.prototype.push() - JavaScript | MDN

push 是 JavaScript 中数组(Array)的内置方法 ,用于向数组的末尾添加一个或多个元素,并且会直接修改原数组(这也是它在 React 组件中容易引发问题的原因)。

一、push 方法的基础用法

语法:数组.push(元素1, 元素2, ...)作用:给原数组添加新元素,返回值是添加后数组的长度。

示例:

javascript 复制代码
const fruits = ['苹果', '香蕉'];
// 向数组末尾添加元素
const newLength = fruits.push('橙子', '葡萄');

console.log(fruits); // 输出:['苹果', '香蕉', '橙子', '葡萄'](原数组被修改了)
console.log(newLength); // 输出:4(添加后数组的长度)

二、为什么在 React 组件中用 push 会出问题?

因为 push直接修改原数组 (产生 "副作用"),而 React 要求组件接收的 props 必须是 "只读的"(不能被修改)【非常形象了!!!!!】。

在你之前的 StoryTray 组件中:

javascript 复制代码
// 传入的 props.stories 是一个数组
stories.push(/* 新元素 */); // 直接修改了原数组,违反了 props 只读原则

这会导致父组件中的原数组被意外修改,引发数据不一致的问题(比如父组件再次使用这个数组时,内容已经被子组件篡改了)。

三、React 中正确处理数组的方式

如果需要基于原数组添加元素,应该创建新数组(不修改原数组),常用方法有:

  1. 扩展运算符(...(推荐):

    javascript 复制代码
    // 基于原数组创建新数组,并添加新元素
    const newArray = [...原数组, 新元素1, 新元素2];
  2. concat 方法

    javascript 复制代码
    // concat 会返回新数组,原数组不变
    const newArray = 原数组.concat(新元素1, 新元素2);

举例(对应之前的组件优化):

javascript 复制代码
// 原数组:stories
// 正确方式:创建新数组,不修改原数组
const allStories = [...stories, { id: 'create', label: 'Create Story' }];

总结

push 是 JavaScript 数组的原生方法,功能是 "给原数组添加元素并修改它"。但在 React 中,由于 props 必须只读,直接对 props 中的数组使用 push 会导致副作用,破坏组件纯粹性。正确的做法是通过扩展运算符或 concat 等方式创建新数组,既满足需求又不修改原数据,这也是 React 中处理数组的最佳实践。

3 渲染树中的 HTML 标签在哪里

UIView | Apple Developer Documentation

https://learn.microsoft.com/en-us/dotnet/api/system.windows.frameworkelement?view=windowsdesktop-7.0

4 React摘要

一、渲染树

  1. 基本定义 树结构常用于表示实体间关系,在 UI 建模中很常见。React 的渲染树专门表示单次渲染过程中 React 组件之间的嵌套关系 ,比如一个页面由Header组件、Content组件和Footer组件组成,这三个组件在渲染树中就呈现出以页面根组件为父,它们为子的嵌套结构。
  2. 动态变化性 由于 React 的条件渲染 机制,渲染树在不同渲染过程中可能发生变化。例如一个组件ConditionalComponent,当属性isShowtrue时渲染ChildA组件,为false时渲染ChildB组件,那么在不同属性值下,渲染树的子组件结构就会不同。
  3. 性能调试价值 渲染树能帮助识别顶级组件叶子组件
    • 顶级组件:处于渲染树上层的组件,其渲染性能会影响其下所有子组件的渲染。比如一个负责数据获取和全局状态管理的顶级组件,如果其渲染逻辑过于复杂,会导致整个页面的渲染性能下降。
    • 叶子组件:处于渲染树末端的组件,通常会频繁重新渲染(比如展示实时数据的组件)。识别这两类组件,能帮助开发者理解和调试渲染性能问题,比如对频繁渲染的叶子组件做性能优化(如使用React.memo),对影响范围大的顶级组件优化其渲染逻辑。

二、依赖树

  1. 基本定义 依赖树表示React 应用程序中的模块依赖关系 ,即一个模块(或组件)依赖于哪些其他模块。例如一个UserList组件依赖于UserService模块来获取用户数据,UserService又依赖于HttpUtil模块来发送网络请求,这些依赖关系就构成了依赖树。
  2. 构建工具的作用 构建工具(如 Webpack、Vite 等)会利用依赖树来捆绑必要的代码以部署应用程序。它会分析依赖树,找出哪些模块是应用运行所必需的,然后将这些模块的代码打包成一个或多个文件,避免打包冗余代码。
  3. 性能优化价值 依赖树有助于调试大型捆绑包带来的渲染速度过慢问题,还能发现哪些捆绑代码可以被优化。比如通过依赖树发现某个未使用的第三方库被错误地打包进了应用,就可以将其从依赖中移除,减小捆绑包体积,提升渲染速度;或者发现某些模块被多个地方重复依赖,可通过代码分割等方式优化。
相关推荐
LEEBELOVED3 小时前
R语言高效数据处理-3个自定义函数笔记
开发语言·笔记·r语言
朝新_3 小时前
【SpringMVC】SpringMVC 请求与响应全解析:从 Cookie/Session 到状态码、Header 配置
java·开发语言·笔记·springmvc·javaee
Moment3 小时前
记录一次修改 PNPM 版本,部署 NextJs 服务时导致服务器崩溃的问题 😡😡😡
前端·javascript·后端
浪裡遊3 小时前
css面试题1
开发语言·前端·javascript·css·vue.js·node.js
钮钴禄·爱因斯晨3 小时前
不只是字符串:Actix-web 路由与 FromRequest的类型安全艺术
前端·安全
喜欢吃燃面3 小时前
C++:红黑树
开发语言·c++·学习
兔兔爱学习兔兔爱学习3 小时前
LangChain4j学习一:聊天和语言模型
人工智能·学习·语言模型
杜子不疼.3 小时前
仓颉语言构造函数深度实践指南
java·服务器·前端
IT_陈寒3 小时前
我用这5个JavaScript性能优化技巧,让页面加载速度提升了60%
前端·人工智能·后端