Vue3 知识点总结 · 2026-03-26

5 阅读2分钟

Vue3 知识点总结 · 2026-03-26

👨‍💻
嘿!大家好 👋
前后端开发工程师 · 日更 CSDN & 掘金

我是一名对代码狂热的 IT 工作者,目前在一家公司任职前后端开发工程师。以后每天都会更新 CSDN 和稀土掘金的文章——工作中写了什么代码都会在平台上展示 🚀 不会公布公司秘密,只是从中提取 IT 语言的知识点,提供给大家学习使用。

我也希望自己一直不忘初心,帖子为证,只要我一天在工位上,都会定时给大家分享开发收获和经验!!! 💪

🏢 前后端开发工程师 📅 每日更新 🎯 从项目代码提炼知识点 🤝 欢迎点赞收藏

本次共识别 15 个知识点,覆盖 7 个分类。

📂 提取来源(共 5 个文件,点击展开)
文件路径识别到的知识点
src/components/zzb_kaip/index.vuescript setup, script setup + TypeScript, watch(), v-slot / #slot, defineProps(), onMounted(), onUnmounted()
src/views/commentAnalysis/components/right_content/index.vuescript setup, computed(), ref(), v-for, v-model, v-if / v-else, useRouter(), useRoute(), defineProps(), defineEmits()
src/views/home/components/content/index.vuescript setup, computed(), watch(), v-for, v-slot / #slot, useRouter()
src/views/home/index.vuescript setup, computed(), v-if / v-else, useRouter(), onMounted(), onUnmounted()
src/views/makeVideo/components/header/index.vuescript setup, computed(), ref(), v-for, v-slot / #slot, v-model, useRouter(), onMounted()

目录


🚀 Vue3 基础

script setup

项目中的用法:

📂 来自 5 个文件(点击展开)
  • src/components/zzb_kaip/index.vue
  • src/views/commentAnalysis/components/right_content/index.vue
  • src/views/home/components/content/index.vue
  • src/views/home/index.vue
  • src/views/makeVideo/components/header/index.vue

src/views/home/components/content/index.vue

</template>

// ← 重点:script setup
<script setup>
import { useRouter } from "vue-router"
import { useUserStore } from "@/store/member_store"
import handler from './handler'
import { CDN_BASE } from '@/constants'

const router = useRouter()
const userStore = useUserStore()

src/views/commentAnalysis/components/right_content/index.vue

</template>

// ← 重点:script setup
<script setup>
import { message } from "ant-design-vue"
import { DownloadOutlined, ZoomInOutlined } from "@ant-design/icons-vue"
import { useRouter, useRoute } from "vue-router"
import imgPlaceholder from "@/assets/commentAnalysis/pic.png"
import handler from "@/views/commentAnalysis/handler"
import makeVideoHandler from "@/views/makeVideo/components/header/handler"

src/views/makeVideo/components/header/index.vue

</template>

// ← 重点:script setup
<script setup>
import { message } from "ant-design-vue"
import { useRouter } from "vue-router"
import bosConfig from "@/util/bos_util/bos_config_hsy"
import handler from "./handler"

const router = useRouter()
const fileInputRef = ref(null)

src/views/home/index.vue

</template>

// ← 重点:script setup
<script setup>
import HandlerBar from './components/handler_bar/index.vue';
import Content from './components/content/index.vue';

const showWinCustomTitleBar = computed(
  () => !window.client || window.client.platform === "win32"
);

src/components/zzb_kaip/index.vue

</template>

// ← 重点:script setup
<script setup lang="ts">
import * as THREE from 'three';
import { onMounted, onUnmounted, ref, watch } from 'vue';

