前端交互体验优化(三):避免无意义Rerender

对于React和Vue的单组件的Diff算法,实现方式相差不大。

一个组件的Rerender,由其 props,state,key 和标签类型等决定。

我们结合一个例子,看下什么样的 Rerender 是无意义的。

"有一个列表组件,选中列表的某一项卡片时,对其进行高亮"

下面是我们实现的一个Bad Case

App.vue

jsx 复制代码
<script setup lang="ts">
import { ref, onMounted } from "vue";
import CardItem from "./components/Card/index.vue";
import { ICard } from "./components/Card/type";

const selectedId = ref("");
const cardList = ref<ICard[]>([]);
const observer = ref<IntersectionObserver>();

onMounted(() => {
  let cards = [];
  for (let i = 0; i < 3000; i++) {
    const title = `Card title (${i})`;
    const content = `This is card content (${i})`;
    cards.push({
      id: `${i}`,
      title,
      content,
      render: true,
    });
  }
  cardList.value = cards;
});

const onSelect = (id: string) => {
  console.log("on select", id);
  selectedId.value = id;
};
</script>

<template>
  <div class="card-list" id="cardContainer">
    <CardItem
      v-for="item in cardList"
      :observer="observer"
      :key="item.id"
      :item="item"
      @on-select="onSelect"
      :selected-id="selectedId"
    />
  </div>
</template>

Card.vue

jsx 复制代码
<script setup lang="ts">
import { ref, defineEmits, onUpdated } from "vue";
const cardRef = ref<HTMLDivElement>();

const props = defineProps({
  item: Object,
  selectedId: String,
  observer: IntersectionObserver,
});

const emits = defineEmits(["on-select"]);
onUpdated(() => {
  console.log("On Selected of Card: ", props.item?.id)
})

</script>

<template>
  <div
    class="card-item flex align-middle mb-4"
    :class="{ selected: selectedId === props.item?.id }"
    @click="emits('on-select', props.item?.id)"
  >
    <div class="card-item--right ml-8">
      <div class="title">{{ props.item?.title }}</div>
      <div class="content flex align-middle">
        {{ props.item?.content }}
      </div>
    </div>
  </div>
</template>

通过点击列表项,可以发现,每次的选中,都会触发列表中所有子项的更新。

在这个例子中,就出现了状态乱入,即把选中卡片的id 传递给了每个卡片,让卡片自己判断自己是否被选中(selectedId === props.item.id),由于每次发生不同的选中,selectedId 都会发生变化,也即是每个卡片的props.selectedId 发生了变化,Diff 之后,每个卡片都需要更新。

我们称这种状态的更新是无意义的。

理论上,当一个卡片选中时,最多会有两个卡片会发生变化,一个卡片由选中变为不选中,一个卡片由不选中变为选中,而其他卡片则没有变化,也不应该发生更新。

既然明白了问题原因,我们可以调整状态的使用,对上面的例子进行优化

示例-状态提升优化

App.vue

在App组件中,遍历时计算每个卡片的选中状态,并将是否选中的bool 值作为props传递给卡片组件

jsx 复制代码
<template>
  <div class="card-list" id="cardContainer">
    <CardItem
      v-for="item in cardList"
      :observer="observer"
      :key="item.id"
      :item="item"
      @on-select="onSelect"
      :selected="item.id === selectedId"
    />
  </div>
</template>

Card.vue

卡片组件通过props.selected 改变自身的状态

jsx 复制代码
<script setup lang="ts">
import { defineEmits, onUpdated } from "vue";

const props = defineProps({
  item: Object,
  selected: Boolean,
  observer: IntersectionObserver,
});

const emits = defineEmits(["on-select"]);
onUpdated(() => {
  console.log("On Updated of Card: ", props.item?.id)
})

</script>

<template>
  <div
    class="card-item flex align-middle mb-4"
    :class="{ selected: selected }"
    @click="emits('on-select', props.item?.id)"
  >
    
    <div class="card-item--right ml-8">
      <div class="title">{{ props.item?.title }}</div>
      <div class="content flex align-middle">
        {{ props.item?.content }}
      </div>
    </div>
  </div>
</template>

<style scoped>

卡片的选中状态不再由组件自己计算,而是交给了父组件。

父组件计算完成选中状态之后,传递给卡片组件的props.selected只会是true 或者 false

那么Diff的结果将会是:

卡片由选中变为不选中(true -> false)【diff props不相同,发生更新】

卡片由不选中变为选中(false -> true)【diff props不相同,发生更新】

卡片保持不选中(false -> false)【diff props相同,不发生更新】

因此,优化之后,每次的选中,最多只会使得两个组件发生更新,其他组件保持不变。

类似的场景还有很多,所以我们要理解单向数据流的好处,同时在做状态管理时,避免将无意义的状态传递给子组件或者子组件去监听很多可能自身并不完全需要的全局状态。避免无意义的渲染,以此提高渲染效率。

相关推荐
卸任13 分钟前
Electron霸屏功能总结
前端·react.js·electron
fengci.13 分钟前
ctfshow黑盒测试前半部分
前端
喵个咪24 分钟前
Headless 架构优势:内容与展示解耦,一套 API 打通全端生态
前端·后端·cms
小江的记录本28 分钟前
【JEECG Boot】 JEECG Boot——数据字典管理 系统性知识体系全解析
java·前端·spring boot·后端·spring·spring cloud·mybatis
喵个咪31 分钟前
传统 CMS 太笨重?试试 Headless 架构的 GoWind,轻量又强大
前端·后端·cms
chenjingming66632 分钟前
jmeter导入浏览器上按F12抓的数据包
前端·chrome·jmeter
张元清32 分钟前
不用 Server Components 也能做 React 流式 SSR —— 实战指南
前端·javascript·面试
前端技术34 分钟前
ArkTS第三章:声明式UI开发实战
java·前端·人工智能·python·华为·鸿蒙
码小瑞38 分钟前
画布文字在不同缩放屏幕上的归一化
前端