前端交互体验优化(三):避免无意义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相同,不发生更新】

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

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

相关推荐
陈随易44 分钟前
农村程序员-关于小孩教育的思考
前端·后端·程序员
云深时现月1 小时前
jenkins使用cli发行uni-app到h5
前端·uni-app·jenkins
昨天今天明天好多天1 小时前
【Node.js]
前端·node.js
2401_857610031 小时前
深入探索React合成事件(SyntheticEvent):跨浏览器的事件处理利器
前端·javascript·react.js
刘艳兵的学习博客1 小时前
刘艳兵-DBA033-如下那种应用场景符合Oracle ROWID存储规则?
服务器·数据库·oracle·面试·刘艳兵
雾散声声慢1 小时前
前端开发中怎么把链接转为二维码并展示?
前端
熊的猫1 小时前
DOM 规范 — MutationObserver 接口
前端·javascript·chrome·webpack·前端框架·node.js·ecmascript
天农学子1 小时前
Easyui ComboBox 数据加载完成之后过滤数据
前端·javascript·easyui
mez_Blog1 小时前
Vue之插槽(slot)
前端·javascript·vue.js·前端框架·插槽
爱睡D小猪2 小时前
vue文本高亮处理
前端·javascript·vue.js