0


Vue3+TS教程

Vue3

组合式API

1.钩子函数steup

  1. 函数的普通用法
<script>exportdefault{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)functionincrement(){
  state.value++}</script><template><button @click="increment">{{ state }}</button></template>
  1. reactive函数
<script setup>import{ reactive }from'vue'const state =reactive({count:0})functionincrement(){
  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})constA=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";exportdefault{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
}exportdefaultfunction(options: Options): Promise<{baseUrl: string }>{returnnewPromise(resolve=>{onMounted(()=>{constimg: HTMLImageElement = document.querySelector(
        options.el
      )as HTMLImageElement
      img.onload=()=>{resolve({baseUrl:base64(img)})}})constbase64=(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">importBASE64from'./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'constgetuser=(a)=>{
  console.log(a)}</script><template><HelloWorld @getuser="getuser"/></template>
  1. 子组件
<script setup lang="ts">const emit =defineEmits(['getuser'])functionbuttonClick(){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){returntrue}else{
      console.warn('Invalid submit event payload!')returnfalse}}})functionsubmitForm(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'importAfrom'./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)=>voidon:(name: string,callback: Function)=>void}

type PramsKey = string | number | symbol

type List ={[key: PramsKey]: Array<Function>}classBusimplementsBusClass{list: List
  constructor(){this.list ={}}emit(name: string,...args: Array<any>){constevnentName: Array<Function>=this.list[name]
    evnentName.forEach(fn=>{fn.apply(this, args)})}on(name: string,callback: Function){constfn: Array<Function>=this.list[name]||[]
    fn.push(callback)this.list[name]= fn
  }}exportdefaultnewBus()
  1. A组件传递数值
<script setup lang="ts">import{ ref }from'vue'import Bus from'../utils/Bus'const flag =ref(1)constPass=()=>{
  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'{exportinterfaceComponentCustomProperties{$Bus:typeof Mit
  }}

app.use(createPinia())
app.config.globalProperties.$Bus = Mit
app.mount('#app')
  1. A组件传递数值
