您当前的位置: 首页 > 

暂无认证

  • 22粉丝

    0关注

    0博文

    0收益

  • 22浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题

发布时间:2022-05-31 18:32:19 ,浏览量:105

文章目录
  • 🥇 虚拟DOM与DOM Diffing算法
    • 📌 案例
    • 📕 基本原理图
    • 🔑 虚拟DOM中的key的作用
    • 🔐用index作为key可能会引发的问题:
    • 🔓开发中如何选择key?

🥇 虚拟DOM与DOM Diffing算法

首先来个案例验证一下Diffing算法的存在以及工作原理

📌 案例

需求:

验证虚拟DOM Diffing算法的存在

效果:

在这里插入图片描述

📕 基本原理图

在这里插入图片描述

代码如下:


  class Time extends React.Component {
    state = { date: new Date() }
    //Date 对象会自动把当前日期和时间保存为其初始值
    componentDidMount() {
      setInterval(() => {
        this.setState({
          date: new Date()
        })
      }, 1000);
    }

    render() {
      return (
        
          {/* 
              每次一更新数据变成新的时间,render重新渲染,
              拿到的一堆虚拟DOM与之前的数据通过Diffing算法进行比较,
              发现h1和input对应的真实DOM在页面上没有变化
              ,而span标签对应的真实DOM在页面上一直变化
            */}
          hello
          
          
            {/* toTimeString():把 Date 对象的时间部分转换为字符串: */}
            现在是:{this.state.date.toTimeString()}
            
            {/* 通过Diffing算法进行对比,发现这个input对应的真实DOM在页面上没有变化,
              所以输入的数据不会丢失,因此Diffing算法不止对比一层的标签,
              有多少层对比多少层,逐层对比,最小的力度是标签 */}
          
        
      );
    }
  }

  ReactDOM.render(, document.getElementById('test'))

代码解释:

  • 首先初始化state,获取当前的日期时间,在组件挂载完毕之后,设置一个计时器,每一秒更新当前的时间,每次一更新数据变成新的时间,render重新渲染, 拿到的一堆虚拟DOM与之前的数据通过Diffing算法进行比较,发现h1和input对应的真实DOM在页面上没有变化,而span标签对应的真实DOM在页面上一直变化,说明一直更新span这个虚拟DOM,映射成真实DOM一直在变化。
  • Diffing算法不仅仅只比较一层的节点,通过Diffing算法进行对比,发现span标签里的input对应的真实DOM在页面上没有变化,所以输入的数据不会丢失,因此Diffing算法不止对比一层的标签, 有多少层对比多少层,逐层对比,最小的力度是标签

🔒了解了Diffing算法的存在之后,接下来介绍一下当遍历数据时,虚拟DOM中的key的作用~~

🔑 虚拟DOM中的key的作用

经典面试题:

  1. react/vue中的key有什么作用?(key的内部原理是什么?)
  2. 为什么遍历列表时,key最好不要用index?
  • 简单的说:key是虚拟DOM对象的标识,在更新显示时key起着极其重要的作用。

  • 详细的说:当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】,随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:

    1. 旧虚拟DOM中找到了与新虚拟DOM相同的key:

      ​ a. 若虚拟DOM中内容没变,直接使用之前的真实DOM

      ​ b. 若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM

    2. 旧虚拟DOM中未找到与新虚拟DOM相同的key

      a. 根据数据创建新的真实DOM,随后渲染到页面

🔐用index作为key可能会引发的问题:
  1. 若对数据进行:逆序添加,逆序删除等破坏顺序操作:

    会产生没有必要的真实DOM更新 ===> 界面效果没问题,但效率低。

举个案例🥜,效果如下:

在这里插入图片描述

代码如下:

class Person extends React.Component {
  state = {
    persons: [
      { id: 1, name: '小张', age: 18 },
      { id: 2, name: '小李', age: 19 }
    ]
  }
  add = () => {
    const { persons } = this.state
    const p = { id: persons.length + 1, name: '小王', age: 20 }
    this.setState({ persons: [p, ...persons] })
  }

  render() {
    return (
      
        展示人员信息
        添加一个小王
        使用index(索引值)作为key
        {
            this.state.persons.map((personObj, index) => {
              return {personObj.name}----{personObj.age}
            })}
        
      
    );
  }
}

