• React Diff 有key和没key混合的时候怎么diff
  • 发布于 2个月前
  • 230 热度
    0 评论
网上有太多的文章都在说React Diff 但是他们都是讲了基本的原理,但是真正使用的时候真的是这样么?根据大佬们的源码解析,有以下几种场景:
1. 一次循环比对所有的节点,全部都能复用,这是非常理想的情况
2. 第一循环完之后,老fiber/新fiber 其中有一个遍历完了,如果老fiber还剩下节点,那么标记删除,如果新fiber还剩下节点,标记新增
3. 第一次循环中途停下来了,这之后使用了索引对比,就是把剩下的oldFiber节点通过[key]: fiber的方式存在一个map中,记录第一次遍历时候的最后一个索引为lastIndex, 然后开始遍历newFiber.sibling,拿遍历当前节点的key去map中找老节点有没有,如果没有就标记新增,进行下一个节点比对,如果有,判断老fiber中的索引,跟当前索引值哪个大,如果老fiber大,就按兵不动,进行下一步对比,如果老fiber节点索引比较小,那么节点复用的同时,把节点放在lastIndex后面,这样以此类推进行处理,当然这样处理完也有可能回到第二步,处理2个链表剩余的节点

这里有比较严重的一个问题,几乎没有人回答,就是这个map怎么生成的,因为不是所有的节点都有key值,有key和无key的节点穿插在里面,这个算法还真的是这样么?几乎所有的文章都默认了节点上是有key的,也有答主说会以老fiber的index作为key,我表示怀疑。
下面就用案例一起看看吧
基础代码
import React, { useEffect, useState } from 'react';
function A() {
  useEffect(() => {
    console.log('[create]:a');
    
    return () => {
      console.log('[unmount]: a')
    }
  }, [])

  return 'a';
}

function B() {
  useEffect(() => {
    console.log('[create]:b')
    return () => {
      console.log('[unmount]: b')
    }
  }, [])
  return 'b'
}

export default function DiffComponent() {
  const [changeDiff, setChangeDiff] = useState(0);
  console.log('[changeDiff]:', changeDiff);
  return <>
    <div>{changeDiff ?'diff之前' : 'diff之后'}<button onClick={() => setChangeDiff(changeDiff+1)}>diff</button></div>
    <div>
      <A />
      <A />
      <A />
      <B />
      <B />
      <B />
    </div>
  </>
}
无key的时候(按照顺序比对)
常规情况
 <div>
  <A />
  <A />
  <A />
  <B />
  <B />
  <B />
</div>

常规情况不管diff怎么点,组件都不会打印,说明复用了

长度相同,但是会有增减
  <div>
      {
        changeDiff == 0
        ? (
          <>
             <A />
             <A />
             <B />
          </>
        ): (
          <>
            <A />
            <B />
            <B />
          </>
        )
      }
    </div>

长短不一的问题
  <div>
      {
        changeDiff == 0
        ? (
          <>
             <A />
             <A />
             <A />
          </>
        ): (
          <>
            <A />
            <A />
            <B />
            <B />
            <B />
          </>
        )
      }
    </div>


A和B组件总量相同,前后顺序不同
 <div>
      {
        changeDiff == 0
        ? (
          <>
             <A />
             <A />
             <B />
             <B />
          </>
        ): (
          <>
            <B />
            <B />
            <A />
            <A />
          </>
        )
      }
    </div>

有key的时候,符合绝大数文章的diff算法
总量相同,key相同,顺序不同
<div>
      {
        changeDiff == 0
        ? (
          <>
             <A key="1" />
             <A key="2"  />
             <B key="3"  />
             <B key="4"  />
          </>
        ): (
          <>
            <B key="4"  />
            <B key="3"  />
            <A key="2"  />
            <A key="1"  />
          </>
        )
      }
    </div>

组件全部复用,基于上一个案例,有没有key逻辑是不一样的,所以多的案例就不写了,符合大家的预期
混合key进行Diff,建议先自己思考答案
  <div>
      {
        changeDiff == 0
        ? (
          <>
             <A  />
             <A key="2"  />
             <B key="3"  />
             <B key="4"  />
          </>
        ): (
          <>
            <B key="4"  />
            <B key="3"  />
            <A key="2"  />
            <A  />
          </>
        )
      }
    </div>
这个A到底会不会重新重建?

有文章说老fiber会用索引为key来创建map真的是这样么?
 <div>
      {
        changeDiff == 0
        ? (
          <>
             <A  />
             <A key="2"  />
             <B key="3"  />
             <B key="4"  />
          </>
        ): (
          <>
            <B key="4"  />
            <B key="3"  />
            <A key={0} />
            <A key="2"  />
           
          </>
        )
      }
    </div>
事实证明还是重新创建了

混合key的时候,什么情况下复用
<!-- 堆代码 duidaima.com --> 
<div>
      {
        changeDiff == 0
        ? (
          <>
             <A key="2"  />
             <B key="3"  />
             <A  />
             <B key="4"  />
          </>
        ): (
          <>
            <B key="4"  />
            <B key="3"  />
            <A />
            <A key="2"  />
           
          </>
        )
      }
    </div>
只有相同的索引下才会复用
所以,在diff的过程中应该加上,如果没key的话,会去老fiber里面找相同位置的fiber进行比对,判断是否需要复用,如果有key,那么去map里面捞,比对类型和索引

作者:吃山鬼的神仙
链接:https://juejin.cn/post/7243087523756064805
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
用户评论