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

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

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

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

我也希望自己一直不忘初心,帖子为证,只要我一天在工位上,都会定时给大家分享开发收获和经验!!! 💪
🏢 前后端开发工程师 📅 每日更新 🎯 从项目代码提炼知识点 🤝 欢迎点赞收藏

本次共识别 18 个知识点,覆盖 8 个分类。
📂 提取来源(共 9 个文件,点击展开)

文件路径 识别到的知识点
src/App.vue script setup, v-slot / #slot
src/components/zzb_kaip/index.vue script setup, script setup + TypeScript, onUnmounted(), onMounted(), defineProps(), v-slot / #slot, watch()
src/components/zzb_layout_head/index.vue script setup, ref(), computed(), onMounted(), v-slot / #slot, useRoute()
src/components/zzb_member_info/index.vue script setup, computed(), defineProps(), v-slot / #slot, v-show, useRouter()
src/store/member_store.js defineStore()
src/views/home/components/content/index.vue script setup, ref(), computed(), onUnmounted(), v-model, v-slot / #slot, v-if / v-else, useRouter()
src/views/home/index.vue script setup, computed(), onUnmounted(), onMounted(), v-if / v-else, useRouter()
src/views/member/components/account_info/components/logout/index.vue script setup, ref(), defineExpose(), v-model, v-slot / #slot, useRouter()
src/views/member/components/account_info/components/modify_password/index.vue script setup, ref(), reactive(), onBeforeUnmount(), defineExpose(), v-model, v-slot / #slot, useRouter()

