Vue3+TS教程

Vue3

组合式API

1.钩子函数steup
  1. 函数的普通用法
export default {
  setup() {

    return {}
  }
}



  

  1. 简写使用setup




 


2.响应式API
  1. ref函数
import { ref } from 'vue'

const state = ref(0)
function increment() {
  state.value++
}



  



  1. reactive函数
import { reactive } from 'vue'
const state = reactive({ count: 0 })
function increment() {
  state.count++
}



  


3.计算属性API
  1. 单向响应
import { computed,reactive } from 'vue'
const Person=reactive({X:'张',M:'三'})
 Person.XM=computed(()=>{
  return Person.X+'-'+Person.M
 })



  姓:
名:
单向响应:
  1. 双向响应
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]
  }
})



  姓:
名:
双向响应:
4.监听属性API
  1. 监听整个对象


import {reactive,watch} from 'vue'
const Person=reactive({name:'张三',age:18, job:{salary:20}})
      watch(Person,(newVal,oldVal)=>{
         console.log('用户信息发生了变化',newVal,oldVal);
      })



 

年龄:{{Person.age}}

  1. 监听对象中单个属性


import {reactive,watch} from 'vue'
const Person=reactive({name:'张三',age:18, job:{salary:20}})
      watch(()=>Person.age,(newVal,oldVal)=>{
         console.log('用户年龄发生了变化',newVal,oldVal);
      })



 

年龄:{{Person.age}}

  1. 监听多个对象


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);
      })



 

姓名:{{Person.name}}

年龄:{{Person.age}}

  1. 监听对象中对象(深度监听)


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
      })



 

薪资:{{Person.job.salary}}K

5.高级监听API
  1. 基本使用(默认执行一次)


import {reactive,watchEffect} from 'vue'
const Person=reactive({
        name:'张三'
      })
     
     watchEffect(()=>{
     Person.name
     console.log('姓名发送了变化');
     })



 

姓名:{{Person.name}}

  1. 监听御前处理oninvalidate参数
import { reactive, watchEffect } from "vue";
const Person = reactive({
  name: "张三",
});

watchEffect((oninvalidate) => {
  oninvalidate(() => {
    console.log("before");
  });
  Person.name;
  console.log("姓名发送了变化");
});



  

姓名:{{ Person.name }}

  1. 停止监听
import { reactive, watchEffect } from "vue";
const Person = reactive({
  name: "张三",
});

const stop = watchEffect((oninvalidate) => {
  oninvalidate(() => {
    console.log("before");
  });
  Person.name;
  console.log("姓名发送了变化");



  

姓名:{{ Person.name }}

6.响应式对象解构API
  1. toRef函数
import {reactive,toRef} from 'vue'
const person=reactive({A:1,B:2})
const A=toRef(person,'A')



 

姓名:{{A}}

  1. toRefs
  import {reactive,toRefs} from 'vue'
  const person=reactive({A:1,B:2})
  const {A,B}=toRefs(person)
  
  
  
   

姓名:{{A}}

7.生命周期API
  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('---卸载---');
   })


8.ref获取dom
  
    我是div
  



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 };
  },
};



9.Hooks
(1)官方hooks
  1. useAttrs()


  





import { useAttrs } from 'vue'
let attr = useAttrs()
console.log(attr)


(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
import BASE64 from './hooks'
BASE64({ el: '#img' }).then(resolve => {
  console.log(resolve.baseUrl)
})


(3)第三方hooks
  1. 安装依赖yarn add @vueuse/core
  2. 简单使用
import { ref } from 'vue'
import { useDraggable } from '@vueuse/core'

const el = ref(null)

// `style` will be a helper computed for `left: ?px; top: ?px;`
const { x, y, style } = useDraggable(el, {
  initialValue: { x: 40, y: 40 }
})



  
    Drag me! I am at {{ x }}, {{ y }}
  



组件间通讯

1.props父传子
  1. 父组件
import HelloWorld from './components/HelloWorld.vue'



  

  1. 子组件
// const props=defineProps(['msg'])
const props=defineProps({msg:String})
console.log(props.msg)

2.emit子传父
  1. 父组件
import HelloWorld from './components/HelloWorld.vue'
const getuser=(a)=>{
  console.log(a)
}



  

  1. 子组件
const emit = defineEmits(['getuser'])

function buttonClick() {
  emit('getuser',1)
}
  


  


  1. 自定义事件事件校检
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 })
}


3.插槽通讯
(1)匿名插槽
  1. 子组件
  
 

  1. 父组件
import HelloWorld from "./components/HelloWorld.vue";



  
     插槽传递
  

(2)具名插槽
  1. 父组件
