Vue3全家桶学习笔记
系统学习 Vue | Web开发 0 366

语法备忘

基础语法

  • 文件构成:
    • <script setup></script>: 组合式API
      • 自动async
    • <style scoped></style>: 单个Vue文件中的样式
  • Vue Directives(指令):
    • v-if="showModal": 用来确定元素/组件是否显示,
      • 可以做到彻底消失,而不是v-show那样的display: none
      • 在不关心SEO时使用
      • v-elsev-else-if搭配使用
    • v-model="newNote": 实现双向绑定,支持v-model.trim="newNote"
      • 如果要处理的是文件,推荐使用@change监听文件变化
        <input type="file" @change="handleChange">
          handleChange(event) {
            // 'event.target.files' 包含了被选中的文件列表
            const file = event.target.files[0];
            // 你可以在这里处理文件,例如上传到服务器或读取文件内容
          }
    • v-for="(note,ind) in notes": 实现可迭代对象的渲染
      • 可以设置key来加速渲染:key="note.id"
    • v-bind: 简写为:,用来在元素属性上支持JS
      • :style="{ backgroundColor: note.backgroundColor }
    • v-on: 简写为@,用来在元素上添加事件监听【支持event以外的参数,还可以通过defineEmits自定义事件用于组件间信息传递】
      • <button v-on:click="doThis"></button>
      • <button @click="doThis"></button>
    • v-html: 用于将数据作为HTML解释并插入到DOM元素中【可能会导致XSS漏洞】
  • vue中的工具:
    • ref: 更适合处理简单的数据类型
      • 保存各种类型的变量为状态
      • 通过.value获取数据
      • 可以更新整个对象
    • reactive: 更适合处理复杂的数据结构
      • 不能放scalar,只能放对象
      • 可以直接获得量,不需要.value
      • 只能更新属性【因为定义为了const
    • watch: 检测状态变化,也可以间接解决ref的无传递性【B ref A,A变B不变】
      const counter = ref(0);
      watch(counter, (newValue, oldValue) => {
        console.log(`Counter changed from ${oldValue} to ${newValue}`);
      });
      • 参数1: 返回单个值的函数、ref、响应式对象、由以上类型的值构成的数组
      • 参数2: 在发生变化时要调用的回调函数。这个回调函数接受三个参数:新值、旧值,以及一个用于注册副作用清理的回调函数。
    • computed: 直接实现ref的传递性【参数为为无参函数】
      const counter = ref(0);
      const doubledCounter = computed(() => counter.value * 2);

路由相关

  • createRouter:
    • createWebHistory: 默认的history属性
    • children: 层叠情况【访问子路径才显示某个View】
    • redirect: "/": 重定向
  • RouterView: 用来根据url切换视图
  • <RouterLink to="/">Home</RouterLink>: 用来显示路由的url【避免刷新,理论上只有外链才需要用到<a>
    • active-class: 用来传递被激活页面的class
  • const route=useRoute(): 这样可以获取当前路由的信息
    • route.params: 参数【都会转化为str,很多时候需要parseInt
  • const router=useRouter(): 用来操作路由【能处理更多需求】
    • router.push(/cars/${car.id})

动画相关

  • 动画四要素: 起始状态、终止状态、时长、动画类型
  • 动画的生命周期: before-xxxxxxafter-xxx
  • 用法:
    • 常见动画类型: enterleavemove
    • 单组件情况:
      • <Transition name="color"></Transition>
      • .color-enter-from.color-enter-to.color-enter-active{transition: all 5s ease}
    • 多组件情况:【v-if-else仍属于单组件】
      • TransitionGroup
    • appear: 直接启动enter动画
    • JS动画gsap:
      import gsap from "gsap"
      gsap.to(el, {
          y: 0,
          opacity: 1,
          duration: 0.3,
          delay: el.dataset.index * 0.3
      })

