当前位置: 首页 > article >正文

React Diffing 算法完整指南

React Diffing 算法完整指南

1. Diffing 算法概述

1.1 什么是 Diffing

Diffing 算法是 React 用于比较两棵虚拟 DOM 树差异的算法,用来确定需要更新的部分,从而最小化 DOM 操作。

1.2 基本原则

  1. 不同类型的元素会产生不同的树
  2. 通过 key 属性标识哪些子元素在不同渲染中保持稳定
  3. 采用同层比较策略

2. Diffing 策略详解

2.1 元素类型比较

// 不同类型元素比较
// 旧树
<div>
  <Counter />
</div>

// 新树
<span>
  <Counter />
</span>

// React 会完全删除旧树,重建新树

2.2 同类型元素比较

// 同类型DOM元素比较
// 旧树
<div className="old" title="old">
  Hello
</div>

// 新树
<div className="new" title="new">
  World
</div>

// React 只会更新变化的属性

2.3 组件比较

class MyComponent extends React.Component {
  render() {
    // 更新时只比较渲染结果
    return (
      <div>
        <h1>{this.props.title}</h1>
        <p>{this.props.content}</p>
      </div>
    );
  }
}

3. 列表 Diffing

3.1 无 key 的情况

// 效率较低的列表渲染
function ListWithoutKeys() {
  return (
    <ul>
      <li>Item 1</li>
      <li>Item 2</li>
      <li>Item 3</li>
    </ul>
  );
}

// 当列表项变化时,React 需要重新渲染所有项

3.2 使用 key 的优化

// 使用 key 的列表渲染
function ListWithKeys() {
  const items = [
    { id: 1, text: 'Item 1' },
    { id: 2, text: 'Item 2' },
    { id: 3, text: 'Item 3' }
  ];

  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>{item.text}</li>
      ))}
    </ul>
  );
}

// React 可以通过 key 识别哪些元素保持不变

3.3 key 的最佳实践

// 不推荐:使用索引作为 key
const BadList = () => (
  <ul>
    {items.map((item, index) => (
      <li key={index}>{item.text}</li>
    ))}
  </ul>
);

// 推荐:使用稳定的唯一标识作为 key
const GoodList = () => (
  <ul>
    {items.map(item => (
      <li key={item.id}>{item.text}</li>
    ))}
  </ul>
);

4. Diffing 算法实现原理

4.1 树的遍历策略

function diffTree(oldTree, newTree) {
  if (oldTree === null) {
    // 插入新节点
    return createNode(newTree);
  }

  if (newTree === null) {
    // 删除旧节点
    return null;
  }

  if (oldTree.type !== newTree.type) {
    // 替换节点
    return createNode(newTree);
  }

  // 更新现有节点
  updateNode(oldTree, newTree);

  // 递归处理子节点
  diffChildren(oldTree.children, newTree.children);
}

4.2 子节点比较算法

function diffChildren(oldChildren, newChildren) {
  // 第一轮:处理更新的节点
  for (let i = 0; i < Math.min(oldChildren.length, newChildren.length); i++) {
    diff(oldChildren[i], newChildren[i]);
  }

  // 处理新增的节点
  if (newChildren.length > oldChildren.length) {
    newChildren.slice(oldChildren.length).forEach(child => {
      create(child);
    });
  }

  // 处理删除的节点
  if (oldChildren.length > newChildren.length) {
    oldChildren.slice(newChildren.length).forEach(child => {
      remove(child);
    });
  }
}

5. 性能优化策略

5.1 避免不必要的渲染

class OptimizedComponent extends React.Component {
  shouldComponentUpdate(nextProps, nextState) {
    // 只在必要时更新
    return this.props.value !== nextProps.value;
  }

  render() {
    return <div>{this.props.value}</div>;
  }
}

// 使用 React.memo 优化函数组件
const MemoizedComponent = React.memo(function MyComponent(props) {
  return <div>{props.value}</div>;
});

5.2 列表优化

// 使用 key 和 memo 优化列表渲染
const OptimizedListItem = React.memo(({ item }) => (
  <li>{item.text}</li>
));

function OptimizedList({ items }) {
  return (
    <ul>
      {items.map(item => (
        <OptimizedListItem 
          key={item.id} 
          item={item}
        />
      ))}
    </ul>
  );
}

5.3 大型列表虚拟化

import { FixedSizeList } from 'react-window';

function VirtualizedList({ items }) {
  const Row = ({ index, style }) => (
    <div style={style}>
      {items[index].text}
    </div>
  );

  return (
    <FixedSizeList
      height={400}
      width={300}
      itemCount={items.length}
      itemSize={35}
    >
      {Row}
    </FixedSizeList>
  );
}

