Vue3

组合式API

1.钩子函数steup

  1. 函数的普通用法
<script>export default {  setup() {    return {}  }}</script><template>  </template>
  1. 简写使用setup
<script setup></script><template> </template>

2.响应式API

  1. ref函数
<script setup>import { ref } from 'vue'const state = ref(0)function increment() {  state.value++}</script><template>  <button @click="increment">    {{ state }}  </button></template>
  1. reactive函数
<script setup>import { reactive } from 'vue'const state = reactive({ count: 0 })function increment() {  state.count++}</script><template>  <button @click="increment">    {{ state.count }}  </button></template>

3.计算属性API

  1. 单向响应
<script setup>import { computed,reactive } from 'vue'const Person=reactive({X:'张',M:'三'}) Person.XM=computed(()=>{  return Person.X+'-'+Person.M })</script><template>  :<input v-model="Person.X"><br>  :<input v-model="Person.M"><br>  单向响应:<input v-model="Person.XM"></template>
  1. 双向响应
<script setup>import { computed,reactive } from 'vue'const Person=reactive({X:'张',M:'三'})Person.AXM=computed({  get(){    return Person.X+'-'+Person.M  },  set(value){    const arr=value.split('-')    Person.X=arr[0]    Person.M=arr[1]  }})</script><template>  :<input v-model="Person.X"><br>  :<input v-model="Person.M"><br>  双向响应:<input v-model="Person.AXM"></template>

4.监听属性API

  1. 监听整个对象
<!--  // 监听整个对象,由于是浅拷贝,他们新旧指向的是通一个对象 --><script setup>import {reactive,watch} from 'vue'const Person=reactive({name:'张三',age:18, job:{salary:20}})      watch(Person,(newVal,oldVal)=>{         console.log('用户信息发生了变化',newVal,oldVal);      })</script><template> <h2>年龄:{{Person.age}}</h2> <button @click="Person.age++">+1</button></template>
  1. 监听对象中单个属性
<!-- 监听对象中单个属性,监听单个属性可以检测到新旧值 --><script setup>import {reactive,watch} from 'vue'const Person=reactive({name:'张三',age:18, job:{salary:20}})      watch(()=>Person.age,(newVal,oldVal)=>{         console.log('用户年龄发生了变化',newVal,oldVal);      })</script><template> <h2>年龄:{{Person.age}}</h2> <button @click="Person.age++">+1</button></template>
  1. 监听多个对象
<!-- 监听对象中多个个属性,监听单个属性可以检测到新旧值 --><script setup>import {reactive,watch} from 'vue'const Person=reactive({name:'张三',age:18, job:{salary:20}})      watch([()=>Person.name,()=>Person.age],(newValue,oldValue)=>{        console.log('person.name或者person.age的值变化了',newValue,oldValue);      })</script><template> <h2>姓名:{{Person.name}}</h2> <button @click="Person.name+='~'">修改</button> <h2>年龄:{{Person.age}}</h2> <button @click="Person.age++">+1</button></template>
  1. 监听对象中对象(深度监听)
<!-- 监听对象中对象,必须开启深度监听,一般情况不监听对象 --><script setup>import {reactive,watch} from 'vue'const Person=reactive({name:'张三',age:18, job:{salary:20}})      watch(()=>Person.job,(newValue,oldValue)=>{        console.log('person.job的值变化了',newValue,oldValue);       },{      deep:true      })</script><template> <h2>薪资:{{Person.job.salary}}K</h2> <button @click="Person.job.salary++">+1</button></template>

5.高级监听API

  1. 基本使用(默认执行一次)
<!-- watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。 --><script setup>import {reactive,watchEffect} from 'vue'const Person=reactive({        name:'张三'      })          watchEffect(()=>{     Person.name     console.log('姓名发送了变化');     })</script><template> <h2>姓名:{{Person.name}}</h2> <button @click="Person.name+='~'">修改</button></template>
  1. 监听御前处理oninvalidate参数
<script setup lang="ts">import { reactive, watchEffect } from "vue";const Person = reactive({  name: "张三",});watchEffect((oninvalidate) => {  oninvalidate(() => {    console.log("before");  });  Person.name;  console.log("姓名发送了变化");});</script><template>  <h2>姓名:{{ Person.name }}</h2>  <button @click="Person.name += '~'">修改</button></template>
  1. 停止监听
