后端程序员入门react笔记——react的diff算法(三)

本文详细解释了React中虚拟DOM的概念,以及为何需要diff算法来减少DOM操作。重点介绍了diff算法的工作原理、优化过的复杂度、React的diff策略,以及如何使用key来提高性能和处理节点变化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

diffing算法

虚拟dom

我们知道,react里面操作的都是虚拟dom,最后经过render渲染为真正的dom,那么为什么要提出虚拟dom这个概念呢?其实就是将逻辑和视图区分开,react的虚拟dom,就相当于mvc的c,将数据逻辑和真正的dom区分开,我们知道,对于前端来说dom操作是非常昂贵的,性能消耗最大的就是dom操作。而virtual dom减少了对dom的操作,,不仅避免了资源浪费,而且页面的构建也得到了很大的提升。

为什么要diff

前面我们说了,应避免过多的操作dom,那么diff就是解决了这种问题。我们知道,react在状态发生变化的时候,会批量更新dom,生成新的UI,但是难道state的某一个值发生变化就要导致整个dom重新渲染吗?这明显是不科学的,为了解决这种问题,react提出了diff算法,通过对比新旧的两个虚拟dom树来检测那些dom是真的需要重新如安然,哪些dom是没有变化的。

diff算法

传统的diff算法是通过循环递归的方式对节点一次进行对比,需要O(n ^3)的时间复杂度。为什么?我们从最简单的说,把一棵树转换为另外一棵树,其实就是把一颗n个节点的树挨个儿去另一棵n个节点的树中查找,整个过程是n*n,那么如果发现有不同的地方,我们就需要需改,对一棵树的增删改的算法复杂度是n,那么整个过程就是n*n*n
在这里插入图片描述
react将这种对比策略做了一个优化,将复杂度降到了O(n),那么react是怎么实现的呢?react的diff会预设三个限制

  • 只进行同层级比较
  • 新旧节点的type不同,直接删除旧节点,创建新节点,比如组件不同,元素的类型不同,原来是ul,里面是li,后来改成了div+p,这个时候就会删除旧dom,创建新的dom.
  • 通过key来复用节点

在上面三个限制的基础上,对tree,conponent,element的处理方式又做了优化

  • dom节点不一致直接删除旧节点,创建新节点
  • 组件类型不一致直接删除组件下所有节点,创建新节点
  • 同一层的dom元素,以每个元素对应的key为标识,提供三种操作方式,删除新建和移动
    在这里插入图片描述

diff的最小颗粒度

diff的最小颗粒度是标签,我们举例看一下

class Hello extends React.Component {
   state={
       time:new Date()
   }
   componentDidMount(){
       this.timer=setInterval(()=>{
           this.setState({
               time:new Date()
           })
       },5000)
   }
   render() {
       console.log("i am render")
       return (
           <ul>
               <li>备注:<input type="text"/></li>
               <li><span>{this.state.time.toTimeString()}</span></li>
           </ul>
       )
   }
}

我们看到,每隔5秒,span标签就会从新刷新一次,而其他元素没有变化
在这里插入图片描述

key

前面我们说了,同一层级的元素如果发生了变化,可以删除 插入和移动,前提是必须有key作为标识,如果没有key,比如下面代码

class Hello extends React.Component {
    state = {
        heros: [
            {
                id: 1,
                name: "张三"
            },
            {
                id: 2,
                name: "李四"
            }
        ]
    }
    render() {
        console.log("i am render")
        return (
            <ul>
                {this.state.heros.map((hero,index)=>{
                    return <li>{hero.name}</li>
                })}
            </ul>
        )
    }
}

这个时候浏览器提示了一个warm ,意思是每一个child都应该有唯一key。
在这里插入图片描述
那么这个key应该怎么选取呢

  • 最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等唯一值。
  • 如果确定只是简单的展示数据,用index也是可以的。 但是不推荐。
    为什么不推荐index作为key,官方文档说,如果列表项目的顺序可能会变化,我们不建议使用索引来用作 key 值,因为这样做会导致性能变差,还可能引起组件状态的问题。

我们来举个例子对比一下,再分析问题所在。这段代码里,我点击添加按钮的时候,改变了原来数组对象的顺序,在数组的前头添加了一个对象,我们点击看一下效果

class Hello extends React.Component {
    state = {
        heros: [
            {
                id: 1,
                name: "张三"
            },
            {
                id: 2,
                name: "李四"
            }
        ]
    }
    addHero=()=>{
        this.setState({
            heros: [
                {
                    id: 3,
                    name: "王五"
                },
                ...this.state.heros
            ]
        })
    }
    render() {
        console.log("i am render")
        return (
            <ul>
                <button onClick={this.addHero}>点击添加王五</button>
                <h1>index as key</h1>
                {this.state.heros.map((hero,index)=>{
                    return <li key={index}>{hero.name}<input type="text" /></li>
                })}
                <h1>age as key</h1>
                {this.state.heros.map((hero,index)=>{
                    return <li key={hero.id}>{hero.name}<input type="text" /></li>
                })}
            </ul>
        )
    }
}

在这里插入图片描述
我们发现了什么,以index做为key,input框并没有跟着往下移动,id作为key的往下移动了,我们发现问题了,那么为什么index作为key,input框不往下移动呢?就是因为数据的顺序发生变化了,王五被塞进了数组对象的头部,但是diff的时候,由于元素的key是index,还是从0开始的,并没有发生变化,所以input框并不会移动。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老A技术联盟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值