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

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

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

相关推荐
六月June June1 小时前
自定义调色盘组件
前端·javascript·调色盘
SY_FC2 小时前
实现一个父组件引入了子组件,跳转到其他页面,其他页面返回回来重新加载子组件函数
java·前端·javascript
糟糕好吃2 小时前
我让 AI 操作网页之后,开始不想点按钮了
前端·javascript·后端
陈天伟教授2 小时前
人工智能应用- 天文学家的助手:08. 星系定位与分类
前端·javascript·数据库·人工智能·机器学习
VaJoy2 小时前
给到夯!前端工具链新标杆 Vite Plus 初探
前端·vite
似水明俊德3 小时前
01-C#.Net-泛型-面试题
java·开发语言·面试·c#·.net
小彭努力中4 小时前
191.Vue3 + OpenLayers 实战:可控化版权信息(Attribution)详解与完整示例
前端·javascript·vue.js·#地图开发·#cesium
奇舞精选4 小时前
用去年 github 最火的 n8n 快速实现自动化推送工具
前端·agent
奇舞精选4 小时前
实践:如何为智能体推理引入外部决策步骤
前端·agent
无限大64 小时前
AI实战02:一个万能提示词模板,搞定90%的文案/设计/分析需求
前端·后端