React 08
1 map遍历数组

map 是 JavaScript 中数组的一个常用方法,作用是遍历数组中的每个元素,对它们进行处理后,返回一个新的数组。它不会改变原数组,非常适合在 React 中动态生成列表(比如你之前看到的化学家列表)。
举个简单的例子理解 map:
假设你有一个数组 [1, 2, 3],想把每个数字都乘以 2,得到 [2, 4, 6],用 map 可以这样写:
const numbers = [1, 2, 3];
// 用 map 遍历每个元素,返回新数组
const doubled = numbers.map(function(number) {return number * 2; // 对每个元素做处理(乘以 2)
});
console.log(doubled); // 输出 [2, 4, 6]
map 的核心用法:
数组.map(function(当前元素, 索引, 原数组) {// 对当前元素进行处理return 处理后的结果; // 这个结果会组成新数组
});
- 参数 1(必填):当前正在遍历的元素(比如上面例子中的
1、2、3)。 - 返回值:必须有
return,否则新数组会是[undefined, undefined, ...]。
在 React 中为什么常用 map?
因为 React 经常需要根据数组数据动态生成 UI 元素(比如列表项 <li>)。比如你有一个化学家数组 chemists,每个元素是一个对象(包含 name、achievement 等属性),用 map 可以快速把每个对象转换成一个 <li>:
const chemists = [{ name: '居里夫人', achievement: '发现镭' },{ name: '门捷列夫', achievement: '发明元素周期表' }
];// 用 map 把每个化学家对象转成 <li>
const listItems = chemists.map(person => {return (<li key={person.name}><h3>{person.name}</h3><p>成就:{person.achievement}</p></li>);
});// 最后把 listItems 渲染到页面
return <ul>{listItems}</ul>;
这样就会生成一个包含两位化学家信息的列表,比手动写两个 <li> 更灵活(如果数组有 100 个元素,map 也能一键处理)。
记住两个关键点:
map会返回一个新数组,原数组不变。- React 中用
map生成列表时,每个元素需要加一个唯一的key属性(比如上面的key={person.name}),帮助 React 高效更新 UI。
只有数组能直接调用 map,但其他数据结构可以通过 “转数组” 的方式间接使用 map 的逻辑
map 方法是数组的专属方法,只有数组类型可以直接调用 map。不过,如果你想对其他数据结构(比如对象、字符串等)实现类似 “遍历并转换” 的效果,可以通过一些方式间接实现:
1. 对象的 “类 map” 处理
对象本身没有 map 方法,但可以通过 Object.keys()、Object.values() 或 Object.entries() 先把对象转成数组,再用 map:
const person = { name: '小明', age: 20, job: '程序员' };// 遍历对象的“键”
const keys = Object.keys(person).map(key => key);
console.log(keys); // 输出 ['name', 'age', 'job']// 遍历对象的“值”
const values = Object.values(person).map(value => value);
console.log(values); // 输出 ['小明', 20, '程序员']// 遍历对象的“键值对”
const entries = Object.entries(person).map(([key, value]) => `${key}: ${value}`);
console.log(entries); // 输出 ['name: 小明', 'age: 20', 'job: 程序员']
2. 字符串的 “类 map” 处理
字符串也没有 map 方法,但可以先转成数组(用 split('') 拆成单个字符),再用 map:
const str = 'hello';
const newStrArr = str.split('').map(char => char.toUpperCase());
console.log(newStrArr.join('')); // 输出 'HELLO'
3. 其他可迭代对象(如 Set、Map)
ES6 中的 Set(集合)、Map(映射)是可迭代对象,可以通过扩展运算符 ... 转成数组后用 map:
const mySet = new Set([1, 2, 3]);
const setArr = [...mySet].map(num => num * 2);
console.log(setArr); // 输出 [2, 4, 6]const myMap = new Map([['a', 1], ['b', 2]]);
const mapArr = [...myMap.values()].map(num => num + 1);
console.log(mapArr); // 输出 [2, 3]
总结:只有数组能直接调用 map,但其他数据结构可以通过 “转数组” 的方式间接使用 map 的逻辑,从而实现遍历和转换的效果。
2 箭头函数
箭头函数表达式 - JavaScript | MDN
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Arrow_functions#%E6%8F%8F%E8%BF%B0这个语法是jsx吗,箭头=>有点没看懂
const chemists = [{ name: '居里夫人', achievement: '发现镭' },{ name: '门捷列夫', achievement: '发明元素周期表' }
];// 用 map 把每个化学家对象转成 <li>
const listItems = chemists.map(person => {return (<li key={person.name}><h3>{person.name}</h3><p>成就:{person.achievement}</p></li>);
});// 最后把 listItems 渲染到页面
return <ul>{listItems}</ul>;
是的,这段代码里的 <li>、<h3> 等标签写法就是 JSX(JavaScript XML),它是 React 中用于描述 UI 的语法扩展,允许在 JavaScript 中直接写 HTML 风格的标签。
而你提到的 => 是 JavaScript 中的 箭头函数(Arrow Function),是一种更简洁的函数写法,在 React 中非常常用。我们结合你的例子具体解释:
1. 先理解箭头函数 =>
原来的普通函数写法是这样的:
// 普通函数
function(参数) {// 函数体return 结果;
}
箭头函数可以简化成:
// 箭头函数(去掉 function,参数后加 =>)
(参数) => {// 函数体return 结果;
}
如果只有一个参数,甚至可以省略括号:
参数 => {// 函数体return 结果;
}
2. 结合你的例子看
代码中的 chemists.map(person => { ... }) 里,person => { ... } 就是一个箭头函数,作为 map 方法的参数(回调函数)。
它等价于用普通函数写:
chemists.map(function(person) { // 普通函数写法return (<li key={person.name}><h3>{person.name}</h3><p>成就:{person.achievement}</p></li>);
});
箭头函数在这里的作用和普通函数完全一样:接收 map 遍历到的当前元素(这里是 person,即每个化学家对象),然后返回一个处理后的结果(这里是 JSX 元素 <li>)。
3. 为什么 React 中常用箭头函数?
- 写法更简洁,尤其在
map、setTimeout等需要传匿名函数的场景下,代码更清爽。 - 箭头函数没有自己的
this,它的this继承自外层作用域,在 React 组件中能避免一些this指向的问题(后续学组件时会更有体会)。
总结:
<li>这些标签是 JSX,让你能在 JS 中写 UI 结构。=>是 箭头函数,是简化的函数写法,这里作为map的回调,处理每个数组元素并返回 JSX。
3 key
在 React 中,key 是一个特殊的属性,主要作用是帮助 React 高效地区分和管理列表中的元素,从而优化渲染性能、避免 bug。具体可以从以下几个维度理解:
1. 核心作用:给 React 一个 “元素身份标识”
当列表中的元素发生变化(新增、删除、重新排序)时,React 需要判断哪些元素是 “旧的”、哪些是 “新的”。key 就像元素的 “身份证”,让 React 能快速识别:
- 如果两个元素的
key相同,React 认为它们是同一个元素,只会更新其内容(而不是销毁再重建)。 - 如果
key不同,React 会认为是新元素,需要创建;或旧元素被删除,需要销毁。
2. 解决的问题:避免性能浪费和状态异常
如果不写 key(或用了不合适的 key,比如数组索引),React 会默认用索引当 key,这在列表顺序变化时(如插入、删除)会引发问题:
-
性能问题:假设列表是
[A, B, C],后来变成[B, A, C]。如果用索引当key,React 会误判 “第一个元素还是 A”(因为索引 0 没变),但实际 A 已经移动到第二个位置了。这会导致 React 重复创建 / 销毁元素,浪费性能。 -
状态异常问题:比如列表项包含用户输入(如输入框),如果
key不稳定,React 会把旧元素的状态错误地绑定到新元素上,导致用户输入 “串位” 或丢失。
3. 正确使用 key 的原则
- 唯一性:列表中每个元素的
key必须唯一(不能重复)。 - 稳定性:
key要从数据本身获取(如数据的id),不能动态生成(如Math.random())或依赖临时状态(如数组索引)。
示例对比:有 key vs 无 key
// 正确用法:用数据的唯一 id 当 key
{recipes.map(recipe => (<div key={recipe.id}><h2>{recipe.name}</h2></div>
))}// 错误用法1:用数组索引当 key(列表顺序变化时会出问题)
{recipes.map((recipe, index) => (<div key={index}><h2>{recipe.name}</h2></div>
))}// 错误用法2:动态生成 key(每次渲染 key 都变,性能极差)
{recipes.map(recipe => (<div key={Math.random()}><h2>{recipe.name}</h2></div>
))}
总结来说,key 是 React 优化列表渲染的 “关键工具”—— 通过给每个元素一个稳定、唯一的标识,让 React 能精准识别元素的变化,从而高效更新 UI、避免各种隐藏 bug。
4 React 中使用 key 属性时需要避免的陷阱和正确做法

