Vue 里强制组件重新渲染:改 key 才是正解

Vue 里”刷新组件”是个模糊需求。要重新 render 一次?重新执行 setup 拉数据?还是彻底把内部 state 归零?不同目标要用不同手段。

四种方式的差别

手段会销毁组件会重新执行 setup会重置 state
:key
$forceUpdate()
重新赋值 ref/reactive只改被赋值的字段
v-if 切换

场景一:改 :key(推荐)

想”完全刷新”一个组件(比如切换 tab 后要重新拉数据、重置表单):

<template>
  <UserForm :key="refreshKey" />
  <button @click="refresh">重新加载</button>
</template>

<script setup>
import { ref } from "vue";

const refreshKey = ref(0);
const refresh = () => refreshKey.value++;
</script>

refreshKey 一变,Vue 视这是新组件——旧的销毁、新的挂载、setup() 重跑、生命周期钩子从 onMounted 全走一遍。

场景二:$forceUpdate(几乎用不到)

只想强制重新 render,不动 state:

import { getCurrentInstance } from "vue";
const instance = getCurrentInstance();
instance.proxy.$forceUpdate();

适用场景其实很窄——一般是把不响应的对象塞进模板,或者用了第三方非响应库。只要还能改成响应式,就别用 $forceUpdate

场景三:v-if 切换

粗暴但清晰:

<template>
  <UserForm v-if="show" />
</template>

<script setup>
const show = ref(true);
async function reset() {
  show.value = false;
  await nextTick();
  show.value = true;
}
</script>

等价于改 key,用于”父组件不方便传 key”的场景。

数据变了但界面不更新

Vue3 大多数场景响应式自动生效,出问题基本是下面几种:

1. 解构丢了响应式

const state = reactive({ count: 1 });
const { count } = state;    // count 是快照,不再响应

改成 toRefs

const { count } = toRefs(state);
count.value++;              // 会触发更新

2. 用了 shallowRef 但改的是深层

const data = shallowRef({ a: 1 });
data.value.a = 2;           // 不会更新

手动触发:

import { triggerRef } from "vue";
triggerRef(data);

或者直接换整个引用:

data.value = { ...data.value, a: 2 };

3. 用了 markRaw / Object.freeze

这些对象上禁用响应式,任你怎么改视图都不动。

4. Vue 2 的经典坑(Vue 3 无此问题)

  • this.arr[index] = value 不触发 → 用 this.$setsplice(index, 1, value)
  • this.obj.newKey = 1 不触发 → 用 this.$set(this.obj, "newKey", 1)

nextTick:拿最新 DOM

修改数据后立刻读 DOM 大小、滚动位置:

import { nextTick } from "vue";

count.value++;
await nextTick();
console.log(el.value.scrollHeight);   // 这时才是新高度

一句话总结

“重置组件”改 :key、“只重画”用 $forceUpdate(且大概率你不该用它)、“数据不更新”先查是不是解构丢了响应式。