组件相关

  • 组件的生命周期/钩子函数:
    • beforeCreate: 数据、视图都未就绪
    • created: 数据就绪、视图未就绪
    • beforeMount: 数据视图都就绪,但是数据还未关联到视图上
    • mounted: 初始化完成
    • beforeUpdate
    • updated
    • beforeUnmount
    • unmounted
  • slot:
    • 用法:
      • 在组件模版中加入<slot></slot>,调用组件时,中间的innerHTML就会替代slot的位置
      • 组件内用name="header",调用时用<template #header></template>[v-slot]
  • defineProps: 组件属性
    • const props = defineProps(['prop1'])
    • const {prop1} = defineProps(['prop1'])
    • <Card :prop1="prop1"/>
  • defineEmits: 组件通过事件传递信息到父一级
    • 子组件:
      const emit = defineEmits(['selectOption']);
      const emitSelectedOption = (isCorrect)=> emit("selectOption", isCorrect);
      @click="emitSelectedOption(option.isCorrect)"
    • 父组件:
      @selectOption="onOptionSelected()"
  • 通用组件说明:
    1. Container: 宽度高度限制(响应式)
    2. Card: 图片、标题、说明
    3. Modal: 对话框/模态窗

请求相关

  • axios:
    • 用法:
      • import axios from "axios"
      • const response = await axios.get(url)
  • 组件加载:
    • <Suspense></Suspense>: setup中直接使用request需要
      <Suspense>
          <template #default>
              <Card />
          </template>
          <template #fallback>
              <div>
                  <p>Loading...</p>
              </div>
          </template>
      </Suspense>
    • onMountedonBeforeMount
  • 开发环境中用代理实现跨域
    • 原理: 同源策略只存在于浏览器上,因此使用代理,将前端请求发给跟前端同源的代理服务器上,由代理服务器向后端发起请求
    • 用法:
      export default defineConfig({
        server: {
          proxy: {
            "/api": {
              secure: false,
              target: "http://127.0.0.1:8091",
              changeOrigin: true,
              // rewrite: (path) => path.replace(/^\/api/, ""),
            },
          },
        },
        ...
      })