-
避免用数组索引作为 key虽然 React 会默认把数组项的索引当作 key,但当数组发生插入、删除、重新排序等操作时(即数组项顺序改变),使用索引作为 key 可能会导致难以察觉的 bug。因为 React 依赖 key 来识别元素的稳定性,索引变化会让 React 误判元素的身份,进而影响渲染效率或导致状态异常。
-
禁止动态生成 key(如随机值)像
key={Math.random()}这类动态生成 key 的方式是不可取的。每次重新渲染时 key 都会变化,这会迫使 React 销毁并重新创建所有相关组件和 DOM 元素,不仅降低性能,还可能导致用户输入(如表单内容)丢失。 -
key 的正确来源应使用从数据中稳定获取的值作为 key(比如数据本身包含的唯一 ID),确保 key 在元素的生命周期内保持不变。
-
key 与 props 的区别key 是 React 内部使用的标识,不会作为 props 传递给组件。如果组件需要用到 ID 信息,需单独通过 props 传递(如
<Profile key={id} userId={id} />)。
总结来说,核心是要保证 key 的唯一性和稳定性,这样才能让 React 高效、正确地管理组件和 DOM 元素的更新。
5 分析渲染列表的组件
import { recipes } from './data.js';export default function RecipeList() {return (<div><h1>菜谱</h1>{recipes.map(recipe =><div key={recipe.id}><h2>{recipe.name}</h2><ul>{recipe.ingredients.map(ingredient =><li key={ingredient}>{ingredient}</li>)}</ul></div>)}</div>);
}
这段代码是一个 React 组件,用于展示菜谱列表,包含了 React 中的核心概念(组件、JSX、列表渲染、key 属性等)。我们一步步拆解,确保每个细节都讲清楚:
1. 整体结构:一个 React 组件
// 导入外部数据
import { recipes } from './data.js';// 定义并导出一个名为 RecipeList 的组件
export default function RecipeList() {return (// JSX 结构:组件要渲染的内容<div>...</div>);
}
- 组件:
RecipeList是一个 “函数组件”(React 中最基础的组件形式),本质是一个返回 UI 结构的函数。 - 导出:
export default表示这个组件可以被其他文件导入使用(比如在 App 组件中引入<RecipeList />来显示)。
2. 导入数据:import { recipes } from './data.js'
- 从当前目录的
data.js文件中,导入了一个名为recipes的数组(假设data.js里存储了菜谱数据,格式类似下面这样):// 假设 data.js 中的内容 export const recipes = [{id: 1,name: "番茄炒蛋",ingredients: ["番茄", "鸡蛋", "盐", "糖"]},{id: 2,name: "凉拌黄瓜",ingredients: ["黄瓜", "蒜末", "醋", "生抽"]} ]; recipes是一个包含多个 “菜谱对象” 的数组,每个对象有id(唯一标识)、name(菜谱名称)、ingredients(食材数组)三个属性。
3. 返回的 JSX 结构:组件的 UI 内容
JSX 是 React 特有的语法,允许在 JavaScript 中直接写 HTML 标签,最终会被转换为浏览器能理解的 JavaScript 代码。
整个返回的结构可以拆分为三层:
第一层:最外层容器 <div>
<div><h1>菜谱</h1>{/* 这里会动态渲染菜谱列表 */}
</div>
- 最外层必须有一个容器(这里用
<div>),因为 React 组件返回的 JSX 必须是 “单一根元素”(不能并列返回多个标签)。 <h1>菜谱</h1>是静态标题,固定显示 “菜谱” 二字。
第二层:遍历 recipes 数组,渲染每个菜谱
{recipes.map(recipe =><div key={recipe.id}><h2>{recipe.name}</h2>{/* 这里会动态渲染当前菜谱的食材列表 */}</div>
)}
这部分是 “动态渲染列表” 的核心,用 map 方法遍历 recipes 数组,把每个菜谱对象转换成对应的 UI 元素:
-
recipes.map(...):map遍历数组recipes,每次遍历拿到一个元素recipe(即单个菜谱对象,比如上面例子中的 “番茄炒蛋” 对象)。 -
箭头函数
recipe => { ... }:作为map的回调函数,接收当前菜谱对象recipe,并返回一个 JSX 结构(每个菜谱对应的 UI)。 -
key={recipe.id}:React 要求列表中的每个元素必须有唯一的key属性(用于优化渲染性能,避免重复创建 / 删除元素)。这里用recipe.id作为key,因为id是每个菜谱的唯一标识(比如 1、2),符合 “唯一性” 和 “稳定性” 要求。 -
<h2>{recipe.name}</h2>:用{ }嵌入 JavaScript 表达式,显示当前菜谱的名称(比如 “番茄炒蛋”)。{ }是 JSX 中嵌入变量 / 表达式的语法。
第三层:遍历 ingredients 数组,渲染食材列表
<ul>{recipe.ingredients.map(ingredient =><li key={ingredient}>{ingredient}</li>)}
</ul>
每个菜谱对象里有 ingredients 数组(比如 ["番茄", "鸡蛋"]),这里再用一次 map 遍历这个数组,渲染食材列表:
-
recipe.ingredients.map(...):遍历当前菜谱的ingredients数组,每次拿到一个元素ingredient(即单个食材,比如 “番茄”)。 -
key={ingredient}:同样需要key,这里直接用食材名称作为key(假设同一菜谱的食材不重复,满足唯一性)。如果食材可能重复(比如 “盐” 在多个步骤出现),更好的做法是给食材也加唯一id(如{ id: 1, name: "盐" }),再用key={ingredient.id}。 -
<li>{ingredient}</li>:用<li>标签显示每个食材,{ingredient}嵌入食材名称(比如 “鸡蛋”)。
4. 最终渲染效果
假设 data.js 中的 recipes 数组是前面举例的内容,那么这个组件会渲染出:
<div><h1>菜谱</h1><!-- 第一个菜谱 --><div><h2>番茄炒蛋</h2><ul><li>番茄</li><li>鸡蛋</li><li>盐</li><li>糖</li></ul></div><!-- 第二个菜谱 --><div><h2>凉拌黄瓜</h2><ul><li>黄瓜</li><li>蒜末</li><li>醋</li><li>生抽</li></ul></div>
</div>
核心知识点总结
- 函数组件:
function RecipeList() { return ... }定义了一个可复用的 UI 组件。 - JSX 语法:允许在 JS 中写 HTML 标签,用
{ }嵌入变量 / 表达式。 - 列表渲染:用
array.map()遍历数组,动态生成多个相同结构的 UI 元素。 - key 属性:列表元素必须有唯一
key,用于 React 优化渲染,通常用数据中的唯一 ID。 - 数据驱动 UI:UI 结构由
recipes数组的数据决定,当数据变化时,UI 会自动更新(这是 React 的核心特性)。
如果 data.js 中的菜谱数据增加、删除或修改,这个组件会自动同步显示最新的内容,无需手动修改 UI 代码 —— 这就是 React “数据驱动” 的优势。
6 Fragment 语法
在 React 中,Fragment(片段) 是一种语法,用于在不添加额外 DOM 节点的前提下,将多个元素组合在一起。
作用
当组件需要返回多个并列的元素时,按照 JSX 规则,必须用一个父元素(如 <div>)包裹。但有时我们不想添加多余的 <div> 节点(避免影响 DOM 结构和样式),这时就可以用 Fragment 来 “包裹” 这些元素,同时不产生额外的 DOM 层级。
语法形式
1. 显式写法(<React.Fragment>)
import React from 'react';function MyComponent() {return (<React.Fragment><h1>标题</h1><p>内容</p></React.Fragment>);
}
2. 简写语法(<> 和 </>)
这是更常用的简洁写法,效果和 <React.Fragment> 完全一致:
function MyComponent() {return (<><h1>标题</h1><p>内容</p></>);
}
与普通容器的区别
| 场景 | 使用 <div> 包裹 | 使用 Fragment 包裹 |
|---|---|---|
| DOM 结构 | 会新增一个 <div> 节点 | 无额外 DOM 节点 |
| 样式影响 | 可能因 <div> 触发布局问题 | 不影响原有样式结构 |
| 使用场景 | 需要给多个元素加统一样式 / 逻辑时 | 仅需组合元素,无额外 DOM 需求时 |
例如,在之前的菜谱列表中,如果不想给每个菜谱外层加 <div>,可以用 Fragment 优化:
{recipes.map(recipe => (<><h2 key={recipe.id}>{recipe.name}</h2><ul>{recipe.ingredients.map(ingredient =><li key={ingredient}>{ingredient}</li>)}</ul></>
))}
这样每个菜谱的 h2 和 ul 就不会被额外的 <div> 包裹,DOM 结构更纯净。
【这不是js的语法吗】
Fragment 并不是原生 JavaScript 的语法,而是 React 特有的语法扩展,专门用于解决 JSX 中 “多元素必须有唯一父节点” 的问题。
具体来说:
- 原生 JavaScript 中并没有
<>或<React.Fragment>这样的语法,它们是 React 团队为了简化 JSX 写法设计的。 - JSX 本身也不是原生 JavaScript 语法(浏览器无法直接识别
<div>这种在 JS 里写标签的形式),需要通过 Babel 等工具编译成普通 JS 代码才能运行。而Fragment作为 JSX 的一部分,自然也依赖 React 的编译处理。
举个编译后的例子你就明白了:
// 你写的 Fragment 代码
return <><h1>标题</h1><p>内容</p>
</>
会被 Babel 编译成 React 能处理的普通函数调用:
return React.createElement(React.Fragment, // 注意这里:Fragment 本质是 React 提供的一个特殊组件null,React.createElement("h1", null, "标题"),React.createElement("p", null, "内容")
);
可以看到,<> 最终会被转换成 React 提供的 React.Fragment 组件 —— 这完全是 React 框架内部的机制,和原生 JavaScript 语法无关。
总结:Fragment 是 React 为了优化 JSX 结构设计的语法,属于 React 生态的一部分,而非原生 JavaScript 语法。它的存在是为了让开发者在写 JSX 时更灵活,同时避免多余的 DOM 节点。