目录

  • [🚀 Vue3 基础](#🚀 Vue3 基础)
  • [⚡ 响应式 API](#⚡ 响应式 API)
  • [🔌 生命周期](#🔌 生命周期)
  • [📡 组件通信](#📡 组件通信)
  • [🎯 模板指令](#🎯 模板指令)
  • [🗺️ Vue Router](#🗺️ Vue Router)
  • [👁️ 侦听器](#👁️ 侦听器)
  • [🍍 Pinia](#🍍 Pinia)

🚀 Vue3 基础

script setup

项目中的用法:
📂 来自 8 个文件(点击展开)

  • src/App.vue
  • src/components/zzb_kaip/index.vue
  • src/components/zzb_layout_head/index.vue
  • src/components/zzb_member_info/index.vue
  • src/views/home/components/content/index.vue
  • src/views/home/index.vue
  • src/views/member/components/account_info/components/logout/index.vue
  • src/views/member/components/account_info/components/modify_password/index.vue

src/views/member/components/account_info/components/modify_password/index.vue

vue 复制代码
</template>

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

const router = useRouter()

src/App.vue

vue 复制代码
</template>

// ← 重点:script setup
<script setup>
import { USER_STATE_KEY } from "@/constants";
import { useUserStore } from "@/store/member_store";
import "./util/plug_util";
import "./util/co_util";
import "dayjs/locale/zh-cn";

const userStore = useUserStore();

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

vue 复制代码
</template>

// ← 重点:script setup
<script setup>
import { useRouter } from "vue-router"
import { message } from "ant-design-vue"
import { useUserStore } from "@/store/member_store"
import handler from "./handler"
import mainCenterUrl from "@/assets/home/main_center.png"
import homeTitleUrl from "@/assets/home/homr_title.png"
import phoneIconUrl from "@/assets/home/phone.png"

src/views/member/components/account_info/components/logout/index.vue

vue 复制代码
</template>

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

const router = useRouter()
const userStore = useUserStore()
const visible = ref(false)

src/views/home/index.vue

vue 复制代码
</template>

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

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

const router = useRouter();

src/components/zzb_layout_head/index.vue

vue 复制代码
    </div>
</template>
// ← 重点:script setup
<script setup>
const route = useRoute()
const isHomePage = computed(() => route.path === "/home")

const isMaximized = ref(false);

// 最小化窗口
const minimizeWindow = async () => {

src/components/zzb_kaip/index.vue

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;

src/components/zzb_member_info/index.vue

vue 复制代码
  </template>

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

  const router = useRouter()

  defineProps({
    collapsed: { type: Boolean, default: false },

是什么: 组合式 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

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

ref()

项目中的用法:
📂 来自 4 个文件(点击展开)

  • src/components/zzb_layout_head/index.vue
  • src/views/home/components/content/index.vue
  • src/views/member/components/account_info/components/logout/index.vue
  • src/views/member/components/account_info/components/modify_password/index.vue

src/views/member/components/account_info/components/modify_password/index.vue

vue 复制代码
  >
    <a-form
// ← 重点:ref
      ref="formRef"
      class="modify_pwd_form"
      :model="formState"
      :rules="formRules"
      layout="horizontal"
      :label-col="{ span: 6 }"
      :wrapper-col="{ span: 18 }"
    >

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

vue 复制代码
const userStore = useUserStore()

// ← 重点:ref
const phone = ref("")
// ← 重点:ref
const code = ref("")
// ← 重点:ref
const phoneFocused = ref(false)
// ← 重点:ref
const codeFocused = ref(false)
// ← 重点:ref
const countdown = ref(0)
// ← 重点:ref
const loading = ref(false)

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

src/views/member/components/account_info/components/logout/index.vue

vue 复制代码
const router = useRouter()
const userStore = useUserStore()
// ← 重点:ref
const visible = ref(false)

const open = () => { visible.value = true }

defineExpose({ open })

const onConfirm = () => {
  visible.value = false

src/components/zzb_layout_head/index.vue

vue 复制代码
const isHomePage = computed(() => route.path === "/home")

// ← 重点:ref
const isMaximized = ref(false);

// 最小化窗口
const minimizeWindow = async () => {
    const winInfo = await PU.GetMainWindowInfo();
    PU.MinimizeWindow(winInfo.winId);
};

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

面试 Q&A:

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


reactive()

项目中的用法:
📂 来自 1 个文件(点击展开)

  • src/views/member/components/account_info/components/modify_password/index.vue

src/views/member/components/account_info/components/modify_password/index.vue

vue 复制代码
const SMS_COUNTDOWN_SECONDS = 60;

// ← 重点:reactive
const formState = reactive({
  phone: "",
  code: "",
  newPwd: "",
  confirmPwd: "",
});

const formRules = {

是什么: 将对象/数组包装为响应式,直接访问属性,但解构会丢失响应性。

面试 Q&A:

❓ reactive 解构为什么丢失响应性?如何解决?
💡 解构相当于把属性值复制出来,脱离了 Proxy 的拦截。解决方法:用 toRefs(state) 解构,每个属性变成 ref,保持与原对象的响应式连接。
❓ reactive 和 ref 底层实现有何不同?
💡 reactive 基于 ES6 Proxy,拦截对象的 get/set;ref 基于 RefImpl 类,通过 .value 的 getter/setter 触发依赖收集,内部如果是对象会调用 reactive。


computed()

项目中的用法:
📂 来自 4 个文件(点击展开)

  • src/components/zzb_layout_head/index.vue
  • src/components/zzb_member_info/index.vue
  • src/views/home/components/content/index.vue
  • src/views/home/index.vue

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

vue 复制代码
const loading = ref(false)

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

let timer = null

const handleSendCode = async () => {
  if (phone.value.length < 11) return message.warning("请输入正确的手机号")
  if (countdown.value > 0) return
  const res = await handler.SendCode(phone.value)

src/views/home/index.vue

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") {

src/components/zzb_layout_head/index.vue

vue 复制代码
<script setup>
const route = useRoute()
// ← 重点:computed
const isHomePage = computed(() => route.path === "/home")

const isMaximized = ref(false);

// 最小化窗口
const minimizeWindow = async () => {
    const winInfo = await PU.GetMainWindowInfo();
    PU.MinimizeWindow(winInfo.winId);

src/components/zzb_member_info/index.vue

vue 复制代码
  const userStore = useUserStore();

// ← 重点:computed
  const memberLogo = computed(() => userStore.memberLogo);
// ← 重点:computed
  const memberName = computed(() => userStore.NickName || userStore.UserName || "-");
// ← 重点:computed
  const memberCredit = computed(() => userStore.Credit ?? 0);

  const toMember = () =>  {
    router.push('/member')
  }
  </script>

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

面试 Q&A:

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


🔌 生命周期

onBeforeUnmount()

项目中的用法:
📂 来自 1 个文件(点击展开)

  • src/views/member/components/account_info/components/modify_password/index.vue

src/views/member/components/account_info/components/modify_password/index.vue

vue 复制代码
}

// ← 重点:onBeforeUnmount
onBeforeUnmount(() => {
  clearCountdownTimer();
});

function open() {
  resetForm();
  submitting.value = false;
  sendLoading.value = false;

是什么: 组件卸载前执行,此时实例仍完整可用,适合做最后的清理。

面试 Q&A:

❓ onBeforeUnmount 和 onUnmounted 的区别?
💡 onBeforeUnmount 时组件还未卸载,DOM 和响应式数据仍可访问,适合做清理准备;onUnmounted 时组件已完全销毁,DOM 已移除,只适合做最后的资源释放。


onUnmounted()

项目中的用法:
📂 来自 3 个文件(点击展开)

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

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

vue 复制代码
const handleGoMember = () => router.push("/member").catch(() => {})

// ← 重点:onUnmounted
onUnmounted(() => {
  if (timer) clearInterval(timer)
})
</script>

<style lang="less" scoped>
.content {
  width: 100vw;

src/views/home/index.vue

vue 复制代码
});

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

</script>

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

src/components/zzb_kaip/index.vue

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 回收,导致内存泄漏。每次重新挂载组件还会叠加创建新定时器。


onMounted()

项目中的用法:
📂 来自 3 个文件(点击展开)

  • src/components/zzb_kaip/index.vue
  • src/components/zzb_layout_head/index.vue
  • src/views/home/index.vue

src/views/home/index.vue

vue 复制代码
};

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

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

src/components/zzb_layout_head/index.vue

vue 复制代码
// 生命周期钩子
// ← 重点:onMounted
onMounted(() => {
    checkMaximizeState();
});

</script>


<style lang="less" scoped>

src/components/zzb_kaip/index.vue

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,此时模板已渲染完毕。


📡 组件通信

defineExpose()

项目中的用法:
📂 来自 2 个文件(点击展开)

  • src/views/member/components/account_info/components/logout/index.vue
  • src/views/member/components/account_info/components/modify_password/index.vue

src/views/member/components/account_info/components/modify_password/index.vue

vue 复制代码
}

// ← 重点:defineExpose
defineExpose({ open });
</script>

<style lang="less" scoped>
.modify_pwd_form {
  margin-top: 8px;
  padding-right: 8px;
}

src/views/member/components/account_info/components/logout/index.vue

vue 复制代码
const open = () => { visible.value = true }

// ← 重点:defineExpose
defineExpose({ open })

const onConfirm = () => {
  visible.value = false
  userStore.$patch({ Token: "", Credit: 0, NickName: "", UserName: "" })
  localStorage.removeItem(USER_STATE_KEY)
  router.push("/home")
}

是什么: 显式暴露组件内部属性/方法,才能被父组件通过 ref 访问。

面试 Q&A:

❓ 为什么 script setup 组件用 ref 拿不到属性?
💡 script setup 默认是封闭的(closed),不像 Options API 组件自动暴露所有实例属性。必须用 defineExpose({ method, data }) 显式声明要暴露的内容。


defineProps()

项目中的用法:
📂 来自 2 个文件(点击展开)

  • src/components/zzb_kaip/index.vue
  • src/components/zzb_member_info/index.vue

src/components/zzb_kaip/index.vue

vue 复制代码
}

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

src/components/zzb_member_info/index.vue

vue 复制代码
  const router = useRouter()

// ← 重点:defineProps
  defineProps({
    collapsed: { type: Boolean, default: false },
  });

  const userStore = useUserStore();

  const memberLogo = computed(() => userStore.memberLogo);
  const memberName = computed(() => userStore.NickName || userStore.UserName || "-");

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

面试 Q&A:

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


🎯 模板指令

v-model

项目中的用法:
📂 来自 3 个文件(点击展开)

  • src/views/home/components/content/index.vue
  • src/views/member/components/account_info/components/logout/index.vue
  • src/views/member/components/account_info/components/modify_password/index.vue

src/views/member/components/account_info/components/modify_password/index.vue

vue 复制代码
<template>
  <a-modal
// ← 重点:v-model
    v-model:open="visible"
    title="修改密码"
    ok-text="确定"
    cancel-text="取消"
    :mask-closable="false"
    :confirm-loading="submitting"
    :style="{ top: '320px' }"
    destroy-on-close

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

vue 复制代码
          </div>
          <input
// ← 重点:v-model
            v-model="phone"
            type="tel"
            class="input_field"
            placeholder="请输入手机号"
            maxlength="11"
            @focus="phoneFocused = true"
            @blur="phoneFocused = false"
          />

src/views/member/components/account_info/components/logout/index.vue

vue 复制代码
<template>
  <a-modal
// ← 重点:v-model
    v-model:open="visible"
    title="确认退出"
    ok-text="确定"
    cancel-text="取消"
    :mask-closable="true"
    :style="{ top: '320px' }"
    @ok="onConfirm"
  >

是什么: 双向绑定语法糖,等价于 :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-slot / #slot

项目中的用法:
📂 来自 7 个文件(点击展开)

  • src/App.vue
  • src/components/zzb_kaip/index.vue
  • src/components/zzb_layout_head/index.vue
  • src/components/zzb_member_info/index.vue
  • src/views/home/components/content/index.vue
  • src/views/member/components/account_info/components/logout/index.vue
  • src/views/member/components/account_info/components/modify_password/index.vue

src/views/member/components/account_info/components/modify_password/index.vue

vue 复制代码
          allow-clear
        >
          <template #suffix>
            <span
              class="send_code_link"
              :class="{ disabled: countdown > 0 || sendLoading }"
              @click="onSendCode"
            >
              {{ countdown > 0 ? `${countdown}s` : "发送验证码" }}
            </span>

src/App.vue

vue 复制代码
  height: 100%;
}
#app {
  margin: 0;
}
</style>

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

vue 复制代码
    <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"

src/views/member/components/account_info/components/logout/index.vue

vue 复制代码
  margin: 0;
  font-size: 14px;
  color: #4b5563;
  line-height: 1.5;
}
</style>

src/components/zzb_layout_head/index.vue

vue 复制代码
    &--colored {
      background: #E6E8F5;
      .window-control {
        color: #333;
      }
    }

    .window-control {
      -webkit-app-region: no-drag;

src/components/zzb_kaip/index.vue

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,

src/components/zzb_member_info/index.vue

vue 复制代码
      flex-shrink: 0;
      object-fit: cover;
      background-color: #fff;
    }
    .text_group {
      flex: 1;
      min-width: 0;
      white-space: nowrap;
      overflow: hidden;
      .username {

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

面试 Q&A:

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


v-if / v-else

项目中的用法:
📂 来自 2 个文件(点击展开)

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

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

vue 复制代码
        <img :src="homeTitleUrl" class="fill_img" />
      </div>
      <template v-if="!isLogin">
        <div class="input_box" :class="{ 'input_box--active': phoneFocused }">
          <div class="input_icon_wrap">
            <img :src="phoneIconUrl" class="fill_img" />
          </div>
          <input
            v-model="phone"
            type="tel"

src/views/home/index.vue

vue 复制代码
<template>
   <div class="home_page">
      <div v-if="!showWinCustomTitleBar" class="mac-drag-region" />
      <ZzbLayoutHead v-if="showWinCustomTitleBar" />
      <Content />
   </div>
</template>

<script setup>
import Content from './components/content/index.vue';

是什么: 条件渲染,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 过滤数据。


v-show

项目中的用法:
📂 来自 1 个文件(点击展开)

  • src/components/zzb_member_info/index.vue

src/components/zzb_member_info/index.vue

vue 复制代码
    <div class="member_info" :class="{ collapsed }"  @click="toMember">
      <img class="avatar" :src="memberLogo">
// ← 重点:v-show
      <div class="text_group" v-show="!collapsed">
        <div class="username">{{ memberName }}</div>
        <div class="stats">
          <span class="stats_label">算力:</span>
          <span class="stats_value">{{ memberCredit }}</span>
        </div>
      </div>
    </div>

是什么: 条件显示,false 时设 display:none,节点始终存在 DOM,切换开销低。

面试 Q&A:

❓ v-show 能用在 `<template>` 标签上吗?
💡 不能。template 是虚拟容器,不渲染为真实 DOM 元素,v-show 需要操作元素的 style,所以无法作用于 template。v-if 可以用在 template 上。


🗺️ Vue Router

useRouter()

项目中的用法:
📂 来自 5 个文件(点击展开)

  • src/components/zzb_member_info/index.vue
  • src/views/home/components/content/index.vue
  • src/views/home/index.vue
  • src/views/member/components/account_info/components/logout/index.vue
  • src/views/member/components/account_info/components/modify_password/index.vue

src/views/member/components/account_info/components/modify_password/index.vue

vue 复制代码
import { useUserStore } from "@/store/member_store";
import handler from "./handler";
// ← 重点:useRouter
import { useRouter } from 'vue-router'

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

const visible = ref(false);
const submitting = ref(false);
const sendLoading = ref(false);

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

vue 复制代码
<script setup>
// ← 重点:useRouter
import { useRouter } from "vue-router"
import { message } from "ant-design-vue"
import { useUserStore } from "@/store/member_store"
import handler from "./handler"
import mainCenterUrl from "@/assets/home/main_center.png"
import homeTitleUrl from "@/assets/home/homr_title.png"
import phoneIconUrl from "@/assets/home/phone.png"
import tiketIconUrl from "@/assets/home/tiket.png"

src/views/member/components/account_info/components/logout/index.vue

vue 复制代码
<script setup>
// ← 重点:useRouter
import { useRouter } from "vue-router"
import { USER_STATE_KEY } from "@/constants"
import { useUserStore } from "@/store/member_store"

// ← 重点:useRouter
const router = useRouter()
const userStore = useUserStore()
const visible = ref(false)

src/views/home/index.vue

vue 复制代码
);

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

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

src/components/zzb_member_info/index.vue

vue 复制代码
  <script setup>
  import { useUserStore } from "@/store/member_store";
// ← 重点:useRouter
  import { useRouter } from 'vue-router'

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

  defineProps({
    collapsed: { type: Boolean, default: false },
  });

是什么: 获取路由实例,用于编程式导航 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/components/zzb_layout_head/index.vue

src/components/zzb_layout_head/index.vue

vue 复制代码
</template>
<script setup>
// ← 重点:useRoute
const route = useRoute()
const isHomePage = computed(() => route.path === "/home")

const isMaximized = ref(false);

// 最小化窗口
const minimizeWindow = async () => {
    const winInfo = await PU.GetMainWindowInfo();

是什么: 获取当前路由对象: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' 强制重建。


👁️ 侦听器

watch()

项目中的用法:
📂 来自 1 个文件(点击展开)

  • src/components/zzb_kaip/index.vue

src/components/zzb_kaip/index.vue

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()` 调用返回值即可停止。组件卸载时会自动停止,手动停止用于在卸载前提前结束侦听。


🍍 Pinia

defineStore()

项目中的用法:
📂 来自 1 个文件(点击展开)

  • src/store/member_store.js

src/store/member_store.js

javascript 复制代码
// ← 重点:defineStore
import {defineStore} from "pinia";
import memberApi from "@/api/fyApi/member";
import { CDN_BASE, USER_STATE_KEY } from "@/constants";

// ← 重点:defineStore
export const useUserStore = defineStore("user", {
    state: () => ({
        memberLogo: `${CDN_BASE}/member_logo.png`,
        Token:'',

是什么: 定义全局状态 store,支持 Options 写法和 Setup 写法。

面试 Q&A:

❓ Pinia 和 Vuex 的核心区别?
💡 ① 无 mutations,直接修改 state ② 天然支持 TypeScript ③ 支持多 store,无嵌套模块 ④ 体积更小(~1KB)⑤ devtools 支持更好 ⑥ 支持 Setup 写法,与 Composition API 一致。
❓ Pinia 如何持久化?
💡 安装 pinia-plugin-persistedstate,`createPinia().use(piniaPluginPersistedstate)`,store 中配置 `persist: true` 即可自动存 localStorage,支持自定义 key 和存储方式。


相关推荐
一拳不是超人3 小时前
前端转全栈:你必须要掌握的 Docker 知识
前端·docker·全栈
C澒3 小时前
微前端容器标准化:接入指南
前端·架构
LXXgalaxy3 小时前
Uni-app 小程序页面跳转带参实战笔记(含对象传参与防坑)
开发语言·前端·javascript
2301_768350233 小时前
Vue指令修饰符
前端·javascript·vue.js
oi..3 小时前
Flag和JavaScript document有关
开发语言·前端·javascript·经验分享·笔记·安全·网络安全
Sgf2273 小时前
2026Web前端进阶学习路线
前端·学习
每天吃饭的羊3 小时前
computed 同时写 get() 和 set()
前端·javascript·vue.js
Highcharts.js3 小时前
Highcharts + TypeScript 集成高级技巧|类型与框架集成实战
前端·javascript·vue.js·react.js·typescript·highcharts·图表生成
芒果8013 小时前
做文档配图太烦?一个小时写了个工具解决
前端