状态管理

  • KeepAlive: 用来保存组件的状态
    • 使用:
      <KeepAlive>
      <Component :is="isBreakingBad ? BreakingBadCards : RickAndMortyCards" />
      </KeepAlive>
  • 状态的隔组件传递: e.g. A->B->C,A跟C用一个状态
  • 解决方法:
    1. Prop Drilling(属性钻取): 通过prop依次传递【显然很混乱且麻烦】
    2. Provide/Inject:
      • 使用:
        import {provide} from "vue";
        provide("numbers", ref([1,2,3]))
        ```js import {inject} from "vue" const numbers = inject("numbers")
      • 缺陷:
        1. 对状态的操作(函数)没法传递
        2. 只从Provide方读时才不混乱
    3. Composables:
      • 使用:
        1. src下创建composables目录,创建useNumbers.js
        2. 编写composable
          import {ref} from "vue";
          export default ()=>{
              const numbers = ref([1,2,3])
              const addNumber = (num) => {
                  numbers.value.push(num)
              } 
             const filterNumber = (minNum) => {
                  return numbers.value.filter(
                      num => num >= minNum
                  )
              }
              return {
                  numbers,
                  addNumber,
                  filterNumber
              }
          }
        3. 调用
          import useNumbers from "../composables/useNumbers";
          const {numbers, addNumber, filterNumber} = useNumbers()
      • 缺点: 这样的状态只会是同名,但不能同步
    4. Pinia:

      • 优点:

        • 提供方便的开发工具
        • 支持热重载
        • 支持大量插件
        • 提供更好的TS支持
        • 支持SSR(Side Rendering Support)
      • 使用:

        1. src下创建stores目录,创建numbers.js
        2. main.js中插入pinia
          ...
          import {createPinia} from "pinia"
          const pinia = createPinia()
          app.use(pinia)
          ...
        3. 编写store
          import {defineStore} from 'pinia'
          export const useNumbersStore = defineStore("numbers", {
              state: ()=>{
                  return {
                      numbers: [1,2,3]
                  }
              },
              actions: {
                  addNumber(num){
                      this.numbers.push(num)
                  }
              },
              getters: {
                  // 这个是一个属性
                  doubleNumber: (state)=>{
                      return state.numbers.map( num => num*2),
                  }
                  // 这个是一个闭包
                  filterNumber: (state)=>{
                      return (minNumber)=>state.numbers.filter(num=>num>=minNumber)
                  }
              }
          })
        4. 调用store
          import { useNumbersStore } from "../stores/numbers";
          const {numbers, addNumber, doubleNumber} = useNumbersStore()
        5. 监控store【一旦const attr = store.attrattr就不再是响应式的】
          import {storeToRefs} from "pinia"
          const userStore = useUserStore() 
          const { errorMessage } = storeToRefs(userStore)

Type相关

  • Types With State:
    // 标量
    const name = ref<string>("")
    // 对象
    interface Intivee {...}
    const invitees = ref<Intivee[]>([{...},{...}])
  • Types With Props:
    enum GENDER {...}
    defineProps<{
        invitee: {
            id: number,
            name: string,
            gender: GENDER,
        }
    }>()
  • Types With Computed
    const count = computed<{
        female: number,
        male: number,
    }>(()=>{
        return invitees.value.reduce((countObj, invitee)=>{
            ...
        })
    }, {male: 0, female: 0}) 
  • Type的导入导出
    1. 创建目录src/types,并创建src/types/invitee.ts
    2. 编辑*.ts
      enum GENDER {...}
      interface Invitee {...}
      export {GENDER, type Invitee}
    3. 导入type
      import {GENDER, type Invitee} from "./types/invitee.ts"

实用工具@vueuse

useHead

常见问题

状态

computed与watch

如果同时使用watchcomputed来监控同一个ref,那么computed会先于watch执行。

这是因为computed是基于响应式数据进行计算,它会在依赖的响应式数据发生变化时重新计算,并将结果缓存起来。所以,当你在computed中使用一个ref作为依赖时,它会先计算出结果。

watch则是通过监听特定的属性或表达式来执行相应的操作,如果你将一个ref作为要监视的属性,它会在该ref的值发生变化时执行回调函数。

ref与reactive

  • ref可以整体重新赋值,reactive不可以(相当于加了final)【主要特点】
  • ref需要.value访问值,reactive不需要

路由

任意路由处理:catchall

在常见的路由匹配模式中,通常会使用类似于 /:catchall(.*) 的语法来定义一个捕获所有参数的路由。然而,在这个特定的表达式中,为了更好地处理路径中的斜线 /,尤其是在Vue Router中,需要使用另一种形式来定义这个路由。

/:catchall(.*)*是一种为捕获所有路径参数的特殊形式。它将匹配以/开头的任意长度的路径,并将路径中的所有内容都捕获到catchall参数中。

为什么不直接使用/:catchall(.*)呢?这是因为在默认情况下,Vue Router会忽略路径参数中的斜线,它们被视为分隔符,而不是属于参数的一部分。这可能会导致捕获的参数不包括斜线。

使用/:catchall(.*)*形式的路由模式,我们明确地告诉Vue Router包括斜线在内的所有字符都属于`catchall 参数的一部分,这样可以确保捕获所有的路径参数。

因此,/:catchall(.*)* 的形式在处理包含斜线的路径参数时更可靠。

动画

.fade-leave-active 中的postion: absolute

在Vue动画中,.fade-leave-active是CSS过渡类名之一,用于定义在元素离开过渡期间的样式。

position: absolute;这一属性通常与动画和过渡一起使用,可以产生一些特定的效果。具体而言,在.fade-leave-active类中使用position: absolute;可以让元素在离开过渡过程中保持绝对定位。这样做的好处是能够确保元素在离开过渡期间不会影响其它元素的布局,而只占用自身在文档流中的空间。

当在Vue动画中使用.fade-leave-active中的position: absolute;时,元素会从文档流中移出,并以绝对定位的方式进行动画。这意味着该元素不再影响其它元素的布局,而是独立显示和动画。这对于实现一些特定的动画效果非常有用,例如淡出动画、滑动动画等。

需要注意的是,.fade-leave-active类仅在元素离开过渡期间应用,并且通常与.fade-leave类一起使用。.fade-leave类定义了元素开始离开过渡时的初始状态,然后在过渡过程中切换到.fade-leave-active类,最后结束过渡时切换到.fade-leave-to类。

综上所述,position: absolute;.fade-leave-active中的使用可以帮助你在 Vue 动画中实现元素从文档流中移出,并以绝对定位进行动画的效果。但具体如何使用这些类取决于你的动画需求和布局结构。

环境

环境变量