import HelloWorld from "./components/HelloWorld.vue";




  
    
    
      
    
  

  1. 子组件
  
 

(3)作用域插槽
  1. 理解:数据在子组件的自身,但根据数据生成的结构需要父组件决定。
  2. 父组件
import HelloWorld from "./components/HelloWorld.vue";
const person=[{name:'小明',age:18},{name:'小红',age:20}]



// 父组件将信息传递给子组件

// 子组件接收父组件的插槽中传的值
  

  {{item.name}}
  {{item.age}}
  




  1. 子组件
const props=defineProps<{person:{name:string,age:number}[]}>()



     
      // 向作用插槽中传值
      
     
姓名 年龄 操作
4.依赖注入
  1. 父组件(祖先组件)


  
    
    
  



import { provide, ref } from 'vue'
import A from './components/Acom.vue'
let flag = ref(1)
provide('flag', flag)

  1. 子组件(后代组件)
  
    我是B
    {{ flag }}
    
  



import { inject, ref } from 'vue'
//  注入值,默认值(让其可以进行类型推断)
const flag = inject('flag', ref(1))


5.兄弟传参
(1)父组件当成一个桥梁
(2)发布订阅模式
  1. Bus传递
type BusClass = {
  emit: (name: string) => void
  on: (name: string, callback: Function) => void
}

type PramsKey = string | number | symbol

type List = {
  [key: PramsKey]: Array
}
class Bus implements BusClass {
  list: List
  constructor() {
    this.list = {}
  }
  emit(name: string, ...args: Array) {
    const evnentName: Array = this.list[name]
    evnentName.forEach(fn => {
      fn.apply(this, args)
    })
  }
  on(name: string, callback: Function) {
    const fn: Array = this.list[name] || []
    fn.push(callback)
    this.list[name] = fn
  }
}

export default new Bus()

  1. A组件传递数值
import { ref } from 'vue'
import Bus from '../utils/Bus'

const flag = ref(1)
const Pass = () => {
  Bus.emit('pass', flag)
}



  
    我是A
    {{ flag }}
    
  




  1. B组件接收数值
import Bus from '../utils/Bus'
import { ref, type Ref } from 'vue'

const flag = ref(0)
Bus.on('pass', (Flag: Ref) => {
  console.log(Flag)
  flag.value = Flag.value
})



  
    我是B
    {{ flag }}
    
  




(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 = Mit
app.mount('#app')

  1. A组件传递数值
import { getCurrentInstance, ref } from 'vue'

const instance = getCurrentInstance()
const flag = ref(1)
const Pass = () => {
  instance?.proxy?.$Bus.emit('pass', flag)
}



  
    我是A
    {{ flag }}
    
  




  1. B组件接收数值
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).value
})



  
    我是B
    {{ flag }}
    
  




  1. *监听事件
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).value
})



  1. 取消监听事件
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).value
})


  1. 取消全部监听事件
import { getCurrentInstance, ref, } from 'vue'

const instance = getCurrentInstance()

instance?.proxy?.$Bus.all.clear()


Typescript的支持

1.全局接口的抽取
  1. src下定义types文件夹命名xx.d.ts
  2. 建立Person接口person.d.ts
interface personInterface{
    name:string
    age:number
}
  1. 组件中直接使用
const props=defineProps<{person:personInterface[]}>()


  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中定义数据

  
  ...
  
  
    const  global=1
  
  
    ...
  


  1. 定义类型增强
// common.d.ts
declare const global:string;
  1. 组件中直接读取
console.log(global)

3.第三方库类型声明
  1. 安装一个库
  2. 安装库的ts类型声明@types/xxxx
4.props组件通讯TS
  1. 父组件
import HelloWorld from './components/HelloWorld.vue'



  

  1. 子组件
interface msgIterface{
  msg:string
}
const props = withDefaults(defineProps(),{
	 msg:'默认值'
	})
console.log(props.msg)

5.emit组件通讯TS
  1. 父组件
import HelloWorld from './components/HelloWorld.vue'
const getuser=(a:number)=>{
  console.log(a)
}



  





  1. 子组件

 const emit = defineEmits<{(e: 'getuser', id: number): void}>()
  // (e: 事件名, 键名:类型): void
function buttonClick() {
  emit('getuser',1)
}
  


  






6.依赖注入类型推断
  1. 父组件(祖先组件)
  
    
    
  



import { provide, ref } from 'vue'
import A from './components/Acom.vue'
let flag = ref(1)
provide('flag', flag)

  1. 子组件(后代组件)
  
    我是B
    {{ flag }}
    
  



import { inject, ref , type Ref} from 'vue'
//  注入值,默认值(让其可以进行类型推断)
const flag<Ref> = inject('flag', ref(1))