<script setup lang="ts">import{ getCurrentInstance, ref }from'vue'const instance =getCurrentInstance()const flag =ref(1)constPass=()=>{
  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
interfacepersonInterface{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中定义数据
<!DOCTYPEhtml><htmllang="en"><head>
  ...
  </head><script>const  global=1</script><body>
    ...
  </body></html>
  1. 定义类型增强
// common.d.ts
declare constglobal: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">interfacemsgIterface{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'constgetuser=(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: 事件名, 键名:类型): voidfunctionbuttonClick(){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'importAfrom'./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'{exportinterfaceComponentCustomProperties{$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/exportdefaultdefineConfig({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/exportdefault({ mode }: any)=>{// 读取环境变量
  console.log(loadEnv(mode, process.cwd()))returndefineConfig({plugins:[vue()],resolve:{alias:{'@':fileURLToPath(newURL('./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-typesconstcomponent: DefineComponent<{},{}, any>exportdefault 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 }constvMove: Directive ={created(){},//元素初始化的时候beforeMount(){},//指令绑定到元素后调用 只调用一次mounted(el: HTMLElement,dir: DirectiveBinding<Dir>){
    console.log(dir.value.background)
    el.style.background = dir.value.background
  },//元素插入父级dom调用beforeUpdate(){},//元素被更新之前调用updated(){},//这个周期方法被移除 改用updatedbeforeUnmount(){},//在元素被移除前调用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 }constvMove: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'constvMove:Directive=(el: HTMLElement)=>{constmove=(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=newProxy(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=newProxy(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”,并且设定它的值为 18
            Object.defineProperty(person,'age',{
                value=18})
            console.log(person)</script>
  1. Object.defineProperty属性的可枚举可修改的实现
<script>let person ={name:'张三',sex:'男',}// 为 person对象 传输了一个新属性 “age”,并且设定它的值为 18
            Object.defineProperty(person,'age',{
        enumerable=true// 可枚举
        writable=true// 可修改configurable:true// 可删除
                value=18})
            console.log(person)</script>
  1. Object.defineProperty() 的get()方法
<script>let person ={name:'张三',sex:'男',}functionObserver(obj){const keys = Object.keys(obj)
            keys.forEach((key)=>{
                Object.defineProperty(this,key,{get(){return obj[key]}})})}const obs =newObserver(person)
        console.log(obs.sex);</script>
  1. Object.defineProperty() 的set()方法
<script>let person ={name:'张三',sex:'男',}functionObserver(obj){const keys = Object.keys(obj)
            keys.forEach((key)=>{
                Object.defineProperty(this,key,{set(val){
                        console.log('set方法调用了')
                        obj[key]= val
                    }})})}const obs =newObserver(person)
        obs.name=15</script>

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

  1. Vue3的Proxy实现
<body><inputtype="text"id="ipt"><pid='op'></p><script>functionreactive(obj){returnnewProxy(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><inputtype="text"id="ipt"><pid='op'></p><script>functionObserver(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 =newObserver([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)constbeforeEnter=()=>{
  console.log('进入之前')}constenter=(_,done: Function)=>{
  console.log('过度曲线')setTimeout(()=>{done()},3000)}constafterEnter=()=>{
  console.log('过度完成')}constenterCancelled=()=>{
  console.log('进入效果被打断')}constbeforeLeave=()=>{
  console.log('离开之前')}constleave=(_,done: Function)=>{setTimeout(()=>{done()},3000)
  console.log('过度曲线')}constafterLeave=()=>{
  console.log('离开之后')}constleaveCancelled=()=>{
  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)// 进入之前constbeforeEnter=(el: Element)=>{
  gsap.set(el,{width:0,height:0})}// 进入过度动画constenter=(el: Element,done: gsap.Callback)=>{
  gsap.to(el,{width:200,height:200,onComplete: done
  })}// 离开之前constbeforeLeave=(el: Element)=>{
  gsap.set(el,{width:200,height:200})}// 进入过度动画constleave=(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}}))// 打乱数组constrandom=()=>{
  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">interfaceResItf{code: number
  data:{a: number; b: number }[]message: string
}letp: Promise<ResItf>=newPromise(resolve=>{setTimeout(()=>{},3000)resolve({code:0,data:[{a:1,b:2},{a:11,b:22}],message:''})})const a =await p
console.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/exportdefaultdefineConfig({plugins:[vue(),// 配置的原子化unocss({rules:[['flex',{display:'flex'}],['red',{color:'red'}],[/^m-(\d+)$/,([, d])=>({margin:`${Number(d)*10}px`})]]})],resolve:{alias:{'@':fileURLToPath(newURL('./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/exportdefaultdefineConfig({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(newURL('./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'interfaceUse{install:(app: App,...options: any[])=>void}// 插件注册的数组const installList =newSet()exportfunction MyUse<TextendsUse>(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'exportconst app =createApp(App)// 使用插件// app.use(Loading)MyUse(Loading)
app.use(createPinia())
app.mount('#app')

type Lod ={show:()=>voidhide:()=>void}//编写ts loading 声明文件放置报错 和 智能提示
declare module '@vue/runtime-core'{exportinterfaceComponentCustomProperties{$loading: Lod
  }}

移动端适配

1.第一种适配方案

  1. 安装依赖yarn add amfe-flexible postcss [email protected]
  2. main.ts引入amfe-flexibleimport "amfe-flexible"
  3. 根目录下创建postcss.config.js文件并配置
module.exports ={plugins:{'postcss-pxtorem':{// 能够把所有元素的px单位转成Rem// rootValue: 转换px的基准值。// 编码时, 一个元素宽是75px,则换成rem之后就是2remrootValue: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/exportdefaultdefineConfig({plugins:[vue()],css:{postcss:{plugins:[// postcss-px-to-viewport的配置pxtoViewPort({unitToConvert:'px',// 要转化的单位viewportWidth:750,// UI设计稿的宽度unitPrecision:6,// 转换后的精度,即小数点位数propList:['*'],// 指定转换的css属性的单位,*代表全部css属性的单位都进行转换viewportUnit:'vw',// 指定需要转换成的视窗单位,默认vwfontViewportUnit:'vw',// 指定字体需要转换成的视窗单位,默认vwselectorBlackList:['ignore-'],// 指定不转换为视窗单位的类名,minPixelValue:1,// 默认值1,小于或等于1px则不进行转换mediaQuery:true,// 是否在媒体查询的css代码中也进行转换,默认falsereplace:true,// 是否转换后直接更换属性值landscape:false// 是否处理横屏情况})]}},resolve:{alias:{'@':fileURLToPath(newURL('./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 propertiesunitPrecision: 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
  }exportdefaultfunction(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.js
module.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'{exportinterfaceComponentCustomProperties{$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'{exportinterfaceComponentCustomProperties{$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显示constshow=()=>{
  console.log(111)
  isShow.value =true}consthide=()=>{
  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'exportdefault{install(app: App){// 变成divconstVnode: 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:()=>voidhide:()=>void}//编写ts loading 声明文件放置报错 和 智能提示
declare module '@vue/runtime-core'{exportinterfaceComponentCustomProperties{$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
}constBtn=(props: Props,ctx: any)=>{returnh('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
}constBtn=(props: Props,ctx: any)=>{returnh('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)}constgetNum=(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
}constBtn=(props: Props,ctx: any)=>{returnh('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())}constgetNum=(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/exportdefault({ mode }: any)=>{
  console.log(loadEnv(mode, process.cwd()))returndefineConfig({plugins:[vue(),// 配置rollup的插件visualizer({open:true})],resolve:{alias:{'@':fileURLToPath(newURL('./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/exportdefaultdefineConfig({...build:{chunkSizeWarningLimit:2000,cssCodeSplit:true,//css 拆分sourcemap:false,//不生成sourcemapminify:'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/exportdefaultdefineConfig({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 =newWorker("./calcBox.js");// 都使用postMessage发送消息

worker.postMessage(arrayBuffer,[arrayBuffer]);// 都使用onmessage接收消息

self.onmessage=function(e){// xxx这里是worker脚本的内容};
关闭

worker.terminate();
  1. 防抖节流

本文转载自: https://blog.csdn.net/qq_45897636/article/details/127076843
版权归原作者 会飛的魚, 所有, 如有侵权,请联系我们删除。

“Vue3+TS教程”的评论:

还没有评论