参考: https://juejin.cn/post/7172012247852515335

  • process.env: 【仅在vite.config.ts中使用】
    • 属于Node.js环境的全局对象,包含了当前Shell的所有环境变量
    • 用于在构建时访问环境变量,比如定义不同的构建环境
  • import.meta.env:
    • 这是ES模块的一个元数据属性
    • 用于在运行时访问环境变量【自定义变量只能访问VITE_开头的】

编译的不同mode

  • 开发模式(development):
    • 开启Source Maps: 它可以将编译后的代码映射回原始源代码,使得在浏览器的开发者工具中进行调试更容易和准确。
    • 不进行代码压缩和混淆: 在开发时,保留可读性较好的代码结构,方便调试和理解。
    • 配置Mock数据: 当后端API还未开发完成时,你可以使用Mock数据来模拟后端接口返回的数据,便于前端开发和测试。
  • 生产模式(production):
    • 代码压缩和混淆: 通过去除注释、无用代码和空格等方式,减小文件大小,提高加载速度。
    • 图片压缩和优化: 对于静态资源中的图片进行压缩和优化,减小文件大小,提高加载性能。
    • 开启缓存和Tree Shaking: 通过合理配置缓存策略和利用Tree Shaking(只打包使用到的代码),减少文件的加载和执行时间。
    • 配置正确的基础URL和域名: 确保在生产环境中使用正确的后端API URL和域名。

技术栈融合

Vue + tailwindCSS + daisyUI

  1. 安装依赖
    yarn add -D tailwindcss postcss autoprefixer daisyui
  2. 生成Tailwind CSS配置文件
    npx tailwindcss init -p
  3. 配置Tailiwind CSS与daisyUI插件
    /** @type {import('tailwindcss').Config} */
    module.exports = {
      content: [
        "./index.html",
        "./src/**/*.{vue,js,ts,jsx,tsx}",
      ],
      theme: {
        extend: {},
      },
      plugins: [require("daisyui")],
    }
  4. 创建tailwindcss.css样式文件,导入基础样式
    /* /src/assets/css/tailwindcss.css */
    @tailwind base;
    @tailwind components;
    @tailwind utilities;
  5. 导入Tailwind CSS
    // src/main.js
    import "./assets/css/tailwindcss.css"
    import { createApp } from 'vue'
    import App from './App.vue'
    createApp(App).mount('#app')
  6. App.vue中测试
    <template>
      <div class=" md:mx-auto relative bg-white overflow-hidden border-b">
        <div class="h-12 md:ml-10 md:pr-4 md:space-x-8 flex justify-center">
          <a href="#" class="font-medium text-gray-500 hover:text-gray-900 flex items-center">Home</a>
          <a href="#" class="font-medium text-gray-500 hover:text-gray-900 flex items-center">Product</a>
          <a href="#" class="font-medium text-gray-500 hover:text-gray-900 flex items-center">Article</a>
          <a href="#" class="font-medium text-gray-500 hover:text-gray-900 flex items-center">About</a>
          <a href="#" class="font-medium text-indigo-600 hover:text-indigo-500 flex items-center">Log in</a>
        </div>
      </div>
    </template>

Vue + Sass

  1. 安装依赖
    yarn add -D sass
  2. *.vue中使用
    <style scoped lang="scss">
    ...
    </style>

Vue + TypeScript

  1. npm init vue@latest时启用TypeScript
  2. <script setup lang="ts"></script>

Vue + FontAwesome

  1. 安装依赖
    yarn add @fortawesome/fontawesome-svg-core @fortawesome/free-solid-svg-icons @fortawesome/vue-fontawesome
  2. 导入依赖【按需导入参考https://fontawesome.com/
    import { createApp } from 'vue'
    import App from './App.vue'
    /* import the fontawesome core */
    import { library } from '@fortawesome/fontawesome-svg-core'
    /* import font awesome icon component */
    import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
    /* import specific icons */
    import { faUserSecret, faTrash } from '@fortawesome/free-solid-svg-icons'
    /* add icons to the library */
    library.add(faUserSecret, faTrash)
    
    /* 完全导入 */
    // import { fas } from '@fortawesome/free-solid-svg-icons';
    // library.add(fas);
    
    import './assets/main.css'
    createApp(App)
        .component('font-awesome-icon', FontAwesomeIcon)
        .mount('#app')
  3. 组件中使用
    <font-awesome-icon icon="fa-solid fa-trash" />
编写
预览