<script setup lang="ts">import { reactive, watchEffect } from "vue";const Person = reactive({  name: "张三",});const stop = watchEffect((oninvalidate) => {  oninvalidate(() => {    console.log("before");  });  Person.name;  console.log("姓名发送了变化");</script><template>  <h2>姓名:{{ Person.name }}</h2>  <button @click="Person.name += '~'">修改</button>  <button @click="stop">停止</button></template>

6.响应式对象解构API

  1. toRef函数
<script setup>import {reactive,toRef} from 'vue'const person=reactive({A:1,B:2})const A=toRef(person,'A')</script><template> <h2>姓名:{{A}}</h2> <button @click="person.A+='~'">修改</button></template>
  1. toRefs
<script setup lang="ts">  import {reactive,toRefs} from 'vue'  const person=reactive({A:1,B:2})  const {A,B}=toRefs(person)  </script>    <template>   <h2>姓名:{{A}}</h2>   <button @click="A+=1">修改</button>  </template>

7.生命周期API

<script setup>  import {onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted,ref} from "vue";  onBeforeMount(()=>{    console.log('---挂载之前---');   })   onMounted(()=>{    console.log('---挂载---');   })   onBeforeUpdate(()=>{    console.log('---更新之前---');   })   onUpdated(()=>{    console.log('---更新---');   })   onBeforeUnmount(()=>{    console.log('---卸载之前---');   })   onUnmounted(()=>{    console.log('---卸载---');   })</script>

8.ref获取dom

<template>  <div>    <div ref="box">我是div</div>  </div></template><script>import { ref,onMounted } from "vue";export default {  setup() {    let box = ref(null); //本质是reactive({value:null})    // 需要在生命周期获取    onMounted(()=>{      // 当界面挂载出来后就会自动执行      console.log(box.value);    })    //接受的是null,原因是setup执行时机比mounted早,dom还没形成    console.log(box.value);    return { box };  },};</script>

9.Hooks

(1)官方hooks
  1. useAttrs()
<!-- 父组件 --><template>  <Acom a="456" title="789" /></template><!-- 子组件 --><!-- 获取父组件传过来的全部参数 --><script setup lang="ts">import { useAttrs } from 'vue'let attr = useAttrs()console.log(attr)</script>
(2)自定hooks
  1. 自定义hooks转换图片
import { onMounted } from 'vue'type Options = {  el: string}export default function (options: Options): Promise<{ baseUrl: string }> {  return new Promise(resolve => {    onMounted(() => {      const img: HTMLImageElement = document.querySelector(        options.el      ) as HTMLImageElement      img.onload = () => {        resolve({          baseUrl: base64(img)        })      }    })    const base64 = (el: HTMLImageElement) => {      const canvas = document.createElement('canvas')      const ctx = canvas.getContext('2d')      canvas.width = el.width      canvas.height = el.height      ctx?.drawImage(el, 0, 0, canvas.width, canvas.height)      return canvas.toDataURL('image/jpg')    }  })}
  1. 使用hooks
<script setup lang="ts">import BASE64 from './hooks'BASE64({ el: '#img' }).then(resolve => {  console.log(resolve.baseUrl)})</script>
(3)第三方hooks
  1. 安装依赖yarn add @vueuse/core
  2. 简单使用
<script setup lang="ts">import { ref } from 'vue'import { useDraggable } from '@vueuse/core'const el = ref<HTMLElement | null>(null)// `style` will be a helper computed for `left: ?px; top: ?px;`const { x, y, style } = useDraggable(el, {  initialValue: { x: 40, y: 40 }})</script><template>  <div ref="el" :style="style" style="position: fixed">    Drag me! I am at {{ x }}, {{ y }}  </div></template>

组件间通讯

1.props父传子

  1. 父组件
<script setup >import HelloWorld from './components/HelloWorld.vue'</script><template>  <HelloWorld msg="1"/></template>
  1. 子组件
<script setup>// const props=defineProps(['msg'])const props=defineProps({msg:String})console.log(props.msg)</script>

2.emit子传父

  1. 父组件
<script setup >import HelloWorld from './components/HelloWorld.vue'const getuser=(a)=>{  console.log(a)}</script><template>  <HelloWorld @getuser="getuser"/></template>
  1. 子组件
<script setup lang="ts">const emit = defineEmits(['getuser'])function buttonClick() {  emit('getuser',1)}  </script><template>  <button @click="buttonClick">传输</button></template>
  1. 自定义事件事件校检
<script setup>const emit = defineEmits({  // 没有校验  click: null,  // 校验 submit 事件  submit: ({ email, password }) => {    if (email && password) {      return true    } else {      console.warn('Invalid submit event payload!')      return false    }  }})function submitForm(email, password) {  emit('submit', { email, password })}</script>

3.插槽通讯

(1)匿名插槽
  1. 子组件
<template>  <!-- slot插槽占位 --> <slot></slot></template>
  1. 父组件
<script setup lang="ts">import HelloWorld from "./components/HelloWorld.vue";</script><template>  <HelloWorld>     插槽传递  </HelloWorld></template>
(2)具名插槽
  1. 父组件
<script setup lang="ts">import HelloWorld from "./components/HelloWorld.vue";</script><template>  <HelloWorld>    <!-- v-slot:简写# -->    <template v-slot:btn>      <button>具名插槽</button>    </template>  </HelloWorld></template>
  1. 子组件
<template>  <!-- slot插槽占位 --> <slot name="btn"></slot></template>
(3)作用域插槽
  1. 理解:数据在子组件的自身,但根据数据生成的结构需要父组件决定。
  2. 父组件
<script setup lang="ts">import HelloWorld from "./components/HelloWorld.vue";const person=[{name:'小明',age:18},{name:'小红',age:20}]</script><template>// 父组件将信息传递给子组件<HelloWorld :person="person">// 子组件接收父组件的插槽中传的值  <template #tab="scope"><tr v-for="(item,index) in scope.person" :key="index">  <th>{{item.name}}</th>  <th>{{item.age}}</th>  <th><button >编辑</button></th></tr></template></HelloWorld></template>
  1. 子组件
<script setup lang="ts">const props=defineProps<{person:{name:string,age:number}[]}>()</script><template>     <table border="1">      <tr>        <th>姓名</th>        <th>年龄</th>        <th>操作</th>      </tr>      <!-- 作用域插槽命名 -->      // 向作用插槽中传值      <slot name="tab" :person="props.person"></slot>     </table></template>

4.依赖注入

  1. 父组件(祖先组件)
<!-- 依赖注入传的参可以在子组件中改变 --><template>  <div class="App">    <button>我是App</button>    <A></A>  </div></template><script setup lang="ts">import { provide, ref } from 'vue'import A from './components/Acom.vue'let flag = ref<number>(1)provide('flag', flag)</script>
  1. 子组件(后代组件)
<template>  <div>    我是B    <div>{{ flag }}</div>    <button @click="flag++">+1</button>  </div></template><script setup lang="ts">import { inject, ref } from 'vue'//  注入值,默认值(让其可以进行类型推断)const flag = inject('flag', ref(1))</script>

5.兄弟传参

(1)父组件当成一个桥梁
(2)发布订阅模式
  1. Bus传递
type BusClass = {  emit: (name: string) => void  on: (name: string, callback: Function) => void}type PramsKey = string | number | symboltype List = {  [key: PramsKey]: Array<Function>}class Bus implements BusClass {  list: List  constructor() {    this.list = {}  }  emit(name: string, ...args: Array<any>) {    const evnentName: Array<Function> = this.list[name]    evnentName.forEach(fn => {      fn.apply(this, args)    })  }  on(name: string, callback: Function) {    const fn: Array<Function> = this.list[name] || []    fn.push(callback)    this.list[name] = fn  }}export default new Bus()
  1. A组件传递数值
<script setup lang="ts">import { ref } from 'vue'import Bus from '../utils/Bus'const flag = ref(1)const Pass = () => {  Bus.emit('pass', flag)}</script><template>  <div>    我是A    <div>{{ flag }}</div>    <button @click="Pass">Pass</button>  </div></template><style scoped lang="less"></style>
  1. B组件接收数值
<script setup lang="ts">import Bus from '../utils/Bus'import { ref, type Ref } from 'vue'const flag = ref(0)Bus.on('pass', (Flag: Ref<number>) => {  console.log(Flag)  flag.value = Flag.value})</script><template>  <div>    我是B    <div>{{ flag }}</div>    <button @click="flag++">+</button>  </div></template><style scoped lang="less"></style>
(3)第三方库mitt
  1. 安装yarn add mitt
  2. 全局挂载mit
import { createApp } from 'vue'import { createPinia } from 'pinia'import App from './App.vue'import './assets/main.css'import mitt from 'mitt'const Mit = mitt()const app = createApp(App)// 类型声明declare module 'vue' {  export interface ComponentCustomProperties {    $Bus: typeof Mit  }}app.use(createPinia())app.config.globalProperties.$Bus = Mitapp.mount('#app')
  1. A组件传递数值
<script setup lang="ts">import { getCurrentInstance, ref } from 'vue'const instance = getCurrentInstance()const flag = ref(1)const Pass = () => {  instance?.proxy?.$Bus.emit('pass', flag)}</script><template>  <div>    我是A    <div>{{ flag }}</div>    <button @click="Pass">Pass</button>  </div></template><style scoped lang="less"></style>
  1. B组件接收数值
<script setup lang="ts">import { getCurrentInstance, ref, type Ref } from 'vue'const instance = getCurrentInstance()const flag = ref(0)instance?.proxy?.$Bus.on('pass', Flag => {  flag.value = (Flag as Ref<number>).value})</script><template>  <div>    我是B    <div>{{ flag }}</div>    <button @click="flag++">+</button>  </div></template><style scoped lang="less"></style>
  1. *监听事件
<script setup lang="ts">import { getCurrentInstance, ref, type Ref } from 'vue'const instance = getCurrentInstance()const flag = ref(0)/** * type:事件名称 * Flag:传递参数 */instance?.proxy?.$Bus.on('*', (type, Flag) => {  flag.value = (Flag as Ref<number>).value})</script>
  1. 取消监听事件
<script setup lang="ts">import { getCurrentInstance, ref, type Ref } from 'vue'const instance = getCurrentInstance()const flag = ref(0)instance?.proxy?.$Bus.off('pass', Flag => {  flag.value = (Flag as Ref<number>).value})</script>
  1. 取消全部监听事件
<script setup lang="ts">import { getCurrentInstance, ref, } from 'vue'const instance = getCurrentInstance()instance?.proxy?.$Bus.all.clear()</script>

Typescript的支持

1.全局接口的抽取

  1. src下定义types文件夹命名xx.d.ts
  2. 建立Person接口person.d.ts
interface personInterface{    name:string    age:number}
  1. 组件中直接使用
<script setup lang="ts">const props=defineProps<{person:personInterface[]}>()</script>
  1. 如果不是在src下或src文件下的xx.d.ts文件则需要在tsconfig.json中配置
{  { ...  },  "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], //配置全局目录  "references": [{ "path": "./tsconfig.node.json" }]}

2.类型增强

  1. 使用环境:全局定义的数据,函数在vue组件中直接访问报错
  2. index.html中定义数据
<!DOCTYPE html><html lang="en">  <head>  ...  </head>  <script>    const  global=1  </script>  <body>    ...  </body></html>
  1. 定义类型增强
// common.d.tsdeclare const global:string;
  1. 组件中直接读取
<script setup lang="ts">console.log(global)</script>

3.第三方库类型声明

  1. 安装一个库
  2. 安装库的ts类型声明@types/xxxx

4.props组件通讯TS

  1. 父组件
<script setup lang="ts">import HelloWorld from './components/HelloWorld.vue'</script><template>  <HelloWorld msg="1"/></template>
  1. 子组件
<script setup lang="ts">interface msgIterface{  msg:string}const props = withDefaults(defineProps<msgIterface>(),{ msg:'默认值'})console.log(props.msg)</script>

5.emit组件通讯TS

  1. 父组件
<script setup lang="ts">import HelloWorld from './components/HelloWorld.vue'const getuser=(a:number)=>{  console.log(a)}</script><template>  <HelloWorld @getuser="getuser"/></template><style scoped></style>
  1. 子组件
<script setup lang="ts"> const emit = defineEmits<{(e: 'getuser', id: number): void}>()  // (e: 事件名, 键名:类型): voidfunction buttonClick() {  emit('getuser',1)}  </script><template>  <button @click="buttonClick">传输</button></template><style scoped></style>

6.依赖注入类型推断

  1. 父组件(祖先组件)
<template>  <div class="App">    <button>我是App</button>    <A></A>  </div></template><script setup lang="ts">import { provide, ref } from 'vue'import A from './components/Acom.vue'let flag = ref<number>(1)provide('flag', flag)</script>
  1. 子组件(后代组件)
<template>  <div>    我是B    <div>{{ flag }}</div>    <button @click="flag++">+1</button>  </div></template><script setup lang="ts">import { inject, ref , type Ref} from 'vue'//  注入值,默认值(让其可以进行类型推断)const flag<Ref<number>> = inject('flag', ref(1))</script>

7.定义全局函数和全局函数的类型支持

import { createApp } from 'vue'...const app = createApp(App)type Fileter = {  format: <T>(str: T) => string}declare module '@vue/runtime-core' {  export interface ComponentCustomProperties {    $filters: Fileter    $env: string  }}// 全局函数app.config.globalProperties.$filters = {  format<T>(str: T): string {    return `${str}`  }}// 全局变量app.config.globalProperties.$env = '全局变量'...

脚手架Vite

1.基本使用

  1. 创建vue3的项目yarn create vite || npm init vite@latest
  2. 安装插件Volar

2.配置项目路径

  1. tsconfig.json中添加
// 让ts可以识别这个路径{  "compilerOptions": {   ...    "baseUrl": "./",    "paths": {      "@/*":[        "src/*"      ]    }  },  ...}
  1. vite.config.ts中添加
// https://vitejs.dev/config/export default defineConfig({  plugins: [vue()],  resolve:{    alias:{      "@":join(__dirname,'src')    }  }})

3.eslint和prettierrc的配置

  1. .prettierrc.json
{    "arrowParens": "always",    "bracketSameLine": true,    "bracketSpacing": true,    "embeddedLanguageFormatting": "auto",    "htmlWhitespaceSensitivity": "css",    "insertPragma": false,    "jsxSingleQuote": false,    "printWidth": 120,    "proseWrap": "never",    "quoteProps": "as-needed",    "requirePragma": false,    "semi": false,    "singleQuote": true,    "tabWidth": 2,    "trailingComma": "all",    "useTabs": false,    "vueIndentScriptAndStyle": false,    "singleAttributePerLine": false  }  
  1. .eslintrc.cjs
/* eslint-env node */require('@rushstack/eslint-patch/modern-module-resolution')module.exports = {  root: true,  extends: [    'plugin:vue/vue3-essential',    'eslint:recommended',    '@vue/eslint-config-typescript',    '@vue/eslint-config-prettier'  ],  rules: {    'vue/multi-word-component-names': 'off', // 关闭命名    semi: 0 // 结尾无分号  },  parserOptions: {    ecmaVersion: 'latest'  }}

4.vite环境变量的配置

  1. vite的环境在import中
<script setup lang="ts">console.log(import.meta.env)</script>
  1. 创建.env.development .env.production
  2. package.json中配置运行生产环境,会自动注入
{  ...  "scripts": {    "dev": "vite --mode development",    ...  },    }
  1. vite.config.ts中读取环境变量
import { fileURLToPath, URL } from 'node:url'import { defineConfig, loadEnv } from 'vite'import unocss from 'unocss/vite'import vue from '@vitejs/plugin-vue'import { presetIcons, presetAttributify, presetUno } from 'unocss'// https://vitejs.dev/config/export default ({ mode }: any) => {  // 读取环境变量  console.log(loadEnv(mode, process.cwd()))  return defineConfig({    plugins: [vue()],    resolve: {      alias: {        '@': fileURLToPath(new URL('./src', import.meta.url))      }    }  })}
  1. 找不到模块“./App.vue”或其相应的类型声明
declare module '*.vue' {  import type { DefineComponent } from 'vue'  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types  const component: DefineComponent<{}, {}, any>  export default component}
  1. 类型“ImportMeta”上不存在属性“env”
// tsconfig.json{  ...  "compilerOptions": {     ...    "types": [ "vite/client" ],  },  ...}

指令的重构

1.v-model指令

(1)v-model实现组件间数据双向绑定
  1. 父组件
<script setup lang="ts">import HelloWorld from "./components/HelloWorld.vue";import { ref } from "vue";const num=ref(1)</script><template>  <HelloWorld v-model="num"/></template>
  1. 子组件
<script setup lang="ts">import { computed } from 'vue';const props=defineProps<{modelValue:number}>()const emit = defineEmits<{(e: 'update:modelValue', id: number): void}>()// 计算属性实现修改数据的同步const value=computed({  get(){    return +props.modelValue  },  set(value){    emit('update:modelValue',+value)  }})</script><template> <input type="text"  v-model="value"></template>
  1. v-model的原理
<template><!-- <HelloWorld v-model="num"/> -->  <HelloWorld :modelValue="num" @update:modelValue="num = $event"/></template>
(2)v-model传递特定的名称
  1. 父组件
<script setup lang="ts">import { ref } from "vue";import HelloWorld from "./components/HelloWorld.vue";const num=ref(1)</script><template>  <!-- <HelloWorld :num="num @update:="num = $event""/> -->  <HelloWorld v-model:num="num"/></template>
  1. 子组件
<script setup lang="ts">import { computed } from 'vue';const props=defineProps<{num:number}>()const emit = defineEmits<{(e: 'update:num', id: number): void}>()const value=computed({  get(){    return +props.num  },  set(value){    emit('update:num',+value)  }})</script><template> <input type="text"  v-model="value"></template>

2.自定义指令

(1)自定义指令的简单使用
  1. 全局自定义指令
// mian.tsimport { createApp } from 'vue'import './style.css'import App from './App.vue'const app=createApp(App)app.directive('focus',{    mounted(el){     el.focus()    }   })app.mount('#app')
  1. 使用自定义指令
<template> <input type="text"  v-model="value" v-focus></template>
  1. 局部自定义指令
<script setup>// 在模板中启用 v-focusconst vFocus = {  mounted: (el) => el.focus()}</script><template>  <input v-focus /></template>
(2)自定义指令详解
  1. 自定义指令的生命周期
<script setup lang="ts">import type { Directive, DirectiveBinding } from 'vue'type Dir = { background: string }const vMove: Directive = {  created() {}, //元素初始化的时候  beforeMount() {}, //指令绑定到元素后调用 只调用一次  mounted(el: HTMLElement, dir: DirectiveBinding<Dir>) {    console.log(dir.value.background)    el.style.background = dir.value.background  }, //元素插入父级dom调用  beforeUpdate() {}, //元素被更新之前调用  updated() {}, //这个周期方法被移除 改用updated  beforeUnmount() {}, //在元素被移除前调用  unmounted() {} //指令被移除后调用 只调用一次}</script><template>  <!-- 自定义指令,参数,修饰符 -->  <div v-move:a.x="{ background: 'red' }">自定义指令</div></template><style scoped lang="less"></style>
  1. 生命周期的简写
<script setup lang="ts">import type { Directive, DirectiveBinding } from 'vue'type Dir = { background: string }const vMove: Directive = (el: HTMLElement, dir: DirectiveBinding<Dir>) => {  el.style.background = dir.value.background}</script><template>  <!-- 自定义指令,参数,修饰符 -->  <div v-move:a.x="{ background: 'red' }">自定义指令</div></template><style scoped lang="less"></style>
  1. 自定义拖拽指令
<script setup lang="ts">import type { Directive } from 'vue'const vMove: Directive = (el: HTMLElement) => {  const move = (e: MouseEvent) => {    console.log(e)    el.style.left = e.clientX + 'px'    el.style.top = e.clientY + 'px'  }  // 鼠标按下  el.addEventListener('mousedown', () => {    // 鼠标按下拖拽    document.addEventListener('mousemove', move)    // 鼠标松开    document.addEventListener('mouseup', () => {      // 清除事件      document.removeEventListener('mousemove', move)    })  })}</script><template>  <!-- 自定义指令,参数,修饰符 -->  <div    v-move    style="      background-color: red;      width: 200px;      height: 200px;      position: fixed;      left: 50%;      top: 50%;      transform: translate(-50%, -50%);    "  >    <div style="background-color: black; width: 200px; color: white">      自定义指令    </div>  </div></template>

响应式原理

1.了解Proxy

  1. Proxy代理的get方法
    <script>        let obj={            name:'Vue',            age:8        }        let obj2=new Proxy(obj,{            /*            *target表示obj这个对象            *property表示读取的属性的key            */            get(target,property){                console.log('执行了get');                return target[property]            }        })        console.log(obj2.age)    </script>
  1. Proxy代理的set方法
    <script>        let obj={            name:'Vue',            age:8        }        let obj2=new Proxy(obj,{            /*            *target表示obj这个对象            *property表示读取的属性的key            *newValue表示设置的值            */            set(target,property,newValue){                console.log('执行了set')        target[property]=newValue    }        })        obj2.age=7        console.log(obj2.age)    </script>

2.了解Object.defineProperty

  1. Object.defineProperty(对象.定义属性,用来为一个对象添加新属性)
<script>  let person = {name:'张三',sex:'男',}// 为 person对象 传输了一个新属性 “age”,并且设定它的值为 18Object.defineProperty(person,'age',{value=18})console.log(person)</script>
  1. Object.defineProperty属性的可枚举可修改的实现
<script>  let person = {name:'张三',sex:'男',}// 为 person对象 传输了一个新属性 “age”,并且设定它的值为 18Object.defineProperty(person,'age',{        enumerable=true  // 可枚举        writable=true   // 可修改        configurable:true // 可删除value=18})console.log(person)</script>
  1. Object.defineProperty() 的get()方法
 <script>        let person = {            name: '张三',            sex: '男',        }        function Observer(obj) {            const keys = Object.keys(obj)            keys.forEach((key) => {                Object.defineProperty(this,key,{                    get() {                        return obj[key]                    }                })            })        }        const obs = new Observer(person)        console.log(obs.sex);    </script>
  1. Object.defineProperty() 的set()方法
 <script>        let person = {            name: '张三',            sex: '男',        }        function Observer(obj) {            const keys = Object.keys(obj)            keys.forEach((key) => {                Object.defineProperty(this,key,{                                    set(val) {                        console.log('set方法调用了')                        obj[key] = val                    }                })            })        }        const obs = new Observer(person)        obs.name=15    </script>

3.Vue双向绑定的实现的对比

  1. Vue3的Proxy实现
<body>    <input type="text" id="ipt">    <p id='op'></p>    <script>        function reactive(obj) {            return new Proxy(obj,{                get(target,property) {                    return target[property]                },                set(target,property,newVal) {                    target[property] = newVal                }            })        }        let newObj = reactive([1,2])        console.log(newObj[1])        const ipt = document.querySelector('#ipt')        ipt.value = newObj[1]        document.querySelector('#op').innerHTML = newObj[1]        ipt.addEventListener('input',function (e) {            newObj[1] = e.target.value            document.querySelector('#op').innerHTML = newObj[1]        })    </script></body>
  1. Vue2的Object.defineProperty实现
<body>    <input type="text" id="ipt">    <p id='op'></p>    <script>        function Observer(obj) {            const keys = Object.keys(obj)            keys.forEach((key) => {                Object.defineProperty(this,key,{                    get() {                        console.log('get方法被调用了');                        return obj[key]                    },                    set(val) {                        console.log('set方法调用了')                        obj[key] = val                    }                })            })        }        const obs = new Observer([1,2,3])             const ipt = document.querySelector('#ipt')        ipt.value = obs[1]        document.querySelector('#op').innerHTML = obs[1]        ipt.addEventListener('input',function (e) {            obs[1] = e.target.value            document.querySelector('#op').innerHTML = obs[1]        })    </script></body>
  1. 上面的测试,Object.property是可以检测到通过索引改变数组的操作的,而Vue没有实现,Object.defineProperty表示这个锅我不背

内置组件

1.内置组件

(1)Teleport组件
  1. 可以将一个组件内部的一部分模板“传送”到该组件的 DOM 结构外层的位置去
  2. 父组件
<!-- 遮罩层组件传送到body下 --><script setup lang="ts">import Acom from './components/Acom.vue'</script><template><div class="app"></div><Acom/></template><style scoped >.app{  width: 200px;  height: 200px;  background-color: pink;}</style>
  1. 子组件
<script setup lang="ts">  import { ref } from 'vue'    const open = ref(false)  </script>    <template>    <button @click="open=true">显示遮罩层</button>    <!-- 传送到body -->  <Teleport to="body">    <div class="cover" v-show="open">      <span @click="open=false"> X</span>   </div>  </Teleport>    </template>    <style scoped>  .cover {   position: absolute;   z-index:2;   top: 0;   left: 0;   bottom: 0;   right: 0;   background-color: rgba(0,0,0,0.5);  }  </style>  
(2)Transition组件
  1. 非命名动画
<script setup lang="ts">import { ref } from 'vue';const show=ref(true)</script><template><button @click="show=!show">显示/隐藏</button><Transition><div class="div" v-if="show"></div></Transition></template><style scoped>.div{  background-color: pink;  width: 200px;  height: 200px;  margin: auto;}.v-enter-active,.v-leave-active {  transition: opacity 0.5s ease;}.v-enter-from,.v-leave-to {  opacity: 0;}</style>
  1. 命名动画
<script setup lang="ts">import { ref } from 'vue';const show=ref(true)</script><template><button @click="show=!show">显示/隐藏</button><Transition name="fade"><div class="div" v-if="show"></div></Transition></template><style scoped>.div{  background-color: pink;  width: 200px;  height: 200px;  margin: auto;}.fade-enter-active {  transition: all 0.3s ease-out;}.fade-leave-active {  transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1);}.fade-enter-from,.fade-leave-to {  transform: translateX(20px);  opacity: 0;}</style>
  1. 过度动画
<Transition mode="out-in">  ...</Transition>
  1. 结合第三方库Animate.css
<!--  yarn add animate.css --><script setup lang="ts">import { ref } from 'vue'import 'animate.css'import Acom from './components/Acom.vue'const show = ref(true)</script><template>  <transition    leave-active-class="animate__animated animate__fadeOut"    enter-active-class="animate__animated animate__fadeIn"  >    <Acom v-if="show"></Acom>  </transition>  <button @click="show = !show">显示/隐藏</button></template><style scoped lang="less"></style>
  1. transition 生命周期
<script setup lang="ts">import { ref } from 'vue'import 'animate.css'import Acom from './components/Acom.vue'const show = ref(true)const beforeEnter = () => {  console.log('进入之前')}const enter = (_, done: Function) => {  console.log('过度曲线')  setTimeout(() => {    done()  }, 3000)}const afterEnter = () => {  console.log('过度完成')}const enterCancelled = () => {  console.log('进入效果被打断')}const beforeLeave = () => {  console.log('离开之前')}const leave = (_, done: Function) => {  setTimeout(() => {    done()  }, 3000)  console.log('过度曲线')}const afterLeave = () => {  console.log('离开之后')}const leaveCancelled = () => {  console.log('离开效果被打断')}</script><template>  <transition    leave-active-class="animate__animated animate__fadeOut"    enter-active-class="animate__animated animate__fadeIn"    @before-enter="beforeEnter"    @enter="enter"    @after-enter="afterEnter"    @enter-cancelled="enterCancelled"    @before-leave="beforeLeave"    @leave="leave"    @after-leave="afterLeave"    @leave-cancelled="leaveCancelled"  >    <Acom v-if="show"></Acom>  </transition>  <button @click="show = !show">显示/隐藏</button></template>
  1. 生命周期结合第三方库gsap.js
<!-- yarn add  gsap --><script setup lang="ts">import { ref } from 'vue'import Acom from './components/Acom.vue'import gsap from 'gsap'const show = ref(true)// 进入之前const beforeEnter = (el: Element) => {  gsap.set(el, {    width: 0,    height: 0  })}// 进入过度动画const enter = (el: Element, done: gsap.Callback) => {  gsap.to(el, {    width: 200,    height: 200,    onComplete: done  })}// 离开之前const beforeLeave = (el: Element) => {  gsap.set(el, {    width: 200,    height: 200  })}// 进入过度动画const leave = (el: Element, done: gsap.Callback) => {  gsap.to(el, {    width: 0,    height: 0,    onComplete: done  })}</script><template>  <transition    @before-enter="beforeEnter"    @enter="enter"    @before-leave="beforeLeave"    @leave="leave"  >    <Acom v-if="show"></Acom>  </transition>  <button @click="show = !show">显示/隐藏</button></template>
  1. 初始化动画
<script setup lang="ts">import { ref } from 'vue'import Acom from './components/Acom.vue'const show = ref(true)</script><template>  <transition    appear-from-class="from"    appear-active-class="active"    appear-to-class="to"    appear  >    <Acom v-if="show"></Acom>  </transition>  <button @click="show = !show">显示/隐藏</button></template><style scoped>.from {  /* 初始化之前 */  width: 0;  height: 0;}.active {  /* 过度动画 */  transition: all 2s ease;}.to {  /* 初始化完成 */  width: 200px;  height: 200px;}</style>
  1. 初始化动画结合Animate.css
<script setup lang="ts">import { ref } from 'vue'import Acom from './components/Acom.vue'import 'animate.css'const show = ref(true)</script><template>  <transition appear-active-class="animate__animated animate__heartBeat" appear>    <Acom v-if="show"></Acom>  </transition>  <button @click="show = !show">显示/隐藏</button></template><style scoped></style>
(3)transition-group过度列表
  1. Transition组件无法对v-for的列表进行渲染
  2. transition-group的tag属性
<!-- tag属性可以让transition-group多加一层节点元素 --><template>  <div class="wraps">    <transition-group tag="session">      <!-- 使用transition-group渲染的组件要有key-->      <div class="item" v-for="item in 5" :key="item">{{ item }}</div>    </transition-group>  </div></template>
  1. 添加列表时的动画效果
<script setup lang="ts">import { ref } from 'vue'import 'animate.css'const num = ref(5)</script><template>  <div class="wraps">    <transition-group      leave-active-class="animate__animated animate__fadeOut"      enter-active-class="animate__animated animate__fadeIn"    >      <!-- 使用transition-group渲染的组件要有key-->      <div class="item" v-for="item in num" :key="item">{{ item }}</div>    </transition-group>  </div>  <button @click="num++">添加</button>  <button @click="num--">删除</button></template><style scoped lang="less">.wraps {  display: flex;  flex-wrap: wrap;  word-break: break-all;  border: 1px solid #ccc;  .item {    margin: 10px;  }}</style>
  1. 平移动画move-class
<script setup lang="ts">import { ref } from 'vue'import _ from 'lodash'// 建立9x9数组let list = ref(  Array.apply(null, { length: 81 } as number[]).map((_, index) => {    return {      id: index,      number: (index % 9) + 1    }  }))// 打乱数组const random = () => {  list.value = _.shuffle(list.value)}console.log(list)</script><template>  <div>    <button @click="random">打乱</button>    <transition-group tag="div" class="wraps" move-class="move">      <div v-for="item in list" :key="item.id" class="item">        {{ item.number }}      </div>    </transition-group>  </div></template><style scoped lang="less">.wraps {  display: flex;  flex-wrap: wrap; // 换行  width: calc(25px * 10 + 9px);  .item {    width: 25px;    height: 25px;    border: 1px solid #ccc;    text-align: center;  }}.move {  transition: all 1s;}</style>
  1. 状态过度(数字过度颜色过度)
<script setup lang="ts">import { reactive, watch } from 'vue'import gsap from 'gsap'const num = reactive({  current: 0,  tweenedNumber: 0})watch(  () => num.current,  newVal => {    gsap.to(num, {      duration: 1, // 过度时间      tweenedNumber: newVal    })  })</script><template>  <div>    <input type="text" v-model="num.current" step="20" />    <div>      <!-- 去掉小数点 -->      {{ num.tweenedNumber.toFixed(0) }}    </div>  </div></template><style scoped lang="less"></style>
(4)keep-alive组件
  1. 开启keep-alive 生命周期的变化
初次进入时: onMounted-> onActivated退出后触发:  deactivated
  1. 缓存数据
<script setup lang="ts">import { ref } from 'vue'import Acom from './components/Acom.vue'const show = ref(true)</script><template>  <keep-alive>    <Acom v-if="show"></Acom>  </keep-alive>  <button @click="show = !show">显示/隐藏</button></template>
  1. include属性和exclude属性
<!-- 注意组件一定要命名才可以使用include --><script setup lang="ts">import { ref } from 'vue'import Acom from './components/Acom.vue'import Bcom from './components/Bcom.vue'const show = ref(true)</script><template>  <keep-alive :include="['Acom']" :exclude="['Bcom']">    <Acom v-if="show"></Acom>    <Bcom v-else></Bcom>  </keep-alive>  <button @click="show = !show">显示/隐藏</button></template><style scoped lang="less"></style>

2.普通组件

(1)全局组件
  1. 配置全局组件
import { createApp } from 'vue'import { createPinia } from 'pinia'import App from './App.vue'import Acom from './components/Acom.vue'import './assets/main.css'const app = createApp(App)app.use(createPinia())app.component('Acom', Acom)app.mount('#app')
  1. 使用组件
<template>  <div>    <Acom></Acom>  </div></template>
(2)异步组件
  1. 子组件中发送了请求变成异步
<script setup lang="ts">interface ResItf {  code: number  data: { a: number; b: number }[]  message: string}let p: Promise<ResItf> = new Promise(resolve => {  setTimeout(() => {}, 3000)  resolve({    code: 0,    data: [      { a: 1, b: 2 },      { a: 11, b: 22 }    ],    message: ''  })})const a = await pconsole.log(a)</script><template>  <div>异步组件</div>  <div>异步组件</div>  <div>异步组件</div></template>
  1. 父组件异步调用组件
<script setup lang="ts">// 异步组件不能这样引入// import Acom from './components/Acom.vue'import { defineAsyncComponent } from 'vue'const Acom = defineAsyncComponent(() => import('./components/Acom.vue'))</script><template>  <div>    <Suspense>      <template #default>        <Acom></Acom>      </template>      <template #fallback> 加载中。。。 </template>    </Suspense>  </div></template><style scoped lang="less"></style>

语法糖组件命名问题

  1. 安装依赖yarn add vite-plugin-vue-setup-extend
  2. 直接命名
<script lang="ts" setup name="xxx"></script>

常用的CSS的功能

  1. 样式穿透
<style scoped lang="less">:deep(input) {  color: red;}</style>
  1. 插槽选择器
<template>  <div>    <slot name="nums" :nums="['1', '2', '3']"> </slot>  </div></template><style scoped lang="less">:slotted(.li) {  color: red;}</style>
  1. 全局选择器
<script setup lang="ts"></script><template>  <div>    <slot name="nums" :nums="['1', '2', '3']"> </slot>  </div></template><style scoped lang="less">:global(.li) {  color: red;}</style>
  1. 动态CSS
<script setup lang="ts">import { reactive } from 'vue'const style = reactive({  color: 'red'})setTimeout(() => {  style.color = 'blue'}, 3000)</script><template>  <div class="div">动态css</div></template><style scoped lang="less">.div {  color: v-bind('style.color');}</style>

1.CSS原子化

  1. 安装unocssyarn add unocss
  2. vite的配置文件中配置
import { fileURLToPath, URL } from 'node:url'import pxtoViewPort from 'postcss-px-to-viewport'import { defineConfig } from 'vite'import unocss from 'unocss/vite'import vue from '@vitejs/plugin-vue'// https://vitejs.dev/config/export default defineConfig({  plugins: [    vue(),    // 配置的原子化    unocss({      rules: [        ['flex', { display: 'flex' }],        ['red', { color: 'red' }],        [/^m-(\d+)$/, ([, d]) => ({ margin: `${Number(d) * 10}px` })]      ]    })  ],  resolve: {    alias: {      '@': fileURLToPath(new URL('./src', import.meta.url))    }  }})
  1. main.ts中引入import 'uno.css'
  2. 其他预设配置中引入
import { fileURLToPath, URL } from 'node:url'import { defineConfig } from 'vite'import unocss from 'unocss/vite'import vue from '@vitejs/plugin-vue'import { presetIcons, presetAttributify, presetUno } from 'unocss'// https://vitejs.dev/config/export default defineConfig({  plugins: [    vue(),    unocss({      // 预设      presets: [presetIcons(), presetAttributify(), presetUno()],      rules: [        ['flex', { display: 'flex' }],        ['red', { color: 'red' }],        [/^m-(\d+)$/, ([, d]) => ({ margin: `${Number(d) * 10}px` })]      ]    })  ],  resolve: {    alias: {      '@': fileURLToPath(new URL('./src', import.meta.url))    }  }})
  1. 第一预设图标库
npm i -D @iconify-json/ic// 后缀ic是选择的图标库
  1. 第二预设属性语义化 无须class
 <div color="red">left</div>
  1. 第三预设
默认的 @unocss/preset-uno 预设(实验阶段)是一系列流行的原子化框架的 通用超集,包括了 Tailwind CSS,Windi CSS,Bootstrap,Tachyons 等。例如,ml-3(Tailwind),ms-2(Bootstrap),ma4(Tachyons),mt-10px(Windi CSS)均会生效。

5.Vue3集成Tailwind CSS

  1. 安装依赖yarn add -D tailwindcss@latest postcss@latest autoprefixer@latest
  2. 安装插件tailwind css inteliSence
  3. 生成配置文件npx tailwindcss init -p
  4. tailwind.config.js配置文件中添加
/** @type {import('tailwindcss').Config} */module.exports = {  content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],  theme: {    extend: {}  },  plugins: []}
  1. 创建index.css文件并且在mian.ts中引入
@tailwind base;@tailwind components;@tailwind utilities;
  1. 使用tailwindcss的样式
<script setup lang="ts"></script><template>  <div    class="w-screen h-screen bg-red-600 flex justify-center items-center text-8xl text-teal-50"  >    hello tailwind  </div></template><style scoped lang="less"></style>

面试常用源码

1.app.use()的源码实现

  1. 实现myuse
import type { App } from 'vue'import { app } from '../main'interface Use {  install: (app: App, ...options: any[]) => void}// 插件注册的数组const installList = new Set()export function MyUse<T extends Use>(plugin: T, ...options: any[]) {  if (installList.has(plugin)) {    console.log('插件件已经注册')    return  }  plugin.install(app, ...options)  installList.add(plugin)}
  1. 使用myuse调用插件
import { createApp } from 'vue'import { createPinia } from 'pinia'import App from './App.vue'import './assets/main.css'import Loading from './components/Loading'import { MyUse } from './utils/myuse'export const app = createApp(App)// 使用插件// app.use(Loading)MyUse(Loading)app.use(createPinia())app.mount('#app')type Lod = {  show: () => void  hide: () => void}//编写ts loading 声明文件放置报错 和 智能提示declare module '@vue/runtime-core' {  export interface ComponentCustomProperties {    $loading: Lod  }}

移动端适配

1.第一种适配方案

  1. 安装依赖yarn add amfe-flexible postcss postcss-pxtorem@5.1.1
  2. main.ts引入amfe-flexibleimport "amfe-flexible"
  3. 根目录下创建postcss.config.js文件并配置
module.exports = {  plugins: {    'postcss-pxtorem': {      // 能够把所有元素的px单位转成Rem      // rootValue: 转换px的基准值。      // 编码时, 一个元素宽是75px,则换成rem之后就是2rem      rootValue: 37.5,      propList: ['*']    }  }}

2.第二种适配方案

  1. 安装依赖yarn add postcss-px-to-viewport -D
  2. vite.config.ts内置postcss.config.js中修改配置
import { fileURLToPath, URL } from 'node:url'import pxtoViewPort from 'postcss-px-to-viewport'import { defineConfig } from 'vite'import vue from '@vitejs/plugin-vue'// https://vitejs.dev/config/export default defineConfig({  plugins: [vue()],  css: {    postcss: {      plugins: [     // postcss-px-to-viewport的配置        pxtoViewPort({          unitToConvert: 'px', // 要转化的单位          viewportWidth: 750, // UI设计稿的宽度          unitPrecision: 6, // 转换后的精度,即小数点位数          propList: ['*'], // 指定转换的css属性的单位,*代表全部css属性的单位都进行转换          viewportUnit: 'vw', // 指定需要转换成的视窗单位,默认vw          fontViewportUnit: 'vw', // 指定字体需要转换成的视窗单位,默认vw          selectorBlackList: ['ignore-'], // 指定不转换为视窗单位的类名,          minPixelValue: 1, // 默认值1,小于或等于1px则不进行转换          mediaQuery: true, // 是否在媒体查询的css代码中也进行转换,默认false          replace: true, // 是否转换后直接更换属性值          landscape: false // 是否处理横屏情况        })      ]    }  },  resolve: {    alias: {      '@': fileURLToPath(new URL('./src', import.meta.url))    }  }})
  1. 创建postcss-px-to-viewport.d.ts的声明文件
declare module 'postcss-px-to-viewport' {  type Options = {    unitToConvert: 'px' | 'rem' | 'cm' | 'em'    viewportWidth: number    viewportHeight: number // not now used; TODO: need for different units and math for different properties    unitPrecision: number    viewportUnit: string    fontViewportUnit: string // vmin is more suitable.    selectorBlackList: string[]    propList: string[]    minPixelValue: number    mediaQuery: boolean    replace: boolean    landscape: boolean    landscapeUnit: string    landscapeWidth: number  }  export default function (options: Partial<Options>): any}
  1. 在tsconfig.json中引入声明文件
{  "extends": "@vue/tsconfig/tsconfig.web.json",  "include": ["env.d.ts", "src/**/*", "src/**/*.vue", "postcss-px-to-viewport.d.ts"],  "compilerOptions": {    "baseUrl": ".",    "types": ["element-plus/global"],    "paths": {      "@/*": ["./src/*"]    }  },  "references": [    {      "path": "./tsconfig.config.json"    }  ]}
  1. 注意:如果外面用到了postcss.config.js,在postcss.config.js中添加配置文件
// 要禁用vite.config.ts内置postcss.config.jsmodule.exports = {  plugins: {    tailwindcss: {},    autoprefixer: {},    'postcss-px-to-viewport': {      unitToConvert: 'px', // 要转化的单位      viewportWidth: 320 // UI设计稿的宽度      // unitPrecision: 6, // 转换后的精度,即小数点位数      // propList: ['*'], // 指定转换的css属性的单位,*代表全部css属性的单位都进行转换      // viewportUnit: 'vw', // 指定需要转换成的视窗单位,默认vw      // fontViewportUnit: 'vw', // 指定字体需要转换成的视窗单位,默认vw      // selectorBlackList: ['wrap'], // 指定不转换为视窗单位的类名,      // minPixelValue: 1, // 默认值1,小于或等于1px则不进行转换      // mediaQuery: true, // 是否在媒体查询的css代码中也进行转换,默认false      // replace: true, // 是否转换后直接更换属性值      // exclude: [/node_modules/], // 设置忽略文件,用正则做目录名匹配      // landscape: false // 是否处理横屏情况    }  }}

的其他知识点

1.全局函数和全局变量

  1. 全局函数
import { createApp } from 'vue'import { createPinia } from 'pinia'import App from './App.vue'import './assets/main.css'const app = createApp(App)type Fileter = {  format: <T>(str: T) => string}declare module '@vue/runtime-core' {  export interface ComponentCustomProperties {    $filters: Fileter  }}// 全局函数app.config.globalProperties.$filters = {  format<T>(str: T): string {    return `${str}`  }}app.use(createPinia())app.mount('#app')
  1. 全局变量
import { createApp } from 'vue'import { createPinia } from 'pinia'import App from './App.vue'import './assets/main.css'const app = createApp(App)declare module '@vue/runtime-core' {  export interface ComponentCustomProperties {    $env: string  }}// 全局变量app.config.globalProperties.$env = '全局变量'app.use(createPinia())app.mount('#app')

2.自定义插件

  1. 封装插件的样式,抛出插件的显示隐藏方法
<script setup lang="ts">import { ref } from 'vue'const isShow = ref(false)// 控制load显示const show = () => {  console.log(111)  isShow.value = true}const hide = () => {  isShow.value = false}// 这里抛出的东西会在插件声明文件中调用defineExpose({  show,  hide})</script><template>  <div v-if="isShow" class="loading">loading....</div></template><style scoped lang="less"></style>
  1. 创建接收调用插件的方法
import { render, type App, type VNode } from 'vue'import Loading from './index.vue'import { createVNode } from 'vue'export default {  install(app: App) {    // 变成div    const Vnode: VNode = createVNode(Loading)    // 挂载    render(Vnode, document.body)    // console.log(app, Vnode)    // // 读取loading组件中导出的方法    // console.log(Vnode.component?.exposed.show)    // 对插件的方法进行全局挂载    app.config.globalProperties.$loading = {      show: Vnode.component?.exposed?.show,      hide: Vnode.component?.exposed?.hide    }  }}
  1. main.ts中挂载上面的方方法
import { createApp } from 'vue'import { createPinia } from 'pinia'import App from './App.vue'import './assets/main.css'import Loading from './components/Loading'const app = createApp(App)// 使用插件app.use(Loading)app.use(createPinia())app.mount('#app')
  1. 对插件的方法进行声明
type Lod = {  show: () => void  hide: () => void}//编写ts loading 声明文件放置报错 和 智能提示declare module '@vue/runtime-core' {  export interface ComponentCustomProperties {    $loading: Lod  }}
  1. 使用插件
<script setup lang="ts">import { getCurrentInstance } from 'vue'const instance = getCurrentInstance()// 调用插件instance?.proxy?.$loading.show()// 5秒关闭插件setTimeout(() => {  instance?.proxy?.$loading.hide()}, 5000)</script><template>  <div></div></template><style scoped lang="less"></style>

3.函数式编程

  1. h函数
h 接收三个参数1.type 元素的类型2.propsOrChildren 数据对象, 这里主要表示(props, attrs, dom props, class 和 style)3.children 子节点
  1. h函数的多种组合
// 除类型之外的所有参数都是可选的h('div')h('div', { id: 'foo' }) //属性和属性都可以在道具中使用//Vue会自动选择正确的分配方式h('div', { class: 'bar', innerHTML: 'hello' }) // props modifiers such as .prop and .attr can be added// with '.' and `^' prefixes respectivelyh('div', { '.name': 'some-name', '^width': '100' }) // class 和 style 可以是对象或者数组h('div', { class: [foo, { bar }], style: { color: 'red' } }) // 定义事件需要加on 如 onXxxh('div', { onClick: () => {} }) // 子集可以字符串h('div', { id: 'foo' }, 'hello') //如果没有props是可以省略props 的h('div', 'hello')h('div', [h('span', 'hello')]) // 子数组可以包含混合的VNode和字符串h('div', ['hello', h('span', 'hello')])
  1. 使用props传递参数
<template>    <Btn text="按钮"></Btn></template>  <script setup lang='ts'>import { h, } from 'vue';type Props = {    text: string}const Btn = (props: Props, ctx: any) => {    return h('div', {        class: 'p-2.5 text-white bg-green-500 rounded shadow-lg w-20 text-center inline m-1',     }, props.text)}</script>
  1. 接收emit
<template>    <Btn @on-click="getNum" text="按钮"></Btn></template>  <script setup lang='ts'>import { h, } from 'vue';type Props = {    text: string}const Btn = (props: Props, ctx: any) => {    return h('div', {        class: 'p-2.5 text-white bg-green-500 rounded shadow-lg w-20 text-center inline m-1',        onClick: () => {            ctx.emit('on-click', 123)        }    }, props.text)} const getNum = (num: number) => {    console.log(num);}</script>
  1. 定义插槽
<template>    <Btn @on-click="getNum">        <template #default>            按钮slots        </template>    </Btn></template>  <script setup lang='ts'>import { h, } from 'vue';type Props = {    text?: string}const Btn = (props: Props, ctx: any) => {    return h('div', {        class: 'p-2.5 text-white bg-green-500 rounded shadow-lg w-20 text-center inline m-1',        onClick: () => {            ctx.emit('on-click', 123)        }    }, ctx.slots.default())} const getNum = (num: number) => {    console.log(num);}</script>

4.vue性能优化

(1)跑分和打包体积
  1. 跑分vue开发工具Lighthouse
从Performance页的表现结果来看,得分37分,并提供了很多的时间信息,我们来解释下这些选项代表的意思:FCP (First Contentful Paint):首次内容绘制的时间,浏览器第一次绘制DOM相关的内容,也是用户第一次看到页面内容的时间。Speed Index: 页面各个可见部分的显示平均时间,当我们的页面上存在轮播图或者需要从后端获取内容加载时,这个数据会被影响到。LCP (Largest Contentful Paint):最大内容绘制时间,页面最大的元素绘制完成的时间。TTI(Time to Interactive):从页面开始渲染到用户可以与页面进行交互的时间,内容必须渲染完毕,交互元素绑定的事件已经注册完成。TBT(Total Blocking Time):记录了首次内容绘制到用户可交互之间的时间,这段时间内,主进程被阻塞,会阻碍用户的交互,页面点击无反应。CLS(Cumulative Layout Shift):计算布局偏移值得分,会比较两次渲染帧的内容偏移情况,可能导致用户想点击A按钮,但下一帧中,A按钮被挤到旁边,导致用户实际点击了B按钮。
  1. 打包后rollup的插件yarn add rollup-plugin-visualizer
import { fileURLToPath, URL } from 'node:url'import { defineConfig, loadEnv } from 'vite'import unocss from 'unocss/vite'import vue from '@vitejs/plugin-vue'import { visualizer } from 'rollup-plugin-visualizer'// https://vitejs.dev/config/export default ({ mode }: any) => {  console.log(loadEnv(mode, process.cwd()))  return defineConfig({    plugins: [vue(),     // 配置rollup的插件    visualizer({ open: true })],    resolve: {      alias: {        '@': fileURLToPath(new URL('./src', import.meta.url))      }    }  })}
  1. vite配置文件中vite的优化
import { fileURLToPath, URL } from "node:url";import { defineConfig } from "vite";import vue from "@vitejs/plugin-vue";import vueJsx from "@vitejs/plugin-vue-jsx";// https://vitejs.dev/config/export default  defineConfig({    ...    build: {      chunkSizeWarningLimit: 2000,      cssCodeSplit: true, //css 拆分      sourcemap: false, //不生成sourcemap      minify: 'terser', //是否禁用最小化混淆,esbuild打包速度最快,terser打包体积最小。      assetsInlineLimit: 5000 //小于该值 图片将打包成Base64    }  })
(2)PWA离线存储技术
  1. 安装依赖yarn add vite-plugin-pwa -D
  2. 配置
import { fileURLToPath, URL } from "node:url";import { VitePWA } from "vite-plugin-pwa";import { defineConfig } from "vite";import vue from "@vitejs/plugin-vue";import vueJsx from "@vitejs/plugin-vue-jsx";// https://vitejs.dev/config/export default defineConfig({  plugins: [    vue(),    vueJsx(),    VitePWA({      workbox: {        cacheId: "key", //缓存名称        runtimeCaching: [          {            urlPattern: /.*\.js.*/, //缓存文件            handler: "StaleWhileRevalidate", //重新验证时失效            options: {              cacheName: "XiaoMan-js", //缓存js,名称              expiration: {                maxEntries: 30, //缓存文件数量 LRU算法                maxAgeSeconds: 30 * 24 * 60 * 60, //缓存有效期              },            },          },        ],      },    }),  ],  ....});
(3)其他性能优化
  1. 图片懒加载
import { createApp } from 'vue'import App from './app'import lazyPlugin from 'vue3-lazy'const app = createApp(App)app.use(lazyPlugin, {  loading: 'loading.png',  error: 'error.png'})app.mount('#app')<img v-lazy="user.avatar" >
  1. 虚拟列表实现
后台返回多数据展示可视区的dom
  1. 多线程 使用 new Worker 创建
// worker脚本与主进程的脚本必须遵守同源限制。他们所在的路径协议、域名、端口号三者需要相同const myWorker1 = new Worker("./calcBox.js");// 都使用postMessage发送消息worker.postMessage(arrayBuffer, [arrayBuffer]);// 都使用onmessage接收消息self.onmessage = function (e) {// xxx这里是worker脚本的内容};关闭worker.terminate();    
  1. 防抖节流