6. 常见问题和解决方案

6.1 key 相关问题

// 问题:key 不稳定导致的重新渲染
const ProblematicList = () => (
  <ul>
    {items.map((item, i) => (
      <li key={Math.random()}>{item.text}</li> // 不要这样做
    ))}
  </ul>
);

// 解决方案:使用稳定的唯一标识
const FixedList = () => (
  <ul>
    {items.map(item => (
      <li key={item.id}>{item.text}</li>
    ))}
  </ul>
);

6.2 不必要的重渲染

// 问题:父组件更新导致子组件不必要的重渲染
const Parent = () => {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>
        Count: {count}
      </button>
      <Child data={data} /> // 即使 data 没变,Child 也会重渲染
    </div>
  );
};

// 解决方案:使用 useMemo 或 React.memo
const Parent = () => {
  const [count, setCount] = useState(0);
  const memoizedData = useMemo(() => data, [data]);
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>
        Count: {count}
      </button>
      <Child data={memoizedData} />
    </div>
  );
};

7. 总结

7.1 Diffing 算法要点

  1. 采用同层比较策略
  2. 不同类型元素产生不同树
  3. key 属性的重要性
  4. 组件的稳定性

7.2 优化建议

  1. 合理使用 key
  2. 避免不必要的嵌套
  3. 使用不可变数据结构
  4. 适当使用 memo 和 useMemo
  5. 大列表考虑虚拟化

7.3 最佳实践

  1. 保持组件的纯粹性
  2. 合理拆分组件
  3. 正确使用 key
  4. 避免深层组件树
  5. 及时进行性能优化

8. 经典面试题

1.react/vue中的key有什么作用? (key的内部原理是什么?)

2.为什么遍历列表时,key最好不要用index?

1. 虚拟DOM中key的作用:
  1. 简单的说:key是虚拟DOM对象的标识,在更新显示时key起着极其重要的作用。
  2. 详细的说:当状态中的数据发生变化时,react会根据【新数据]生成[新的虚拟DOM], 随后React进行【新虚拟DOM]与【旧虚拟DOM]的diff 比较,比较规则如下:
    a,旧虚拟DOM中找到了与新虚拟DOM相同的key:
    (1).若虚拟DOM中内容没变,直接使用之前的真实DOM
    (2).若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM
    b.旧虚拟DOM中未找到与新虚拟DOM相同的key 根据数据创建新的真实DOM,随后渲染到到页面
2.用indexf作 key可能会引发的问题:
1.若对数据进行:逆序添加,逆序删除等破坏顺序操作: 会产生没有必要的真实DOM更新 ==>界面效果没问题,但效半低。
2.如果结构中还包含输入类的DOM: 会产生错误DOM更新 ==>界面有问题。
3.注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。 
3.开发中如何选择key?:
1.最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等唯一值。
2.如果确定只是简单的展示数据,用index也是可以的。

http://www.kler.cn/a/457200.html

相关文章:

  • STM32配合可编程加密芯片SMEC88ST的防抄板加密方案设计
  • uniapp:微信小程序文本长按无法出现复制菜单
  • 使用C++实现一个高效的线程池
  • 自动化办公-合并多个excel
  • redis数据结构设计
  • 代码随想录day22 | leetcode 39.组合总和 40.组合总和II 131.分割回文串
  • 精读DeepSeek v3技术文档的心得感悟
  • ensp 关于ARRP 的讲解 配置
  • 【WSL】Ubuntu 24.04 安装配置docker
  • Lua语言的计算机基础
  • 基于aspose.words组件的word bytes转pdf bytes,去除水印和解决linux中文乱码问题
  • EsChatPro 接入国内 DeepSeek 大模型
  • vue3点击按钮出现右抽屉组件vue页面
  • Linux复习3——管理文件系统2
  • uboot与kernel通常不位于安全secure区域
  • 不同操作系统下安装Node.js及配置环境的详细步骤
  • Linux RTC 驱动框架
  • C++类与对象中
  • 网络安全专有名词详解_3
  • 微服务篇-深入了解 XXL-JOB 分布式任务调度的具体使用(XXL-JOB 的工作流程、框架搭建)
  • ipad如何直连主机(Moonlight Sunshine)
  • JVM - JVM基础
  • LeetCode:3159. 查询数组中元素的出现位置(hash Java)
  • 【深度学习实战:kaggle自然场景的图像分类-----使用keras框架实现vgg16的迁移学习】
  • Scala_【1】概述
  • 解决单台Elasticsearch 未授权访问漏洞