7.定义全局函数和全局函数的类型支持
import { createApp } from 'vue'
...
const app = createApp(App)
type Fileter = {
  format: (str: T) => string
}
declare module '@vue/runtime-core' {
  export interface ComponentCustomProperties {
    $filters: Fileter
    $env: string
  }
}

// 全局函数
app.config.globalProperties.$filters = {
  format(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中
console.log(import.meta.env)

  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. 父组件
import HelloWorld from "./components/HelloWorld.vue";
import { ref } from "vue";
const num=ref(1)



  


  1. 子组件
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)
  }
})



 


  1. v-model的原理
<!--  -->
  


(2)v-model传递特定的名称
  1. 父组件
import { ref } from "vue";
import HelloWorld from "./components/HelloWorld.vue";
const num=ref(1)



  <!--  -->
  


  1. 子组件
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)
  }
})



 


2.自定义指令
(1)自定义指令的简单使用
  1. 全局自定义指令
// mian.ts
import { 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. 使用自定义指令
 


  1. 局部自定义指令
// 在模板中启用 v-focus
const vFocus = {
  mounted: (el) => el.focus()
}



  

(2)自定义指令详解
  1. 自定义指令的生命周期
import type { Directive, DirectiveBinding } from 'vue'

type Dir = { background: string }
const vMove: Directive = {
  created() {}, //元素初始化的时候
  beforeMount() {}, //指令绑定到元素后调用 只调用一次
  mounted(el: HTMLElement, dir: DirectiveBinding) {
    console.log(dir.value.background)
    el.style.background = dir.value.background
  }, //元素插入父级dom调用
  beforeUpdate() {}, //元素被更新之前调用
  updated() {}, //这个周期方法被移除 改用updated
  beforeUnmount() {}, //在元素被移除前调用
  unmounted() {} //指令被移除后调用 只调用一次
}



  
  自定义指令




  1. 生命周期的简写
import type { Directive, DirectiveBinding } from 'vue'

type Dir = { background: string }
const vMove: Directive = (el: HTMLElement, dir: DirectiveBinding) => {
  el.style.background = dir.value.background
}



  
  自定义指令




  1. 自定义拖拽指令
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)
    })
  })
}



  
  
    
      自定义指令
    
  


响应式原理

1.了解Proxy
  1. Proxy代理的get方法
    
        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)
    
  1. Proxy代理的set方法
    
        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)
    
2.了解Object.defineProperty
  1. Object.defineProperty(对象.定义属性,用来为一个对象添加新属性)
  let person = {
				name:'张三',
				sex:'男',
			}
			
			// 为 person对象 传输了一个新属性 “age”,并且设定它的值为 18
			Object.defineProperty(person,'age',{
				value=18
			})
			console.log(person)

  1. Object.defineProperty属性的可枚举可修改的实现
  let person = {
				name:'张三',
				sex:'男',
			}
			
			// 为 person对象 传输了一个新属性 “age”,并且设定它的值为 18
			Object.defineProperty(person,'age',{
        enumerable=true  // 可枚举
        writable=true   // 可修改
        configurable:true // 可删除
				value=18
			})
			console.log(person)

  1. Object.defineProperty() 的get()方法
 
        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);
    
  1. Object.defineProperty() 的set()方法
 
        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
    
3.Vue双向绑定的实现的对比
  1. Vue3的Proxy实现
    
    

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] })
  1. Vue2的Object.defineProperty实现
    
    

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] })
  1. 上面的测试,Object.property是可以检测到通过索引改变数组的操作的,而Vue没有实现,Object.defineProperty表示这个锅我不背

内置组件

1.内置组件
(1)Teleport组件
  1. 可以将一个组件内部的一部分模板“传送”到该组件的 DOM 结构外层的位置去
  2. 父组件


import Acom from './components/Acom.vue'








.app{
  width: 200px;
  height: 200px;
  background-color: pink;
}


  1. 子组件
  import { ref } from 'vue'
  
  const open = ref(false)
  
  
  
    
    
  
    
       X
   
  
  
  
  
  
  .cover {
   position: absolute;
   z-index:2;
   top: 0;
   left: 0;
   bottom: 0;
   right: 0;
   background-color: rgba(0,0,0,0.5);
  }
  
  

(2)Transition组件
  1. 非命名动画
import { ref } from 'vue';

const show=ref(true)











.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;
}


  1. 命名动画
import { ref } from 'vue';

const show=ref(true)











.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;
}


  1. 过度动画
  ...

  1. 结合第三方库Animate.css


