Showing Posts From

Vue

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.$set 或 splice(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(且大概率你不该用它)、"数据不更新"先查是不是解构丢了响应式。

TypeScript `import type` 和 "没有导出的成员" 的坑

Vue 项目里想约束一个变量只能是 element-plus tag 的合法类型: import type { TagType } from "element-plus";const t: TagType = "success";结果 TS 报错: 模块 "element-plus" 没有导出的成员 "TagType"。 你是想改用 "import TagType from 'element-plus'" 吗?问题不是 import type 用错了,是 element-plus 根本没在主入口导出 TagType。 import type 是干嘛的 先说 import type 本身,它只导入类型信息,编译成 JS 时会完全消失: import type { User } from "./types";const u: User = { id: 1 }; // 编译后 import 语句被抹掉对比普通 import: import { User } from "./types"; // 编译后仍然 require("./types") — 运行时如果这个模块只导出类型就报错import type 的三个作用:避免运行时副作用 — 只用类型的场景不会拉进模块 配合 verbatimModuleSyntax — 现代 TS 项目里"类型 vs 值"必须显式区分 减小打包体积 — 无用运行时代码会被 tree-shake 掉为什么 element-plus 报"没有导出的成员" TagType 在 element-plus 内部定义: // packages/components/tag/src/tag.ts export type TagType = "" | "success" | "info" | "warning" | "danger";但没转发到主入口 element-plus/es/index.d.ts。所以: import type { TagType } from "element-plus"; // 找不到两种解法 方案 1:自己定义(推荐) 三秒钟搞定,稳定: type TagType = "" | "success" | "info" | "warning" | "danger";const t: TagType = "success";对内部类型 element-plus 后续升级也不会一言不合就改。自己定义等于 API 边界完全掌控。 方案 2:从深路径引 理论上可以: import type { TagType } from "element-plus/es/components/tag/src/tag";局限:这是非公开 API,element-plus 版本升级可能改路径 需要构建工具能解析这种深路径 不同版本的 element-plus 目录结构可能不同(lib/ vs es/ vs dist/)做工具库或第三方组件时优先方案 1,业务代码里图省事偶尔可以用方案 2。 import type 的常见变体 内联 type(TS 4.5+): import { type User, createUser } from "./user"; // ^^^^ 只这个是类型同一 import 里混装类型和值,编译后 User 被抹掉,createUser 保留。 全部当值 import(老式,不推荐): import { User } from "./user"; type MyRole = User["role"]; // 运行时会真的加载模块export type: export type { User } from "./user";再转发类型,同样只影响类型系统,不产生运行时依赖。 一张速查表场景 写法只用类型 import type { X } from "..."同一 import 混装 import { type X, y } from "..."转发类型 export type { X } from "..."库没有导出的内部类型 自己定义 or 深路径引(慎用)verbatimModuleSyntax=true 类型必须用 import type,否则报错一句话总结 import type = 只要类型不要运行时代码。库没导出的内部类型别硬拽——自己定义一份最稳,深路径 import 只是应急。