ReactDOM.render(, document.getElementById('test'))

慢动作回放----使用index索引值作为key

​ 初始数据:

​ { id: 1, name: ‘小张’, age: 18 },

​ { id: 2, name: ‘小李’, age: 19 }

​ 初次挂载时,根据数据生成初始的虚拟DOM:

​ < < 小张----18 < <

​ < < 小李----19 < <

​ 更新后的数据:

​ { id: 3, name: ‘小王’, age: 20 },

​ { id: 1, name: ‘小张’, age: 18 },

​ { id: 2, name: ‘小李’, age: 19 }

​ 更新后的数据的虚拟DOM:

​ < < 小王----20 < <

​ < < 小张----18 < <

​ < < 小李----19 < <

根据前面的作用分析,用index会打乱顺序,虽然界面效果没问题,

但是,这样导致没有必要的真实DOM更新,效率降低

如何解决上述问题呢?

可以将id值作为key问题就解决了。

{
    this.state.persons.map((personObj) => {
      return {personObj.name}----{personObj.age}
    })}

慢动作回放----使用id唯一标识作为key

​ 初始数据:

​ { id: 1, name: ‘小张’, age: 18 },

​ { id: 2, name: ‘小李’, age: 19 }

​ 初次挂载时,根据数据生成初始的虚拟DOM:

  • 小张----18

  • 小李----19

    ​ 更新后的数据:

    ​ { id: 3, name: ‘小王’, age: 20 },

    ​ { id: 1, name: ‘小张’, age: 18 },

    ​ { id: 2, name: ‘小李’, age: 19 }

    ​ 更新后的数据的虚拟DOM:

  • 小王----20 (生成新的真实DOM)

  • 小张----18 (复用之前生成的真实DOM)

  • 小李----19 (复用之前生成的真实DOM)

    1. 如果结构中还包含输入类的DOM

      会产生错误DOM更新 ===> 界面有问题。

    我们先在每一个li标签里面加上一个input框:

    render() {
      return (
        
          展示人员信息
          添加一个小王
          使用index(索引值)作为key
          {
              this.state.persons.map((personObj, index) => {
                return {personObj.name}----{personObj.age}
                  	   
              })}
          
          
          
          使用id(数据的唯一标识)索引值作为key
          {
              this.state.persons.map((personObj) => {
                return {personObj.name}----{personObj.age}
                       
              })}
          
        
      );
    }
    

    当我们在输入框里分别输入框对应的名字之后,点击添加小王,会发现用index作为key的数据乱了,而用id作为key的数据是正确的。

    效果如下:

    在这里插入图片描述

    再次进行分析:

    慢动作回放----使用index索引值作为key

    ​ 初始数据:

    ​ { id: 1, name: ‘小张’, age: 18 },

    ​ { id: 2, name: ‘小李’, age: 19 }

    ​ 初次挂载时,根据数据生成初始的虚拟DOM:

    ​ < < 小张----18 < < < <

    ​ < < 小李----19 < < < <

    ​ 更新后的数据:

    ​ { id: 3, name: ‘小王’, age: 20 },

    ​ { id: 1, name: ‘小张’, age: 18 },

    ​ { id: 2, name: ‘小李’, age: 19 }

    ​ 更新后的数据的虚拟DOM:

    ​ < < 小王----20 < < < <

    ​ < < 小张----18 < < < <

    ​ < < 小李----19 < < < <

    对比新旧虚拟DOM的第一条数据时,发现姓名年龄不一样,而input输入框这个节点一样,然后将旧的虚拟DOM对应的真实DOM给新的虚拟DOM复用,但是旧的虚拟DOM对应的真实DOM里面还残留了小张的信息。

    将id作为key就避免了这个问题

    1. 注意!!!如果不存在对数据的逆序添加,逆序删除等破坏性顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。
    🔓开发中如何选择key?
    1. 最好使用每条数据的唯一标识作为key,比如id,手机号,身份证号,学号等唯一值。
    2. 如果确定只是简单的展示数据,用index也是可以的。
  • 关注
    打赏
    1652792496
    查看更多评论
    广告位
    立即登录/注册

    微信扫码登录

    0.4343s