普通视图

发现新文章,点击刷新页面。
昨天 — 2026年5月4日首页

reeact虚拟DOM、Diff算法原理、key的作用与为什么不能用index

作者 光影少年
2026年5月4日 10:23

一、虚拟 DOM(Virtual DOM)

1. 是什么

虚拟 DOM 本质就是一个 JS 对象,用来描述真实 DOM 结构。

例如 JSX:

<div className="box">
  <span>Hello</span>
</div>

会被转换成类似:

{
  type: 'div',
  props: {
    className: 'box',
    children: [
      {
        type: 'span',
        props: {
          children: 'Hello'
        }
      }
    ]
  }
}

2. 为什么要有虚拟 DOM?

核心目的:减少真实 DOM 操作

因为:

  • 真实 DOM 操作成本高(重排 / 重绘)
  • JS 计算相对便宜

👉 所以 React 做了一层“中间层”:

状态变化 → 生成新的虚拟DOM → Diff → 最小化更新真实DOM

二、Diff 算法原理

React 的 Diff 不是传统树算法(O(n³)),而是做了优化 → O(n)

核心基于 3 个假设:


1️⃣ 同层比较(不跨层)

👉 React 只比较同一层节点,不会跨层移动

例如:

A
 ├─ B
 └─ C

如果变成:

A
 └─ B
     └─ C

React 会认为:

  • C 被删除
  • 新建一个 C

❗不会复用

👉 这是用空间换时间


2️⃣ 类型不同直接替换

<div />
→
<span />

👉 直接销毁旧节点,创建新节点


3️⃣ 列表使用 key 优化

👉 这是重点(和你下面的问题强相关)


三、key 的作用

本质作用:

👉 标识节点的唯一身份

让 React 在 Diff 时可以:

✔ 复用节点
✔ 只更新变化的部分
✔ 避免错误复用


举例

旧列表:

[{id:1}, {id:2}, {id:3}]

新列表:

[{id:3}, {id:1}, {id:2}]

❌ 没有 key(或用 index)

React 会按位置比较:

旧: 1 2 3
新: 3 1 2

👉 结果:

  • 全部节点都被认为变了
  • 全部重新渲染

✅ 使用 key

key: 1 2 3key: 3 1 2

React 会:

👉 发现只是“顺序变了”
👉 复用节点,只移动 DOM


四、为什么不能用 index 作为 key?

很多人背这个结论,但不理解原因。

核心问题:index 不是稳定标识


场景 1:插入元素

旧:

[A, B, C]
key: 0 1 2

新(头部插入 D):

[D, A, B, C]
key: 0 1 2 3

React 看到的是:

0 → 新 0A → D ❌)
旧 1 → 新 1BA ❌)
旧 2 → 新 2 (C → B ❌)

👉 全错位


后果:

  • 组件状态错乱(最严重问题
  • 输入框内容串位
  • 动画异常

场景 2:删除元素

[A, B, C]
→
[A, C]

index 变化:

B 被删 → C 的 index 从 21

👉 React 误以为:

  • B → C(复用错误)

场景 3:表单输入(经典面试题)

<input value="A" />
<input value="B" />

删除第一个后:

👉 B 会变成 A(错位)


五、什么时候可以用 index?

不是绝对不能用,而是有条件

👉 满足以下条件可以用:

  • 列表不会发生顺序变化
  • 没有插入 / 删除
  • 只是静态展示

例如:

[1,2,3].map((item, index) => <li key={index}>{item}</li>)

✔ 安全


六、总结(面试版)

你可以这样说:

React 通过虚拟 DOM 来减少真实 DOM 操作,在状态更新时生成新的虚拟 DOM,然后通过 Diff 算法进行对比。
Diff 采用同层比较策略,并通过 key 来标识节点,提高复用效率。
key 的作用是帮助 React 识别节点是否可复用,如果使用 index 作为 key,在列表发生插入、删除、排序时会导致节点错位,可能引发状态错乱,因此不推荐使用。


七、给你一个更进阶的理解(加分项)

👉 React Diff 本质:

不是找“最优解”,而是找“足够快的近似解

👉 核心 trade-off:

精确性 ↓
性能 ↑
昨天以前首页

Polyline 组件如何绘制渐变区域?

作者 光影少年
2026年4月26日 17:07

✅ 方案一:用 Polygon 替代 Polyline(最推荐)

如果你是想做“线下方渐变”(类似折线图面积图),可以:

  1. 把 Polyline 的点复制一份
  2. 补齐底部闭合路径
  3. Polygon 填充渐变

示例(以高德地图 JS API 为例)

const path = [
  [116.3, 39.9],
  [116.4, 39.8],
  [116.5, 39.85],
];

// 构造闭合区域(补到底部)
const polygonPath = [
  ...path,
  [116.5, 39.7],
  [116.3, 39.7],
];

const polygon = new AMap.Polygon({
  path: polygonPath,
  fillColor: 'rgba(0, 0, 255, 0.5)', // 基础色
  fillOpacity: 0.5,
});

👉 渐变实现:
高德原生不支持渐变填充,但你可以:

  • CanvasLayer 自绘渐变
  • 或使用 自定义覆盖物

✅ 方案二:CanvasLayer + 渐变(高级玩法)

如果你需要真正的渐变(linear-gradient),可以:

const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');

// 创建渐变
const gradient = ctx.createLinearGradient(0, 0, 0, 200);
gradient.addColorStop(0, 'rgba(0,0,255,0.8)');
gradient.addColorStop(1, 'rgba(0,0,255,0)');

ctx.fillStyle = gradient;

// 画路径(类似 polygon)
ctx.beginPath();
ctx.moveTo(...);
ctx.lineTo(...);
ctx.fill();

然后通过:

new AMap.CanvasLayer({
  canvas: canvas,
  bounds: ...
});

👉 优点:

  • 完全自定义渐变方向/颜色
  • 可做动态效果

👉 缺点:

  • 需要自己处理坐标转换(经纬度 → 像素)

✅ 方案三:ECharts(如果你是数据可视化场景)

你之前用过 ECharts,这个其实最简单:

series: [{
  type: 'line',
  data: [...],
  areaStyle: {
    color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
      { offset: 0, color: 'blue' },
      { offset: 1, color: 'transparent' }
    ])
  }
}]

👉 直接支持渐变区域,不用自己画


✅ 方案四:Polyline + 多条叠加(伪渐变)

如果不想用 Canvas,可以:

  • 画多条宽度不同、透明度不同的 Polyline
  • 模拟渐变效果
// 多层叠加
strokeOpacity: 0.3 / 0.2 / 0.1
strokeWeight: 10 / 20 / 30

👉 效果有限,但实现简单


💡 总结

你要的是“渐变区域”,关键不是 Polyline,而是:

需求 推荐方案
简单渐变区域 Polygon + 伪渐变
高质量渐变 CanvasLayer
数据图表 ECharts
快速hack 多Polyline叠加
❌
❌