import { ref } from 'vue'
import 'animate.css'
import Acom from './components/Acom.vue'
const show = ref(true)



  
    
  

  




  1. transition 生命周期
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('离开效果被打断')
}



  
    
  

  



  1. 生命周期结合第三方库gsap.js


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
  })
}



  
    
  

  



  1. 初始化动画
import { ref } from 'vue'
import Acom from './components/Acom.vue'

const show = ref(true)



  
    
  

  



.from {
  /* 初始化之前 */
  width: 0;
  height: 0;
}
.active {
  /* 过度动画 */
  transition: all 2s ease;
}
.to {
  /* 初始化完成 */
  width: 200px;
  height: 200px;
}


  1. 初始化动画结合Animate.css
import { ref } from 'vue'
import Acom from './components/Acom.vue'
import 'animate.css'
const show = ref(true)



  
    
  

  




(3)transition-group过度列表
  1. Transition组件无法对v-for的列表进行渲染
  2. transition-group的tag属性


  
    
      
      {{ item }}
    
  


  1. 添加列表时的动画效果
import { ref } from 'vue'
import 'animate.css'
const num = ref(5)



  
    
      
      {{ item }}
    
  
  
  



.wraps {
  display: flex;
  flex-wrap: wrap;
  word-break: break-all;
  border: 1px solid #ccc;
  .item {
    margin: 10px;
  }
}


  1. 平移动画move-class
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)



  
    
    
      
        {{ item.number }}
      
    
  



.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;
}


  1. 状态过度(数字过度颜色过度)
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
    })
  }
)



  
    
    
      
      {{ num.tweenedNumber.toFixed(0) }}
    
  




(4)keep-alive组件
  1. 开启keep-alive 生命周期的变化
初次进入时: onMounted-> onActivated
退出后触发:  deactivated
  1. 缓存数据
import { ref } from 'vue'
import Acom from './components/Acom.vue'

const show = ref(true)



  
    
  
  


  1. include属性和exclude属性


import { ref } from 'vue'
import Acom from './components/Acom.vue'
import Bcom from './components/Bcom.vue'
const show = ref(true)



  
    
    
  
  




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. 使用组件
  
    
  

(2)异步组件
  1. 子组件中发送了请求变成异步
interface ResItf {
  code: number
  data: { a: number; b: number }[]
  message: string
}

let p: Promise = new Promise(resolve => {
  setTimeout(() => {}, 3000)
  resolve({
    code: 0,
    data: [
      { a: 1, b: 2 },
      { a: 11, b: 22 }
    ],
    message: ''
  })
})
const a = await p
console.log(a)



  异步组件
  异步组件
  异步组件

  1. 父组件异步调用组件
// 异步组件不能这样引入
// import Acom from './components/Acom.vue'
import { defineAsyncComponent } from 'vue'
const Acom = defineAsyncComponent(() => import('./components/Acom.vue'))



  
    
      
        
      

       加载中。。。 
    
  




语法糖组件命名问题

  1. 安装依赖yarn add vite-plugin-vue-setup-extend
  2. 直接命名


常用的CSS的功能

  1. 样式穿透
:deep(input) {
  color: red;
}


  1. 插槽选择器
  
     
  



:slotted(.li) {
  color: red;
}


  1. 全局选择器


  
     
  



:global(.li) {
  color: red;
}


  1. 动态CSS
import { reactive } from 'vue'
const style = reactive({
  color: 'red'
})
setTimeout(() => {
  style.color = 'blue'
}, 3000)



  动态css



.div {
  color: v-bind('style.color');
}



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
 left
  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的样式


  
    hello tailwind
  




面试常用源码

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(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): 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: (str: T) => string
}
declare module '@vue/runtime-core' {
  export interface ComponentCustomProperties {
    $filters: Fileter
  }
}

// 全局函数
app.config.globalProperties.$filters = {
  format(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. 封装插件的样式,抛出插件的显示隐藏方法
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
})



  loading....




  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. 使用插件
import { getCurrentInstance } from 'vue'

const instance = getCurrentInstance()
// 调用插件
instance?.proxy?.$loading.show()
// 5秒关闭插件
setTimeout(() => {
  instance?.proxy?.$loading.hide()
}, 5000)



  




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 respectively
h('div', { '.name': 'some-name', '^width': '100' })
 
// class 和 style 可以是对象或者数组
h('div', { class: [foo, { bar }], style: { color: 'red' } })
 
// 定义事件需要加on 如 onXxx
h('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传递参数
    

  

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)
}

  1. 接收emit
    

  

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);
}

  1. 定义插槽
    
        
            按钮slots
        
    

  

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);
}

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')



  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. 防抖节流

本文来自网络,不代表协通编程立场,如若转载,请注明出处:https://net2asp.com/5f799a841f.html