interface LiquidEtherProps {
    mouseForce?: number;
    cursorSize?: number;
    isViscous?: boolean;

是什么: 组合式 API 语法糖,顶层变量/函数自动暴露给模板,无需 return。

面试 Q&A:

❓ Vue2 Options API 和 Vue3 Composition API 有什么区别?
💡 Options API 按选项类型组织代码(data/methods/computed),逻辑分散;Composition API 按功能聚合,逻辑复用靠 composable 函数,更适合大型项目。
❓ setup() 和 `<script setup>` 有什么不同?
💡 setup() 是函数,需要手动 return 暴露给模板;`<script setup>` 是编译器语法糖,顶层声明自动暴露,代码更简洁,性能略优(编译阶段优化)。

script setup + TypeScript

项目中的用法:

📂 来自 1 个文件(点击展开)
  • src/components/zzb_kaip/index.vue

src/components/zzb_kaip/index.vue

</template>

<script setup lang="ts">
import * as THREE from 'three';
import { onMounted, onUnmounted, ref, watch } from 'vue';

interface LiquidEtherProps {
    mouseForce?: number;
    cursorSize?: number;
    isViscous?: boolean;

是什么: 在 script setup 中启用 TS,defineProps/defineEmits 均可带类型约束。

面试 Q&A:

❓ 如何给 props 加 TS 类型?
💡 两种方式:① 泛型 `defineProps<{ name: string }>()` ② 带默认值用 `withDefaults(defineProps<Props>(), { name: 'default' })`,推荐第一种,更简洁。

⚡ 响应式 API

computed()

项目中的用法:

📂 来自 4 个文件(点击展开)
  • src/views/commentAnalysis/components/right_content/index.vue
  • src/views/home/components/content/index.vue
  • src/views/home/index.vue
  • src/views/makeVideo/components/header/index.vue

src/views/home/components/content/index.vue

const homeTipUrl = `${CDN_BASE}/home_tip.svg`

// ← 重点:computed
const isLogin = computed(() => !!userStore.Token)

watch(isLogin, (val) => {
  if (!val) return
  router.push('/workbench').catch(() => {})
})
// 进入工作台
const handleWorkbenchEntry = () => {

src/views/commentAnalysis/components/right_content/index.vue

const router = useRouter()
const route = useRoute()
// ← 重点:computed
const initRatio = computed(() => route.query.ratio || "")
const props = defineProps({
  activeTask: { type: Object, default: null },
  reportReady: { type: Boolean, default: false },
  polling: { type: Boolean, default: false },
  imgGenResult: { type: Object, default: null }
})

src/views/makeVideo/components/header/index.vue

const numVal = ref("1")

// ← 重点:computed
const formState = computed(() => ({
  uploadedList: uploadedList.value,
  ratioVal: ratioVal.value,
  numVal: numVal.value,
  promptText: promptText.value
}))

// ← 重点:computed
const isFormValid = computed(() => uploadedList.value?.length)

src/views/home/index.vue

import Content from './components/content/index.vue';

// ← 重点:computed
const showWinCustomTitleBar = computed(
  () => !window.client || window.client.platform === "win32"
);

const router = useRouter();

const handleKeyDown = (e) => {
  if (e.ctrlKey && e.key.toLowerCase() === "t") {

是什么: 基于响应式数据派生的缓存值,依赖不变时不重新计算。

面试 Q&A:

❓ computed 和 methods 的区别?
💡 computed 有缓存,依赖不变则直接返回缓存值,适合计算量大的场景;methods 每次调用都重新执行,适合需要触发副作用的操作。
❓ 如何实现可写的 computed?
💡 传入包含 get 和 set 的对象:`computed({ get: () => x.value, set: v => x.value = v })`,set 中手动更新源数据。

ref()

项目中的用法:

📂 来自 2 个文件(点击展开)
  • src/views/commentAnalysis/components/right_content/index.vue
  • src/views/makeVideo/components/header/index.vue

src/views/commentAnalysis/components/right_content/index.vue

const onViewReport = () => emit("viewReport")

// ← 重点:ref
const imgPreviewOpen = ref(false)
// ← 重点:ref
const imgPreviewUrl = ref("")
const openImgPreview = (url) => {
  imgPreviewUrl.value = url
  imgPreviewOpen.value = true
}
// 下载生成图
const onDownloadGenImg = (url) => {

src/views/makeVideo/components/header/index.vue

<template>
  <div class="make_video_header">
// ← 重点:ref
    <input ref="fileInputRef" type="file" accept="image/*" multiple class="hidden_input" @change="onFileChange" />
    <img class="pic_begin" src="@/assets/makeVideo/pic_begin.png" alt="" />
    <div class="card_box">
      <div class="row_images">
        <img src="@/assets/makeVideo/cj1.png" alt="" />
        <img src="@/assets/makeVideo/cj2.png" alt="" />
        <img src="@/assets/makeVideo/cj2.png" alt="" />
      </div>

是什么: 将基本类型包装为响应式对象,JS 中访问要加 .value,模板中自动解包。

面试 Q&A:

❓ ref 和 reactive 的区别?
💡 ref 用于基本类型(内部用 RefImpl 包装),访问需 .value;reactive 用于对象/数组(基于 Proxy),直接访问属性。ref 也可包对象,内部会自动调用 reactive。
❓ 模板里为什么不用写 .value?
💡 模板编译时会自动对 ref 解包,访问 ref.value 的操作由编译器插入,开发者无感知。

👁️ 侦听器

watch()

项目中的用法:

📂 来自 2 个文件(点击展开)
  • src/components/zzb_kaip/index.vue
  • src/views/home/components/content/index.vue

src/views/home/components/content/index.vue

const isLogin = computed(() => !!userStore.Token)

// ← 重点:watch
watch(isLogin, (val) => {
  if (!val) return
  router.push('/workbench').catch(() => {})
})
// 进入工作台
const handleWorkbenchEntry = () => {
  if (!isLogin.value) return userStore.setLoginModalVisible(true)
  router.push('/workbench').catch(() => {})

src/components/zzb_kaip/index.vue

<script setup lang="ts">
import * as THREE from 'three';
// ← 重点:watch
import { onMounted, onUnmounted, ref, watch } from 'vue';

interface LiquidEtherProps {
    mouseForce?: number;
    cursorSize?: number;
    isViscous?: boolean;
    viscous?: number;
    iterationsViscous?: number;

是什么: 侦听指定数据源,变化时执行回调,可获取新旧值,支持 deep/immediate。

面试 Q&A:

❓ watch 和 watchEffect 的核心区别?
💡 watch 需要明确指定侦听源,懒执行(默认不立即执行),可拿到新旧值;watchEffect 自动收集依赖,立即执行一次,拿不到旧值。
❓ watch 监听对象的某个属性怎么写?
💡 用 getter 函数:`watch(() => obj.key, callback)`。直接写 `watch(obj.key, ...)` 只是监听当时的值,不是响应式的。
❓ 如何停止侦听?
💡 `const stop = watch(...); stop()` 调用返回值即可停止。组件卸载时会自动停止,手动停止用于在卸载前提前结束侦听。

🎯 模板指令

v-for

项目中的用法:

📂 来自 3 个文件(点击展开)
  • src/views/commentAnalysis/components/right_content/index.vue
  • src/views/home/components/content/index.vue
  • src/views/makeVideo/components/header/index.vue

src/views/home/components/content/index.vue

                    class="item_card" 
                    :class="{ 'item_card--vertical': item.vertical  }"
// ← 重点:v-for
                    v-for="item in CardList" 
                    :key="item.title" 
                    :style="{ width: item.width, height: item.height }"
                >
                    <div class="item_card_title">
                        <div style="font-size:20px;color: #252A31;;font-weight: 700;">{{ item.title }}</div>
                        <div style="font-size: 14px;color: #9a9b9b;">{{item.tip}}</div>
                    </div>

src/views/commentAnalysis/components/right_content/index.vue

      </div>
      <div v-if="imgGenUrls?.length" class="pic_list">
// ← 重点:v-for
        <div v-for="(url, i) in imgGenUrls" :key="i" class="pic_item">
          <img :src="url" alt="" />
          <div class="pic_item_overlay">
            <button type="button" class="pic_item_icon_btn" title="放大" @click.stop="openImgPreview(url)">
              <ZoomInOutlined />
            </button>
            <button type="button" class="pic_item_icon_btn" title="下载" @click.stop="onDownloadGenImg(url)">
              <DownloadOutlined />

src/views/makeVideo/components/header/index.vue

      <div class="row_input">
        <div class="left_area">
// ← 重点:v-for
          <div v-for="url in uploadedList" :key="url" class="upload_preview_wrap">
            <img :src="url" class="upload_preview" />
            <div class="upload_delete" @click="uploadedList.splice(uploadedList.indexOf(url), 1)">×</div>
          </div>
          <img class="upload_placeholder" src="@/assets/makeVideo/cj_input.png" alt="" @click="triggerUpload" />
        </div>
        <div class="right_input">
          <!-- <textarea v-model="promptText" placeholder="描述场景生成的提示词,如……"></textarea> -->

是什么: 列表渲染,必须加 :key(唯一且稳定),key 用于 Diff 算法复用节点。

面试 Q&A:

❓ 为什么 v-for 必须加 key?key 用 index 有什么问题?
💡 key 让 Diff 算法识别节点身份,复用 DOM 减少操作。用 index 做 key 在列表增删时会导致错误复用(节点身份和数据不匹配),造成状态错乱,应用数据唯一 ID。
❓ Vue3 的 Diff 算法有什么优化?
💡 Vue3 Diff 引入最长递增子序列(LIS)算法,最小化节点移动次数;同时增加静态标记(patchFlag)跳过静态节点对比,比 Vue2 快 1.3~2 倍。

v-slot / #slot

项目中的用法:

📂 来自 3 个文件(点击展开)
  • src/components/zzb_kaip/index.vue
  • src/views/home/components/content/index.vue
  • src/views/makeVideo/components/header/index.vue

src/views/home/components/content/index.vue

<template>
    <div class="content">
        <ZzbKaip :style="{ position: 'absolute', inset: '0', zIndex: 0 }" :colors="['#5227FF', '#FF9FFC', '#B19EEF']" :mouseForce="20" :cursorSize="100" :isViscous="false" :resolution="0.5" :autoDemo="true" :autoSpeed="0.5" :autoIntensity="2.2" :takeoverDuration="0.25" :autoResumeDelay="3000" :autoRampDuration="0.6" />
        <div class="content_box">
            <div class="title_box">
                <div style="margin-bottom:8px;"><img :src="homeTitleUrl" ></div>
                <div><img :src="homeTipUrl" ></div>
            </div>
            <div class="card_list">
                <div 

src/views/makeVideo/components/header/index.vue

            <img class="opt_arrow" src="@/assets/makeVideo/soc_bottorm.png" alt="" />
          </div>
          <template #overlay>
            <a-menu :selected-keys="ratioVal ? [ratioVal] : []" @click="({ key }) => { ratioVal = key }">
              <a-menu-item key="1:1">1:1</a-menu-item>
              <a-menu-item key="3:4">3:4</a-menu-item>
            </a-menu>
          </template>
        </a-dropdown>
        <!-- 数量选择暂时隐藏,提交时默认 1 -->

src/components/zzb_kaip/index.vue

    resolution: 0.5,
    isBounce: false,
    colors: () => ['#5227FF', '#FF9FFC', '#B19EEF'],
    style: () => ({}),
    className: '',
    autoDemo: true,
    autoSpeed: 0.5,
    autoIntensity: 2.2,
    takeoverDuration: 0.25,
    autoResumeDelay: 1000,

是什么: 插槽内容分发,支持默认插槽、具名插槽、作用域插槽。

面试 Q&A:

❓ 默认、具名、作用域插槽的区别?
💡 默认插槽:`<slot>` 接收未命名内容;具名插槽:`<slot name='header'>` + `<template #header>` 指定位置;作用域插槽:`<slot :data='item'>` + `<template #default='{ data }'>` 让父组件拿到子组件数据。

v-model

项目中的用法:

📂 来自 2 个文件(点击展开)
  • src/views/commentAnalysis/components/right_content/index.vue
  • src/views/makeVideo/components/header/index.vue

src/views/commentAnalysis/components/right_content/index.vue

      </div>
      <a-modal
// ← 重点:v-model
        v-model:open="imgPreviewOpen"
        :footer="null"
        centered
        :width="600"
        wrap-class-name="pic_preview_modal"
        destroy-on-close
        @cancel="imgPreviewUrl = ''"
      >

src/views/makeVideo/components/header/index.vue

        </div>
        <div class="right_input">
// ← 重点:v-model
          <!-- <textarea v-model="promptText" placeholder="描述场景生成的提示词,如……"></textarea> -->
          <p class="fixed_prompt">将产品主图进行优化,生成一张场景化的商品图</p>
        </div>
      </div>
      <div class="row_options">
        <div class="option_item">
          <img class="opt_icon" src="@/assets/makeVideo/lan_icon.png" alt="" />
          <span class="opt_text">语言</span>

是什么: 双向绑定语法糖,等价于 :modelValue + @update:modelValue。

面试 Q&A:

❓ v-model 的实现原理?
💡 编译器将 v-model 展开为 :modelValue='val' 和 @update:modelValue='val=$event'。组件内用 defineProps(['modelValue']) + defineEmits(['update:modelValue']) 配合使用。
❓ Vue3 和 Vue2 的 v-model 有什么变化?
💡 Vue2 用 :value + @input,修改靠 .sync;Vue3 统一为 :modelValue + @update:modelValue,支持多个 v-model(v-model:title、v-model:content),去掉了 .sync。

v-if / v-else

项目中的用法:

📂 来自 2 个文件(点击展开)
  • src/views/commentAnalysis/components/right_content/index.vue
  • src/views/home/index.vue

src/views/commentAnalysis/components/right_content/index.vue

  <div class="right_content">
    <!-- 图生图任务 -->
    <div v-if="activeTask?.taskType === 'imgGen'" class="img_gen_task">
      <div class="card_box">
        <div class="row_type">
          <div class="type_icon">
            <img src="@/assets/commentAnalysis/pic_gener.png" alt="" />
          </div>
          <span class="type_text">图片生成</span>
        </div>

src/views/home/index.vue

<template>
   <div class="home_page">
      <div v-if="!showWinCustomTitleBar" class="mac-drag-region" />
      <ZzbLayoutHead v-if="showWinCustomTitleBar" />
      <HandlerBar /> 
      <Content />
   </div>
</template>

<script setup>

是什么: 条件渲染,false 时节点从 DOM 移除并销毁组件,适合不频繁切换的场景。

面试 Q&A:

❓ v-if 和 v-show 如何选择?
💡 频繁切换用 v-show(只改 display,开销小);初始条件为假且不常切换用 v-if(减少初始渲染开销)。v-if 切换时组件会销毁重建,触发完整生命周期。
❓ v-if 和 v-for 的优先级?
💡 Vue3 中 v-if 优先级高于 v-for(Vue2 相反)。不建议同时使用,应用 template 包裹 v-for,在内部元素上用 v-if,或用 computed 过滤数据。

🗺️ Vue Router

useRouter()

项目中的用法:

📂 来自 4 个文件(点击展开)
  • src/views/commentAnalysis/components/right_content/index.vue
  • src/views/home/components/content/index.vue
  • src/views/home/index.vue
  • src/views/makeVideo/components/header/index.vue

src/views/home/components/content/index.vue


<script setup>
// ← 重点:useRouter
import { useRouter } from "vue-router"
import { useUserStore } from "@/store/member_store"
import handler from './handler'
import { CDN_BASE } from '@/constants'

// ← 重点:useRouter
const router = useRouter()
const userStore = useUserStore()
const CardList = handler.CardList

src/views/commentAnalysis/components/right_content/index.vue

import { message } from "ant-design-vue"
import { DownloadOutlined, ZoomInOutlined } from "@ant-design/icons-vue"
// ← 重点:useRouter
import { useRouter, useRoute } from "vue-router"
import imgPlaceholder from "@/assets/commentAnalysis/pic.png"
import handler from "@/views/commentAnalysis/handler"
import makeVideoHandler from "@/views/makeVideo/components/header/handler"

// ← 重点:useRouter
const router = useRouter()
const route = useRoute()
const initRatio = computed(() => route.query.ratio || "")

src/views/makeVideo/components/header/index.vue

<script setup>
import { message } from "ant-design-vue"
// ← 重点:useRouter
import { useRouter } from "vue-router"
import bosConfig from "@/util/bos_util/bos_config_hsy"
import handler from "./handler"

// ← 重点:useRouter
const router = useRouter()
const fileInputRef = ref(null)
const promptText = ref("将产品主图进行优化,生成一张场景化的商品图")
const uploadedList = ref([])

src/views/home/index.vue

);

// ← 重点:useRouter
const router = useRouter();

const handleKeyDown = (e) => {
  if (e.ctrlKey && e.key.toLowerCase() === "t") {
    e.preventDefault();
    router.push("/fyTest");
  }
};

是什么: 获取路由实例,用于编程式导航 push/replace/go/back。

面试 Q&A:

❓ push 和 replace 的区别?
💡 push 往历史记录栈推入新记录,可以后退;replace 替换当前记录,不增加历史栈,无法后退到替换前的页面。登录后跳转首页通常用 replace,避免用户后退回登录页。
❓ 导航守卫如何做权限控制?
💡 `router.beforeEach((to, from, next) => { if (to.meta.requiresAuth && !isLogin) next('/login'); else next() })`,在路由 meta 中标记需要认证的页面,守卫中统一拦截。

useRoute()

项目中的用法:

📂 来自 1 个文件(点击展开)
  • src/views/commentAnalysis/components/right_content/index.vue

src/views/commentAnalysis/components/right_content/index.vue

import { message } from "ant-design-vue"
import { DownloadOutlined, ZoomInOutlined } from "@ant-design/icons-vue"
// ← 重点:useRoute
import { useRouter, useRoute } from "vue-router"
import imgPlaceholder from "@/assets/commentAnalysis/pic.png"
import handler from "@/views/commentAnalysis/handler"
import makeVideoHandler from "@/views/makeVideo/components/header/handler"

// ← 重点:useRoute
const router = useRouter()
// ← 重点:useRoute
const route = useRoute()
const initRatio = computed(() => route.query.ratio || "")

是什么: 获取当前路由对象:params、query、path、meta、name 等。

面试 Q&A:

❓ params 和 query 的区别?
💡 params 是路径参数(/user/:id),必须在路由定义中声明,刷新后仍存在(history 模式);query 是查询字符串(?id=1),无需声明,URL 中可见,刷新后仍存在。
❓ 路由参数变化但组件不更新怎么解决?
💡 同一组件复用时不会重新挂载。解决:① watch(() => route.params, handler) ② 给 router-view 加 :key='route.fullPath' 强制重建。

📡 组件通信

defineProps()

项目中的用法:

📂 来自 2 个文件(点击展开)
  • src/components/zzb_kaip/index.vue
  • src/views/commentAnalysis/components/right_content/index.vue

src/views/commentAnalysis/components/right_content/index.vue

const route = useRoute()
const initRatio = computed(() => route.query.ratio || "")
// ← 重点:defineProps
const props = defineProps({
  activeTask: { type: Object, default: null },
  reportReady: { type: Boolean, default: false },
  polling: { type: Boolean, default: false },
  imgGenResult: { type: Object, default: null }
})

const imgGenParams = computed(() => props.imgGenResult?.Params ?? null)

src/components/zzb_kaip/index.vue

}

// ← 重点:defineProps
const props = withDefaults(defineProps<LiquidEtherProps>(), {
    mouseForce: 20,
    cursorSize: 100,
    isViscous: false,
    viscous: 30,
    iterationsViscous: 32,
    iterationsPoisson: 32,
    dt: 0.014,

是什么: 声明组件接收的 props,父传子的核心方式,支持类型约束和默认值。

面试 Q&A:

❓ Vue3 有哪些组件通信方式?
💡 ① props/emit(父子)② v-model(双向)③ ref + defineExpose(父调子方法)④ provide/inject(跨层级)⑤ Pinia(全局状态)⑥ mitt 事件总线(任意组件)
❓ props 是单向数据流,子组件能直接修改吗?
💡 不能直接修改,会报警告。正确做法:① emit 通知父组件修改 ② 将 prop 赋值给本地 ref 再修改本地数据 ③ 使用 v-model。

defineEmits()

项目中的用法:

📂 来自 1 个文件(点击展开)
  • src/views/commentAnalysis/components/right_content/index.vue

src/views/commentAnalysis/components/right_content/index.vue

const truncateName = (name) => handler.truncateFileName(name, 20)

// ← 重点:defineEmits
const emit = defineEmits(["viewReport"])
const onViewReport = () => emit("viewReport")

const imgPreviewOpen = ref(false)
const imgPreviewUrl = ref("")
const openImgPreview = (url) => {
  imgPreviewUrl.value = url
  imgPreviewOpen.value = true

是什么: 声明组件可触发的自定义事件,子传父的核心方式。

面试 Q&A:

❓ emit 和 v-model 的关系?
💡 v-model 本质是 `:modelValue + @update:modelValue` 的语法糖。子组件用 defineEmits(['update:modelValue']),然后 emit('update:modelValue', newVal) 即可实现双向绑定。

🔌 生命周期

onMounted()

项目中的用法:

📂 来自 3 个文件(点击展开)
  • src/components/zzb_kaip/index.vue
  • src/views/home/index.vue
  • src/views/makeVideo/components/header/index.vue

src/views/makeVideo/components/header/index.vue

}

// ← 重点:onMounted
onMounted(() => {
  const raw = sessionStorage.getItem(handler.SCENE_IMG_EDIT_KEY)
  if (!raw) return
  sessionStorage.removeItem(handler.SCENE_IMG_EDIT_KEY)
  const data = JSON.parse(raw)
  if (data.imgUrl?.length) uploadedList.value = [...data.imgUrl]
  if (data.prompt != null) promptText.value = data.prompt
  if (data.ratio) ratioVal.value = data.ratio

src/views/home/index.vue

};

// ← 重点:onMounted
onMounted(() => {
  window.addEventListener("keydown", handleKeyDown);
});

onUnmounted(() => {
  window.removeEventListener("keydown", handleKeyDown);
});

src/components/zzb_kaip/index.vue

<script setup lang="ts">
import * as THREE from 'three';
// ← 重点:onMounted
import { onMounted, onUnmounted, ref, watch } from 'vue';

interface LiquidEtherProps {
    mouseForce?: number;
    cursorSize?: number;
    isViscous?: boolean;
    viscous?: number;
    iterationsViscous?: number;

是什么: 组件挂载到真实 DOM 后执行,可安全操作 DOM、发起请求。

面试 Q&A:

❓ Vue3 生命周期和 Vue2 的对应关系?
💡 beforeCreate/created → setup();beforeMount → onBeforeMount;mounted → onMounted;beforeUpdate → onBeforeUpdate;updated → onUpdated;beforeDestroy → onBeforeUnmount;destroyed → onUnmounted。
❓ 为什么不在 setup 顶层直接操作 DOM?
💡 setup 执行时组件还未挂载,DOM 不存在。需要在 onMounted 里操作 DOM,此时模板已渲染完毕。

onUnmounted()

项目中的用法:

📂 来自 2 个文件(点击展开)
  • src/components/zzb_kaip/index.vue
  • src/views/home/index.vue

src/views/home/index.vue

});

// ← 重点:onUnmounted
onUnmounted(() => {
  window.removeEventListener("keydown", handleKeyDown);
});

</script>

<style lang="less" scoped> 
.home_page {

src/components/zzb_kaip/index.vue

<script setup lang="ts">
import * as THREE from 'three';
// ← 重点:onUnmounted
import { onMounted, onUnmounted, ref, watch } from 'vue';

interface LiquidEtherProps {
    mouseForce?: number;
    cursorSize?: number;
    isViscous?: boolean;
    viscous?: number;
    iterationsViscous?: number;

是什么: 组件销毁后执行,必须在此清除定时器、取消订阅,防止内存泄漏。

面试 Q&A:

❓ 不清除定时器会发生什么?
💡 组件销毁后定时器仍在运行,回调中访问已销毁组件的数据会报错,且定时器无法被 GC 回收,导致内存泄漏。每次重新挂载组件还会叠加创建新定时器。