当前位置:   article > 正文

Vue3+TS教程

Vue3+TS教程

Vue3

组合式API

1.钩子函数steup
  1. 函数的普通用法
  1. <script>
  2. export default {
  3. setup() {
  4. return {}
  5. }
  6. }
  7. </script>
  8. <template>
  9. </template>
  1. 简写使用setup
  1. <script setup>
  2. </script>
  3. <template>
  4. </template>
2.响应式API
  1. ref函数
  1. <script setup>
  2. import { ref } from 'vue'
  3. const state = ref(0)
  4. function increment() {
  5. state.value++
  6. }
  7. </script>
  8. <template>
  9. <button @click="increment">
  10. {{ state }}
  11. </button>
  12. </template>

  1. reactive函数
  1. <script setup>
  2. import { reactive } from 'vue'
  3. const state = reactive({ count: 0 })
  4. function increment() {
  5. state.count++
  6. }
  7. </script>
  8. <template>
  9. <button @click="increment">
  10. {{ state.count }}
  11. </button>
  12. </template>
3.计算属性API
  1. 单向响应
  1. <script setup>
  2. import { computed,reactive } from 'vue'
  3. const Person=reactive({X:'张',M:'三'})
  4. Person.XM=computed(()=>{
  5. return Person.X+'-'+Person.M
  6. })
  7. </script>
  8. <template>
  9. 姓:<input v-model="Person.X"><br>
  10. 名:<input v-model="Person.M"><br>
  11. 单向响应:<input v-model="Person.XM">
  12. </template>
  1. 双向响应
  1. <script setup>
  2. import { computed,reactive } from 'vue'
  3. const Person=reactive({X:'张',M:'三'})
  4. Person.AXM=computed({
  5. get(){
  6. return Person.X+'-'+Person.M
  7. },
  8. set(value){
  9. const arr=value.split('-')
  10. Person.X=arr[0]
  11. Person.M=arr[1]
  12. }
  13. })
  14. </script>
  15. <template>
  16. 姓:<input v-model="Person.X"><br>
  17. 名:<input v-model="Person.M"><br>
  18. 双向响应:<input v-model="Person.AXM">
  19. </template>

4.监听属性API
  1. 监听整个对象
  1. <!-- // 监听整个对象,由于是浅拷贝,他们新旧指向的是通一个对象 -->
  2. <script setup>
  3. import {reactive,watch} from 'vue'
  4. const Person=reactive({name:'张三',age:18, job:{salary:20}})
  5. watch(Person,(newVal,oldVal)=>{
  6. console.log('用户信息发生了变化',newVal,oldVal);
  7. })
  8. </script>
  9. <template>
  10. <h2>年龄:{{Person.age}}</h2>
  11. <button @click="Person.age++">+1</button>
  12. </template>
  1. 监听对象中单个属性
  1. <!-- 监听对象中单个属性,监听单个属性可以检测到新旧值 -->
  2. <script setup>
  3. import {reactive,watch} from 'vue'
  4. const Person=reactive({name:'张三',age:18, job:{salary:20}})
  5. watch(()=>Person.age,(newVal,oldVal)=>{
  6. console.log('用户年龄发生了变化',newVal,oldVal);
  7. })
  8. </script>
  9. <template>
  10. <h2>年龄:{{Person.age}}</h2>
  11. <button @click="Person.age++">+1</button>
  12. </template>
  1. 监听多个对象
  1. <!-- 监听对象中多个个属性,监听单个属性可以检测到新旧值 -->
  2. <script setup>
  3. import {reactive,watch} from 'vue'
  4. const Person=reactive({name:'张三',age:18, job:{salary:20}})
  5. watch([()=>Person.name,()=>Person.age],(newValue,oldValue)=>{
  6. console.log('person.name或者person.age的值变化了',newValue,oldValue);
  7. })
  8. </script>
  9. <template>
  10. <h2>姓名:{{Person.name}}</h2>
  11. <button @click="Person.name+='~'">修改</button>
  12. <h2>年龄:{{Person.age}}</h2>
  13. <button @click="Person.age++">+1</button>
  14. </template>

  1. 监听对象中对象(深度监听)
  1. <!-- 监听对象中对象,必须开启深度监听,一般情况不监听对象 -->
  2. <script setup>
  3. import {reactive,watch} from 'vue'
  4. const Person=reactive({name:'张三',age:18, job:{salary:20}})
  5. watch(()=>Person.job,(newValue,oldValue)=>{
  6. console.log('person.job的值变化了',newValue,oldValue);
  7. },{
  8. deep:true
  9. })
  10. </script>
  11. <template>
  12. <h2>薪资:{{Person.job.salary}}K</h2>
  13. <button @click="Person.job.salary++">+1</button>
  14. </template>

5.高级监听API
  1. 基本使用(默认执行一次)
  1. <!-- watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。 -->
  2. <script setup>
  3. import {reactive,watchEffect} from 'vue'
  4. const Person=reactive({
  5. name:'张三'
  6. })
  7. watchEffect(()=>{
  8. Person.name
  9. console.log('姓名发送了变化');
  10. })
  11. </script>
  12. <template>
  13. <h2>姓名:{{Person.name}}</h2>
  14. <button @click="Person.name+='~'">修改</button>
  15. </template>

  1. 监听御前处理oninvalidate参数
  1. <script setup lang="ts">
  2. import { reactive, watchEffect } from "vue";
  3. const Person = reactive({
  4. name: "张三",
  5. });
  6. watchEffect((oninvalidate) => {
  7. oninvalidate(() => {
  8. console.log("before");
  9. });
  10. Person.name;
  11. console.log("姓名发送了变化");
  12. });
  13. </script>
  14. <template>
  15. <h2>姓名:{{ Person.name }}</h2>
  16. <button @click="Person.name += '~'">修改</button>
  17. </template>

  1. 停止监听
  1. <script setup lang="ts">
  2. import { reactive, watchEffect } from "vue";
  3. const Person = reactive({
  4. name: "张三",
  5. });
  6. const stop = watchEffect((oninvalidate) => {
  7. oninvalidate(() => {
  8. console.log("before");
  9. });
  10. Person.name;
  11. console.log("姓名发送了变化");
  12. </script>
  13. <template>
  14. <h2>姓名:{{ Person.name }}</h2>
  15. <button @click="Person.name += '~'">修改</button>
  16. <button @click="stop">停止</button>
  17. </template>

6.响应式对象解构API
  1. toRef函数
  1. <script setup>
  2. import {reactive,toRef} from 'vue'
  3. const person=reactive({A:1,B:2})
  4. const A=toRef(person,'A')
  5. </script>
  6. <template>
  7. <h2>姓名:{{A}}</h2>
  8. <button @click="person.A+='~'">修改</button>
  9. </template>
  1. toRefs
  1. <script setup lang="ts">
  2. import {reactive,toRefs} from 'vue'
  3. const person=reactive({A:1,B:2})
  4. const {A,B}=toRefs(person)
  5. </script>
  6. <template>
  7. <h2>姓名:{{A}}</h2>
  8. <button @click="A+=1">修改</button>
  9. </template>
7.生命周期API
  1. <script setup>
  2. import {onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted,ref} from "vue";
  3. onBeforeMount(()=>{
  4. console.log('---挂载之前---');
  5. })
  6. onMounted(()=>{
  7. console.log('---挂载---');
  8. })
  9. onBeforeUpdate(()=>{
  10. console.log('---更新之前---');
  11. })
  12. onUpdated(()=>{
  13. console.log('---更新---');
  14. })
  15. onBeforeUnmount(()=>{
  16. console.log('---卸载之前---');
  17. })
  18. onUnmounted(()=>{
  19. console.log('---卸载---');
  20. })
  21. </script>

8.ref获取dom
  1. <template>
  2. <div>
  3. <div ref="box">我是div</div>
  4. </div>
  5. </template>
  6. <script>
  7. import { ref,onMounted } from "vue";
  8. export default {
  9. setup() {
  10. let box = ref(null); //本质是reactive({value:null})
  11. // 需要在生命周期获取
  12. onMounted(()=>{
  13. // 当界面挂载出来后就会自动执行
  14. console.log(box.value);
  15. })
  16. //接受的是null,原因是setup执行时机比mounted早,dom还没形成
  17. console.log(box.value);
  18. return { box };
  19. },
  20. };
  21. </script>

9.Hooks
(1)官方hooks
  1. useAttrs()
  1. <!-- 父组件 -->
  2. <template>
  3. <Acom a="456" title="789" />
  4. </template>
  5. <!-- 子组件 -->
  6. <!-- 获取父组件传过来的全部参数 -->
  7. <script setup lang="ts">
  8. import { useAttrs } from 'vue'
  9. let attr = useAttrs()
  10. console.log(attr)
  11. </script>
(2)自定hooks
  1. 自定义hooks转换图片
  1. import { onMounted } from 'vue'
  2. type Options = {
  3. el: string
  4. }
  5. export default function (options: Options): Promise<{ baseUrl: string }> {
  6. return new Promise(resolve => {
  7. onMounted(() => {
  8. const img: HTMLImageElement = document.querySelector(
  9. options.el
  10. ) as HTMLImageElement
  11. img.onload = () => {
  12. resolve({
  13. baseUrl: base64(img)
  14. })
  15. }
  16. })
  17. const base64 = (el: HTMLImageElement) => {
  18. const canvas = document.createElement('canvas')
  19. const ctx = canvas.getContext('2d')
  20. canvas.width = el.width
  21. canvas.height = el.height
  22. ctx?.drawImage(el, 0, 0, canvas.width, canvas.height)
  23. return canvas.toDataURL('image/jpg')
  24. }
  25. })
  26. }

  1. 使用hooks
  1. <script setup lang="ts">
  2. import BASE64 from './hooks'
  3. BASE64({ el: '#img' }).then(resolve => {
  4. console.log(resolve.baseUrl)
  5. })
  6. </script>
(3)第三方hooks
  1. 安装依赖yarn add @vueuse/core
  2. 简单使用
  1. <script setup lang="ts">
  2. import { ref } from 'vue'
  3. import { useDraggable } from '@vueuse/core'
  4. const el = ref<HTMLElement | null>(null)
  5. // `style` will be a helper computed for `left: ?px; top: ?px;`
  6. const { x, y, style } = useDraggable(el, {
  7. initialValue: { x: 40, y: 40 }
  8. })
  9. </script>
  10. <template>
  11. <div ref="el" :style="style" style="position: fixed">
  12. Drag me! I am at {{ x }}, {{ y }}
  13. </div>
  14. </template>

组件间通讯

1.props父传子
  1. 父组件
  1. <script setup >
  2. import HelloWorld from './components/HelloWorld.vue'
  3. </script>
  4. <template>
  5. <HelloWorld msg="1"/>
  6. </template>
  1. 子组件
  1. <script setup>
  2. // const props=defineProps(['msg'])
  3. const props=defineProps({msg:String})
  4. console.log(props.msg)
  5. </script>
2.emit子传父
  1. 父组件
  1. <script setup >
  2. import HelloWorld from './components/HelloWorld.vue'
  3. const getuser=(a)=>{
  4. console.log(a)
  5. }
  6. </script>
  7. <template>
  8. <HelloWorld @getuser="getuser"/>
  9. </template>
  1. 子组件
  1. <script setup lang="ts">
  2. const emit = defineEmits(['getuser'])
  3. function buttonClick() {
  4. emit('getuser',1)
  5. }
  6. </script>
  7. <template>
  8. <button @click="buttonClick">传输</button>
  9. </template>
  1. 自定义事件事件校检
  1. <script setup>
  2. const emit = defineEmits({
  3. // 没有校验
  4. click: null,
  5. // 校验 submit 事件
  6. submit: ({ email, password }) => {
  7. if (email && password) {
  8. return true
  9. } else {
  10. console.warn('Invalid submit event payload!')
  11. return false
  12. }
  13. }
  14. })
  15. function submitForm(email, password) {
  16. emit('submit', { email, password })
  17. }
  18. </script>

3.插槽通讯
(1)匿名插槽
  1. 子组件
  1. <template>
  2. <!-- slot插槽占位 -->
  3. <slot></slot>
  4. </template>
  1. 父组件
  1. <script setup lang="ts">
  2. import HelloWorld from "./components/HelloWorld.vue";
  3. </script>
  4. <template>
  5. <HelloWorld>
  6. 插槽传递
  7. </HelloWorld>
  8. </template>
(2)具名插槽
  1. 父组件
  1. <script setup lang="ts">
  2. import HelloWorld from "./components/HelloWorld.vue";
  3. </script>
  4. <template>
  5. <HelloWorld>
  6. <!-- v-slot:简写# -->
  7. <template v-slot:btn>
  8. <button>具名插槽</button>
  9. </template>
  10. </HelloWorld>
  11. </template>
  1. 子组件
  1. <template>
  2. <!-- slot插槽占位 -->
  3. <slot name="btn"></slot>
  4. </template>
(3)作用域插槽
  1. 理解:数据在子组件的自身,但根据数据生成的结构需要父组件决定。
  2. 父组件
  1. <script setup lang="ts">
  2. import HelloWorld from "./components/HelloWorld.vue";
  3. const person=[{name:'小明',age:18},{name:'小红',age:20}]
  4. </script>
  5. <template>
  6. // 父组件将信息传递给子组件
  7. <HelloWorld :person="person">
  8. // 子组件接收父组件的插槽中传的值
  9. <template #tab="scope">
  10. <tr v-for="(item,index) in scope.person" :key="index">
  11. <th>{{item.name}}</th>
  12. <th>{{item.age}}</th>
  13. <th><button >编辑</button></th>
  14. </tr>
  15. </template>
  16. </HelloWorld>
  17. </template>

  1. 子组件
  1. <script setup lang="ts">
  2. const props=defineProps<{person:{name:string,age:number}[]}>()
  3. </script>
  4. <template>
  5. <table border="1">
  6. <tr>
  7. <th>姓名</th>
  8. <th>年龄</th>
  9. <th>操作</th>
  10. </tr>
  11. <!-- 作用域插槽命名 -->
  12. // 向作用插槽中传值
  13. <slot name="tab" :person="props.person"></slot>
  14. </table>
  15. </template>

4.依赖注入
  1. 父组件(祖先组件)
  1. <!-- 依赖注入传的参可以在子组件中改变 -->
  2. <template>
  3. <div class="App">
  4. <button>我是App</button>
  5. <A></A>
  6. </div>
  7. </template>
  8. <script setup lang="ts">
  9. import { provide, ref } from 'vue'
  10. import A from './components/Acom.vue'
  11. let flag = ref<number>(1)
  12. provide('flag', flag)
  13. </script>
  1. 子组件(后代组件)
  1. <template>
  2. <div>
  3. 我是B
  4. <div>{{ flag }}</div>
  5. <button @click="flag++">+1</button>
  6. </div>
  7. </template>
  8. <script setup lang="ts">
  9. import { inject, ref } from 'vue'
  10. // 注入值,默认值(让其可以进行类型推断)
  11. const flag = inject('flag', ref(1))
  12. </script>
5.兄弟传参
(1)父组件当成一个桥梁
(2)发布订阅模式
  1. Bus传递
  1. type BusClass = {
  2. emit: (name: string) => void
  3. on: (name: string, callback: Function) => void
  4. }
  5. type PramsKey = string | number | symbol
  6. type List = {
  7. [key: PramsKey]: Array<Function>
  8. }
  9. class Bus implements BusClass {
  10. list: List
  11. constructor() {
  12. this.list = {}
  13. }
  14. emit(name: string, ...args: Array<any>) {
  15. const evnentName: Array<Function> = this.list[name]
  16. evnentName.forEach(fn => {
  17. fn.apply(this, args)
  18. })
  19. }
  20. on(name: string, callback: Function) {
  21. const fn: Array<Function> = this.list[name] || []
  22. fn.push(callback)
  23. this.list[name] = fn
  24. }
  25. }
  26. export default new Bus()

  1. A组件传递数值
  1. <script setup lang="ts">
  2. import { ref } from 'vue'
  3. import Bus from '../utils/Bus'
  4. const flag = ref(1)
  5. const Pass = () => {
  6. Bus.emit('pass', flag)
  7. }
  8. </script>
  9. <template>
  10. <div>
  11. 我是A
  12. <div>{{ flag }}</div>
  13. <button @click="Pass">Pass</button>
  14. </div>
  15. </template>
  16. <style scoped lang="less"></style>

  1. B组件接收数值
  1. <script setup lang="ts">
  2. import Bus from '../utils/Bus'
  3. import { ref, type Ref } from 'vue'
  4. const flag = ref(0)
  5. Bus.on('pass', (Flag: Ref<number>) => {
  6. console.log(Flag)
  7. flag.value = Flag.value
  8. })
  9. </script>
  10. <template>
  11. <div>
  12. 我是B
  13. <div>{{ flag }}</div>
  14. <button @click="flag++">+</button>
  15. </div>
  16. </template>
  17. <style scoped lang="less"></style>

(3)第三方库mitt
  1. 安装yarn add mitt
  2. 全局挂载mit
  1. import { createApp } from 'vue'
  2. import { createPinia } from 'pinia'
  3. import App from './App.vue'
  4. import './assets/main.css'
  5. import mitt from 'mitt'
  6. const Mit = mitt()
  7. const app = createApp(App)
  8. // 类型声明
  9. declare module 'vue' {
  10. export interface ComponentCustomProperties {
  11. $Bus: typeof Mit
  12. }
  13. }
  14. app.use(createPinia())
  15. app.config.globalProperties.$Bus = Mit
  16. app.mount('#app')

  1. A组件传递数值
  1. <script setup lang="ts">
  2. import { getCurrentInstance, ref } from 'vue'
  3. const instance = getCurrentInstance()
  4. const flag = ref(1)
  5. const Pass = () => {
  6. instance?.proxy?.$Bus.emit('pass', flag)
  7. }
  8. </script>
  9. <template>
  10. <div>
  11. 我是A
  12. <div>{{ flag }}</div>
  13. <button @click="Pass">Pass</button>
  14. </div>
  15. </template>
  16. <style scoped lang="less"></style>

  1. B组件接收数值
  1. <script setup lang="ts">
  2. import { getCurrentInstance, ref, type Ref } from 'vue'
  3. const instance = getCurrentInstance()
  4. const flag = ref(0)
  5. instance?.proxy?.$Bus.on('pass', Flag => {
  6. flag.value = (Flag as Ref<number>).value
  7. })
  8. </script>
  9. <template>
  10. <div>
  11. 我是B
  12. <div>{{ flag }}</div>
  13. <button @click="flag++">+</button>
  14. </div>
  15. </template>
  16. <style scoped lang="less"></style>

  1. *监听事件
  1. <script setup lang="ts">
  2. import { getCurrentInstance, ref, type Ref } from 'vue'
  3. const instance = getCurrentInstance()
  4. const flag = ref(0)
  5. /**
  6. * type:事件名称
  7. * Flag:传递参数
  8. */
  9. instance?.proxy?.$Bus.on('*', (type, Flag) => {
  10. flag.value = (Flag as Ref<number>).value
  11. })
  12. </script>
  1. 取消监听事件
  1. <script setup lang="ts">
  2. import { getCurrentInstance, ref, type Ref } from 'vue'
  3. const instance = getCurrentInstance()
  4. const flag = ref(0)
  5. instance?.proxy?.$Bus.off('pass', Flag => {
  6. flag.value = (Flag as Ref<number>).value
  7. })
  8. </script>
  1. 取消全部监听事件
  1. <script setup lang="ts">
  2. import { getCurrentInstance, ref, } from 'vue'
  3. const instance = getCurrentInstance()
  4. instance?.proxy?.$Bus.all.clear()
  5. </script>

Typescript的支持

1.全局接口的抽取
  1. src下定义types文件夹命名xx.d.ts
  2. 建立Person接口person.d.ts
  1. interface personInterface{
  2. name:string
  3. age:number
  4. }
  1. 组件中直接使用
  1. <script setup lang="ts">
  2. const props=defineProps<{person:personInterface[]}>()
  3. </script>
  1. 如果不是在src下或src文件下的xx.d.ts文件则需要在tsconfig.json中配置
  1. {
  2. {
  3. ...
  4. },
  5. "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], //配置全局目录
  6. "references": [{ "path": "./tsconfig.node.json" }]
  7. }
2.类型增强
  1. 使用环境:全局定义的数据,函数在vue组件中直接访问报错
  2. index.html中定义数据
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. ...
  5. </head>
  6. <script>
  7. const global=1
  8. </script>
  9. <body>
  10. ...
  11. </body>
  12. </html>
  1. 定义类型增强
  1. // common.d.ts
  2. declare const global:string;
  1. 组件中直接读取
  1. <script setup lang="ts">
  2. console.log(global)
  3. </script>
3.第三方库类型声明
  1. 安装一个库
  2. 安装库的ts类型声明@types/xxxx
4.props组件通讯TS
  1. 父组件
  1. <script setup lang="ts">
  2. import HelloWorld from './components/HelloWorld.vue'
  3. </script>
  4. <template>
  5. <HelloWorld msg="1"/>
  6. </template>
  1. 子组件
  1. <script setup lang="ts">
  2. interface msgIterface{
  3. msg:string
  4. }
  5. const props = withDefaults(defineProps<msgIterface>(),{
  6. msg:'默认值'
  7. })
  8. console.log(props.msg)
  9. </script>
5.emit组件通讯TS
  1. 父组件
  1. <script setup lang="ts">
  2. import HelloWorld from './components/HelloWorld.vue'
  3. const getuser=(a:number)=>{
  4. console.log(a)
  5. }
  6. </script>
  7. <template>
  8. <HelloWorld @getuser="getuser"/>
  9. </template>
  10. <style scoped>
  11. </style>
  1. 子组件
  1. <script setup lang="ts">
  2. const emit = defineEmits<{(e: 'getuser', id: number): void}>()
  3. // (e: 事件名, 键名:类型): void
  4. function buttonClick() {
  5. emit('getuser',1)
  6. }
  7. </script>
  8. <template>
  9. <button @click="buttonClick">传输</button>
  10. </template>
  11. <style scoped>
  12. </style>

6.依赖注入类型推断
  1. 父组件(祖先组件)
  1. <template>
  2. <div class="App">
  3. <button>我是App</button>
  4. <A></A>
  5. </div>
  6. </template>
  7. <script setup lang="ts">
  8. import { provide, ref } from 'vue'
  9. import A from './components/Acom.vue'
  10. let flag = ref<number>(1)
  11. provide('flag', flag)
  12. </script>
  1. 子组件(后代组件)
  1. <template>
  2. <div>
  3. 我是B
  4. <div>{{ flag }}</div>
  5. <button @click="flag++">+1</button>
  6. </div>
  7. </template>
  8. <script setup lang="ts">
  9. import { inject, ref , type Ref} from 'vue'
  10. // 注入值,默认值(让其可以进行类型推断)
  11. const flag<Ref<number>> = inject('flag', ref(1))
  12. </script>
7.定义全局函数和全局函数的类型支持
  1. import { createApp } from 'vue'
  2. ...
  3. const app = createApp(App)
  4. type Fileter = {
  5. format: <T>(str: T) => string
  6. }
  7. declare module '@vue/runtime-core' {
  8. export interface ComponentCustomProperties {
  9. $filters: Fileter
  10. $env: string
  11. }
  12. }
  13. // 全局函数
  14. app.config.globalProperties.$filters = {
  15. format<T>(str: T): string {
  16. return `真${str}`
  17. }
  18. }
  19. // 全局变量
  20. app.config.globalProperties.$env = '全局变量'
  21. ...

脚手架Vite

1.基本使用
  1. 创建vue3的项目yarn create vite || npm init vite@latest
  2. 安装插件Volar
2.配置项目路径
  1. tsconfig.json中添加
  1. // 让ts可以识别这个路径
  2. {
  3. "compilerOptions": {
  4. ...
  5. "baseUrl": "./",
  6. "paths": {
  7. "@/*":[
  8. "src/*"
  9. ]
  10. }
  11. },
  12. ...
  13. }
  1. vite.config.ts中添加
  1. // https://vitejs.dev/config/
  2. export default defineConfig({
  3. plugins: [vue()],
  4. resolve:{
  5. alias:{
  6. "@":join(__dirname,'src')
  7. }
  8. }
  9. })
3.eslint和prettierrc的配置
  1. .prettierrc.json
  1. {
  2. "arrowParens": "always",
  3. "bracketSameLine": true,
  4. "bracketSpacing": true,
  5. "embeddedLanguageFormatting": "auto",
  6. "htmlWhitespaceSensitivity": "css",
  7. "insertPragma": false,
  8. "jsxSingleQuote": false,
  9. "printWidth": 120,
  10. "proseWrap": "never",
  11. "quoteProps": "as-needed",
  12. "requirePragma": false,
  13. "semi": false,
  14. "singleQuote": true,
  15. "tabWidth": 2,
  16. "trailingComma": "all",
  17. "useTabs": false,
  18. "vueIndentScriptAndStyle": false,
  19. "singleAttributePerLine": false
  20. }

  1. .eslintrc.cjs
  1. /* eslint-env node */
  2. require('@rushstack/eslint-patch/modern-module-resolution')
  3. module.exports = {
  4. root: true,
  5. extends: [
  6. 'plugin:vue/vue3-essential',
  7. 'eslint:recommended',
  8. '@vue/eslint-config-typescript',
  9. '@vue/eslint-config-prettier'
  10. ],
  11. rules: {
  12. 'vue/multi-word-component-names': 'off', // 关闭命名
  13. semi: 0 // 结尾无分号
  14. },
  15. parserOptions: {
  16. ecmaVersion: 'latest'
  17. }
  18. }

4.vite环境变量的配置
  1. vite的环境在import中
  1. <script setup lang="ts">
  2. console.log(import.meta.env)
  3. </script>
  1. 创建.env.development .env.production
  2. package.json中配置运行生产环境,会自动注入
  1. {
  2. ...
  3. "scripts": {
  4. "dev": "vite --mode development",
  5. ...
  6. },
  7. }
  1. vite.config.ts中读取环境变量
  1. import { fileURLToPath, URL } from 'node:url'
  2. import { defineConfig, loadEnv } from 'vite'
  3. import unocss from 'unocss/vite'
  4. import vue from '@vitejs/plugin-vue'
  5. import { presetIcons, presetAttributify, presetUno } from 'unocss'
  6. // https://vitejs.dev/config/
  7. export default ({ mode }: any) => {
  8. // 读取环境变量
  9. console.log(loadEnv(mode, process.cwd()))
  10. return defineConfig({
  11. plugins: [vue()],
  12. resolve: {
  13. alias: {
  14. '@': fileURLToPath(new URL('./src', import.meta.url))
  15. }
  16. }
  17. })
  18. }

  1. 找不到模块“./App.vue”或其相应的类型声明
  1. declare module '*.vue' {
  2. import type { DefineComponent } from 'vue'
  3. // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
  4. const component: DefineComponent<{}, {}, any>
  5. export default component
  6. }
  1. 类型“ImportMeta”上不存在属性“env”
  1. // tsconfig.json
  2. {
  3. ...
  4. "compilerOptions": {
  5. ...
  6. "types": [ "vite/client" ],
  7. },
  8. ...
  9. }

指令的重构

1.v-model指令
(1)v-model实现组件间数据双向绑定
  1. 父组件
  1. <script setup lang="ts">
  2. import HelloWorld from "./components/HelloWorld.vue";
  3. import { ref } from "vue";
  4. const num=ref(1)
  5. </script>
  6. <template>
  7. <HelloWorld v-model="num"/>
  8. </template>
  1. 子组件
  1. <script setup lang="ts">
  2. import { computed } from 'vue';
  3. const props=defineProps<{modelValue:number}>()
  4. const emit = defineEmits<{(e: 'update:modelValue', id: number): void}>()
  5. // 计算属性实现修改数据的同步
  6. const value=computed({
  7. get(){
  8. return +props.modelValue
  9. },
  10. set(value){
  11. emit('update:modelValue',+value)
  12. }
  13. })
  14. </script>
  15. <template>
  16. <input type="text" v-model="value">
  17. </template>

  1. v-model的原理
  1. <template>
  2. <!-- <HelloWorld v-model="num"/> -->
  3. <HelloWorld :modelValue="num" @update:modelValue="num = $event"/>
  4. </template>
(2)v-model传递特定的名称
  1. 父组件
  1. <script setup lang="ts">
  2. import { ref } from "vue";
  3. import HelloWorld from "./components/HelloWorld.vue";
  4. const num=ref(1)
  5. </script>
  6. <template>
  7. <!-- <HelloWorld :num="num @update:="num = $event""/> -->
  8. <HelloWorld v-model:num="num"/>
  9. </template>
  1. 子组件
  1. <script setup lang="ts">
  2. import { computed } from 'vue';
  3. const props=defineProps<{num:number}>()
  4. const emit = defineEmits<{(e: 'update:num', id: number): void}>()
  5. const value=computed({
  6. get(){
  7. return +props.num
  8. },
  9. set(value){
  10. emit('update:num',+value)
  11. }
  12. })
  13. </script>
  14. <template>
  15. <input type="text" v-model="value">
  16. </template>

2.自定义指令
(1)自定义指令的简单使用
  1. 全局自定义指令
  1. // mian.ts
  2. import { createApp } from 'vue'
  3. import './style.css'
  4. import App from './App.vue'
  5. const app=createApp(App)
  6. app.directive('focus',{
  7. mounted(el){
  8. el.focus()
  9. }
  10. })
  11. app.mount('#app')
  1. 使用自定义指令
  1. <template>
  2. <input type="text" v-model="value" v-focus>
  3. </template>
  1. 局部自定义指令
  1. <script setup>
  2. // 在模板中启用 v-focus
  3. const vFocus = {
  4. mounted: (el) => el.focus()
  5. }
  6. </script>
  7. <template>
  8. <input v-focus />
  9. </template>
(2)自定义指令详解
  1. 自定义指令的生命周期
  1. <script setup lang="ts">
  2. import type { Directive, DirectiveBinding } from 'vue'
  3. type Dir = { background: string }
  4. const vMove: Directive = {
  5. created() {}, //元素初始化的时候
  6. beforeMount() {}, //指令绑定到元素后调用 只调用一次
  7. mounted(el: HTMLElement, dir: DirectiveBinding<Dir>) {
  8. console.log(dir.value.background)
  9. el.style.background = dir.value.background
  10. }, //元素插入父级dom调用
  11. beforeUpdate() {}, //元素被更新之前调用
  12. updated() {}, //这个周期方法被移除 改用updated
  13. beforeUnmount() {}, //在元素被移除前调用
  14. unmounted() {} //指令被移除后调用 只调用一次
  15. }
  16. </script>
  17. <template>
  18. <!-- 自定义指令,参数,修饰符 -->
  19. <div v-move:a.x="{ background: 'red' }">自定义指令</div>
  20. </template>
  21. <style scoped lang="less"></style>

  1. 生命周期的简写
  1. <script setup lang="ts">
  2. import type { Directive, DirectiveBinding } from 'vue'
  3. type Dir = { background: string }
  4. const vMove: Directive = (el: HTMLElement, dir: DirectiveBinding<Dir>) => {
  5. el.style.background = dir.value.background
  6. }
  7. </script>
  8. <template>
  9. <!-- 自定义指令,参数,修饰符 -->
  10. <div v-move:a.x="{ background: 'red' }">自定义指令</div>
  11. </template>
  12. <style scoped lang="less"></style>

  1. 自定义拖拽指令
  1. <script setup lang="ts">
  2. import type { Directive } from 'vue'
  3. const vMove: Directive = (el: HTMLElement) => {
  4. const move = (e: MouseEvent) => {
  5. console.log(e)
  6. el.style.left = e.clientX + 'px'
  7. el.style.top = e.clientY + 'px'
  8. }
  9. // 鼠标按下
  10. el.addEventListener('mousedown', () => {
  11. // 鼠标按下拖拽
  12. document.addEventListener('mousemove', move)
  13. // 鼠标松开
  14. document.addEventListener('mouseup', () => {
  15. // 清除事件
  16. document.removeEventListener('mousemove', move)
  17. })
  18. })
  19. }
  20. </script>
  21. <template>
  22. <!-- 自定义指令,参数,修饰符 -->
  23. <div
  24. v-move
  25. style="
  26. background-color: red;
  27. width: 200px;
  28. height: 200px;
  29. position: fixed;
  30. left: 50%;
  31. top: 50%;
  32. transform: translate(-50%, -50%);
  33. "
  34. >
  35. <div style="background-color: black; width: 200px; color: white">
  36. 自定义指令
  37. </div>
  38. </div>
  39. </template>

响应式原理

1.了解Proxy
  1. Proxy代理的get方法
  1. <script>
  2. let obj={
  3. name:'Vue',
  4. age:8
  5. }
  6. let obj2=new Proxy(obj,{
  7. /*
  8. *target表示obj这个对象
  9. *property表示读取的属性的key
  10. */
  11. get(target,property){
  12. console.log('执行了get');
  13. return target[property]
  14. }
  15. })
  16. console.log(obj2.age)
  17. </script>

  1. Proxy代理的set方法
  1. <script>
  2. let obj={
  3. name:'Vue',
  4. age:8
  5. }
  6. let obj2=new Proxy(obj,{
  7. /*
  8. *target表示obj这个对象
  9. *property表示读取的属性的key
  10. *newValue表示设置的值
  11. */
  12. set(target,property,newValue){
  13. console.log('执行了set')
  14. target[property]=newValue
  15. }
  16. })
  17. obj2.age=7
  18. console.log(obj2.age)
  19. </script>

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

  1. Object.defineProperty() 的set()方法
  1. <script>
  2. let person = {
  3. name: '张三',
  4. sex: '男',
  5. }
  6. function Observer(obj) {
  7. const keys = Object.keys(obj)
  8. keys.forEach((key) => {
  9. Object.defineProperty(this,key,{
  10. set(val) {
  11. console.log('set方法调用了')
  12. obj[key] = val
  13. }
  14. })
  15. })
  16. }
  17. const obs = new Observer(person)
  18. obs.name=15
  19. </script>

3.Vue双向绑定的实现的对比
  1. Vue3的Proxy实现
  1. <body>
  2. <input type="text" id="ipt">
  3. <p id='op'></p>
  4. <script>
  5. function reactive(obj) {
  6. return new Proxy(obj,{
  7. get(target,property) {
  8. return target[property]
  9. },
  10. set(target,property,newVal) {
  11. target[property] = newVal
  12. }
  13. })
  14. }
  15. let newObj = reactive([1,2])
  16. console.log(newObj[1])
  17. const ipt = document.querySelector('#ipt')
  18. ipt.value = newObj[1]
  19. document.querySelector('#op').innerHTML = newObj[1]
  20. ipt.addEventListener('input',function (e) {
  21. newObj[1] = e.target.value
  22. document.querySelector('#op').innerHTML = newObj[1]
  23. })
  24. </script>
  25. </body>

  1. Vue2的Object.defineProperty实现
  1. <body>
  2. <input type="text" id="ipt">
  3. <p id='op'></p>
  4. <script>
  5. function Observer(obj) {
  6. const keys = Object.keys(obj)
  7. keys.forEach((key) => {
  8. Object.defineProperty(this,key,{
  9. get() {
  10. console.log('get方法被调用了');
  11. return obj[key]
  12. },
  13. set(val) {
  14. console.log('set方法调用了')
  15. obj[key] = val
  16. }
  17. })
  18. })
  19. }
  20. const obs = new Observer([1,2,3])
  21. const ipt = document.querySelector('#ipt')
  22. ipt.value = obs[1]
  23. document.querySelector('#op').innerHTML = obs[1]
  24. ipt.addEventListener('input',function (e) {
  25. obs[1] = e.target.value
  26. document.querySelector('#op').innerHTML = obs[1]
  27. })
  28. </script>
  29. </body>

  1. 上面的测试,Object.property是可以检测到通过索引改变数组的操作的,而Vue没有实现,Object.defineProperty表示这个锅我不背

内置组件

1.内置组件
(1)Teleport组件
  1. 可以将一个组件内部的一部分模板“传送”到该组件的 DOM 结构外层的位置去
  2. 父组件
  1. <!-- 遮罩层组件传送到body下 -->
  2. <script setup lang="ts">
  3. import Acom from './components/Acom.vue'
  4. </script>
  5. <template>
  6. <div class="app"></div>
  7. <Acom/>
  8. </template>
  9. <style scoped >
  10. .app{
  11. width: 200px;
  12. height: 200px;
  13. background-color: pink;
  14. }
  15. </style>

  1. 子组件
  1. <script setup lang="ts">
  2. import { ref } from 'vue'
  3. const open = ref(false)
  4. </script>
  5. <template>
  6. <button @click="open=true">显示遮罩层</button>
  7. <!-- 传送到body -->
  8. <Teleport to="body">
  9. <div class="cover" v-show="open">
  10. <span @click="open=false"> X</span>
  11. </div>
  12. </Teleport>
  13. </template>
  14. <style scoped>
  15. .cover {
  16. position: absolute;
  17. z-index:2;
  18. top: 0;
  19. left: 0;
  20. bottom: 0;
  21. right: 0;
  22. background-color: rgba(0,0,0,0.5);
  23. }
  24. </style>

(2)Transition组件
  1. 非命名动画
  1. <script setup lang="ts">
  2. import { ref } from 'vue';
  3. const show=ref(true)
  4. </script>
  5. <template>
  6. <button @click="show=!show">显示/隐藏</button>
  7. <Transition>
  8. <div class="div" v-if="show"></div>
  9. </Transition>
  10. </template>
  11. <style scoped>
  12. .div{
  13. background-color: pink;
  14. width: 200px;
  15. height: 200px;
  16. margin: auto;
  17. }
  18. .v-enter-active,
  19. .v-leave-active {
  20. transition: opacity 0.5s ease;
  21. }
  22. .v-enter-from,
  23. .v-leave-to {
  24. opacity: 0;
  25. }
  26. </style>

  1. 命名动画
  1. <script setup lang="ts">
  2. import { ref } from 'vue';
  3. const show=ref(true)
  4. </script>
  5. <template>
  6. <button @click="show=!show">显示/隐藏</button>
  7. <Transition name="fade">
  8. <div class="div" v-if="show"></div>
  9. </Transition>
  10. </template>
  11. <style scoped>
  12. .div{
  13. background-color: pink;
  14. width: 200px;
  15. height: 200px;
  16. margin: auto;
  17. }
  18. .fade-enter-active {
  19. transition: all 0.3s ease-out;
  20. }
  21. .fade-leave-active {
  22. transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1);
  23. }
  24. .fade-enter-from,
  25. .fade-leave-to {
  26. transform: translateX(20px);
  27. opacity: 0;
  28. }
  29. </style>

  1. 过度动画
  1. <Transition mode="out-in">
  2. ...
  3. </Transition>
  1. 结合第三方库Animate.css
  1. <!-- yarn add animate.css -->
  2. <script setup lang="ts">
  3. import { ref } from 'vue'
  4. import 'animate.css'
  5. import Acom from './components/Acom.vue'
  6. const show = ref(true)
  7. </script>
  8. <template>
  9. <transition
  10. leave-active-class="animate__animated animate__fadeOut"
  11. enter-active-class="animate__animated animate__fadeIn"
  12. >
  13. <Acom v-if="show"></Acom>
  14. </transition>
  15. <button @click="show = !show">显示/隐藏</button>
  16. </template>
  17. <style scoped lang="less"></style>

  1. transition 生命周期
  1. <script setup lang="ts">
  2. import { ref } from 'vue'
  3. import 'animate.css'
  4. import Acom from './components/Acom.vue'
  5. const show = ref(true)
  6. const beforeEnter = () => {
  7. console.log('进入之前')
  8. }
  9. const enter = (_, done: Function) => {
  10. console.log('过度曲线')
  11. setTimeout(() => {
  12. done()
  13. }, 3000)
  14. }
  15. const afterEnter = () => {
  16. console.log('过度完成')
  17. }
  18. const enterCancelled = () => {
  19. console.log('进入效果被打断')
  20. }
  21. const beforeLeave = () => {
  22. console.log('离开之前')
  23. }
  24. const leave = (_, done: Function) => {
  25. setTimeout(() => {
  26. done()
  27. }, 3000)
  28. console.log('过度曲线')
  29. }
  30. const afterLeave = () => {
  31. console.log('离开之后')
  32. }
  33. const leaveCancelled = () => {
  34. console.log('离开效果被打断')
  35. }
  36. </script>
  37. <template>
  38. <transition
  39. leave-active-class="animate__animated animate__fadeOut"
  40. enter-active-class="animate__animated animate__fadeIn"
  41. @before-enter="beforeEnter"
  42. @enter="enter"
  43. @after-enter="afterEnter"
  44. @enter-cancelled="enterCancelled"
  45. @before-leave="beforeLeave"
  46. @leave="leave"
  47. @after-leave="afterLeave"
  48. @leave-cancelled="leaveCancelled"
  49. >
  50. <Acom v-if="show"></Acom>
  51. </transition>
  52. <button @click="show = !show">显示/隐藏</button>
  53. </template>

  1. 生命周期结合第三方库gsap.js
  1. <!-- yarn add gsap -->
  2. <script setup lang="ts">
  3. import { ref } from 'vue'
  4. import Acom from './components/Acom.vue'
  5. import gsap from 'gsap'
  6. const show = ref(true)
  7. // 进入之前
  8. const beforeEnter = (el: Element) => {
  9. gsap.set(el, {
  10. width: 0,
  11. height: 0
  12. })
  13. }
  14. // 进入过度动画
  15. const enter = (el: Element, done: gsap.Callback) => {
  16. gsap.to(el, {
  17. width: 200,
  18. height: 200,
  19. onComplete: done
  20. })
  21. }
  22. // 离开之前
  23. const beforeLeave = (el: Element) => {
  24. gsap.set(el, {
  25. width: 200,
  26. height: 200
  27. })
  28. }
  29. // 进入过度动画
  30. const leave = (el: Element, done: gsap.Callback) => {
  31. gsap.to(el, {
  32. width: 0,
  33. height: 0,
  34. onComplete: done
  35. })
  36. }
  37. </script>
  38. <template>
  39. <transition
  40. @before-enter="beforeEnter"
  41. @enter="enter"
  42. @before-leave="beforeLeave"
  43. @leave="leave"
  44. >
  45. <Acom v-if="show"></Acom>
  46. </transition>
  47. <button @click="show = !show">显示/隐藏</button>
  48. </template>

  1. 初始化动画
  1. <script setup lang="ts">
  2. import { ref } from 'vue'
  3. import Acom from './components/Acom.vue'
  4. const show = ref(true)
  5. </script>
  6. <template>
  7. <transition
  8. appear-from-class="from"
  9. appear-active-class="active"
  10. appear-to-class="to"
  11. appear
  12. >
  13. <Acom v-if="show"></Acom>
  14. </transition>
  15. <button @click="show = !show">显示/隐藏</button>
  16. </template>
  17. <style scoped>
  18. .from {
  19. /* 初始化之前 */
  20. width: 0;
  21. height: 0;
  22. }
  23. .active {
  24. /* 过度动画 */
  25. transition: all 2s ease;
  26. }
  27. .to {
  28. /* 初始化完成 */
  29. width: 200px;
  30. height: 200px;
  31. }
  32. </style>

  1. 初始化动画结合Animate.css
  1. <script setup lang="ts">
  2. import { ref } from 'vue'
  3. import Acom from './components/Acom.vue'
  4. import 'animate.css'
  5. const show = ref(true)
  6. </script>
  7. <template>
  8. <transition appear-active-class="animate__animated animate__heartBeat" appear>
  9. <Acom v-if="show"></Acom>
  10. </transition>
  11. <button @click="show = !show">显示/隐藏</button>
  12. </template>
  13. <style scoped></style>

(3)transition-group过度列表
  1. Transition组件无法对v-for的列表进行渲染
  2. transition-group的tag属性
  1. <!-- tag属性可以让transition-group多加一层节点元素 -->
  2. <template>
  3. <div class="wraps">
  4. <transition-group tag="session">
  5. <!-- 使用transition-group渲染的组件要有key-->
  6. <div class="item" v-for="item in 5" :key="item">{{ item }}</div>
  7. </transition-group>
  8. </div>
  9. </template>
  1. 添加列表时的动画效果
  1. <script setup lang="ts">
  2. import { ref } from 'vue'
  3. import 'animate.css'
  4. const num = ref(5)
  5. </script>
  6. <template>
  7. <div class="wraps">
  8. <transition-group
  9. leave-active-class="animate__animated animate__fadeOut"
  10. enter-active-class="animate__animated animate__fadeIn"
  11. >
  12. <!-- 使用transition-group渲染的组件要有key-->
  13. <div class="item" v-for="item in num" :key="item">{{ item }}</div>
  14. </transition-group>
  15. </div>
  16. <button @click="num++">添加</button>
  17. <button @click="num--">删除</button>
  18. </template>
  19. <style scoped lang="less">
  20. .wraps {
  21. display: flex;
  22. flex-wrap: wrap;
  23. word-break: break-all;
  24. border: 1px solid #ccc;
  25. .item {
  26. margin: 10px;
  27. }
  28. }
  29. </style>

  1. 平移动画move-class
  1. <script setup lang="ts">
  2. import { ref } from 'vue'
  3. import _ from 'lodash'
  4. // 建立9x9数组
  5. let list = ref(
  6. Array.apply(null, { length: 81 } as number[]).map((_, index) => {
  7. return {
  8. id: index,
  9. number: (index % 9) + 1
  10. }
  11. })
  12. )
  13. // 打乱数组
  14. const random = () => {
  15. list.value = _.shuffle(list.value)
  16. }
  17. console.log(list)
  18. </script>
  19. <template>
  20. <div>
  21. <button @click="random">打乱</button>
  22. <transition-group tag="div" class="wraps" move-class="move">
  23. <div v-for="item in list" :key="item.id" class="item">
  24. {{ item.number }}
  25. </div>
  26. </transition-group>
  27. </div>
  28. </template>
  29. <style scoped lang="less">
  30. .wraps {
  31. display: flex;
  32. flex-wrap: wrap; // 换行
  33. width: calc(25px * 10 + 9px);
  34. .item {
  35. width: 25px;
  36. height: 25px;
  37. border: 1px solid #ccc;
  38. text-align: center;
  39. }
  40. }
  41. .move {
  42. transition: all 1s;
  43. }
  44. </style>

  1. 状态过度(数字过度颜色过度)
  1. <script setup lang="ts">
  2. import { reactive, watch } from 'vue'
  3. import gsap from 'gsap'
  4. const num = reactive({
  5. current: 0,
  6. tweenedNumber: 0
  7. })
  8. watch(
  9. () => num.current,
  10. newVal => {
  11. gsap.to(num, {
  12. duration: 1, // 过度时间
  13. tweenedNumber: newVal
  14. })
  15. }
  16. )
  17. </script>
  18. <template>
  19. <div>
  20. <input type="text" v-model="num.current" step="20" />
  21. <div>
  22. <!-- 去掉小数点 -->
  23. {{ num.tweenedNumber.toFixed(0) }}
  24. </div>
  25. </div>
  26. </template>
  27. <style scoped lang="less"></style>

(4)keep-alive组件
  1. 开启keep-alive 生命周期的变化
  1. 初次进入时: onMounted-> onActivated
  2. 退出后触发: deactivated
  1. 缓存数据
  1. <script setup lang="ts">
  2. import { ref } from 'vue'
  3. import Acom from './components/Acom.vue'
  4. const show = ref(true)
  5. </script>
  6. <template>
  7. <keep-alive>
  8. <Acom v-if="show"></Acom>
  9. </keep-alive>
  10. <button @click="show = !show">显示/隐藏</button>
  11. </template>
  1. include属性和exclude属性
  1. <!-- 注意组件一定要命名才可以使用include -->
  2. <script setup lang="ts">
  3. import { ref } from 'vue'
  4. import Acom from './components/Acom.vue'
  5. import Bcom from './components/Bcom.vue'
  6. const show = ref(true)
  7. </script>
  8. <template>
  9. <keep-alive :include="['Acom']" :exclude="['Bcom']">
  10. <Acom v-if="show"></Acom>
  11. <Bcom v-else></Bcom>
  12. </keep-alive>
  13. <button @click="show = !show">显示/隐藏</button>
  14. </template>
  15. <style scoped lang="less"></style>

2.普通组件
(1)全局组件
  1. 配置全局组件
  1. import { createApp } from 'vue'
  2. import { createPinia } from 'pinia'
  3. import App from './App.vue'
  4. import Acom from './components/Acom.vue'
  5. import './assets/main.css'
  6. const app = createApp(App)
  7. app.use(createPinia())
  8. app.component('Acom', Acom)
  9. app.mount('#app')
  1. 使用组件
  1. <template>
  2. <div>
  3. <Acom></Acom>
  4. </div>
  5. </template>
(2)异步组件
  1. 子组件中发送了请求变成异步
  1. <script setup lang="ts">
  2. interface ResItf {
  3. code: number
  4. data: { a: number; b: number }[]
  5. message: string
  6. }
  7. let p: Promise<ResItf> = new Promise(resolve => {
  8. setTimeout(() => {}, 3000)
  9. resolve({
  10. code: 0,
  11. data: [
  12. { a: 1, b: 2 },
  13. { a: 11, b: 22 }
  14. ],
  15. message: ''
  16. })
  17. })
  18. const a = await p
  19. console.log(a)
  20. </script>
  21. <template>
  22. <div>异步组件</div>
  23. <div>异步组件</div>
  24. <div>异步组件</div>
  25. </template>

  1. 父组件异步调用组件
  1. <script setup lang="ts">
  2. // 异步组件不能这样引入
  3. // import Acom from './components/Acom.vue'
  4. import { defineAsyncComponent } from 'vue'
  5. const Acom = defineAsyncComponent(() => import('./components/Acom.vue'))
  6. </script>
  7. <template>
  8. <div>
  9. <Suspense>
  10. <template #default>
  11. <Acom></Acom>
  12. </template>
  13. <template #fallback> 加载中。。。 </template>
  14. </Suspense>
  15. </div>
  16. </template>
  17. <style scoped lang="less"></style>

语法糖组件命名问题

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

常用的CSS的功能

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

1.CSS原子化
  1. 安装unocssyarn add unocss
  2. vite的配置文件中配置
  1. import { fileURLToPath, URL } from 'node:url'
  2. import pxtoViewPort from 'postcss-px-to-viewport'
  3. import { defineConfig } from 'vite'
  4. import unocss from 'unocss/vite'
  5. import vue from '@vitejs/plugin-vue'
  6. // https://vitejs.dev/config/
  7. export default defineConfig({
  8. plugins: [
  9. vue(),
  10. // 配置的原子化
  11. unocss({
  12. rules: [
  13. ['flex', { display: 'flex' }],
  14. ['red', { color: 'red' }],
  15. [/^m-(\d+)$/, ([, d]) => ({ margin: `${Number(d) * 10}px` })]
  16. ]
  17. })
  18. ],
  19. resolve: {
  20. alias: {
  21. '@': fileURLToPath(new URL('./src', import.meta.url))
  22. }
  23. }
  24. })

  1. main.ts中引入import 'uno.css'
  2. 其他预设配置中引入
  1. import { fileURLToPath, URL } from 'node:url'
  2. import { defineConfig } from 'vite'
  3. import unocss from 'unocss/vite'
  4. import vue from '@vitejs/plugin-vue'
  5. import { presetIcons, presetAttributify, presetUno } from 'unocss'
  6. // https://vitejs.dev/config/
  7. export default defineConfig({
  8. plugins: [
  9. vue(),
  10. unocss({
  11. // 预设
  12. presets: [presetIcons(), presetAttributify(), presetUno()],
  13. rules: [
  14. ['flex', { display: 'flex' }],
  15. ['red', { color: 'red' }],
  16. [/^m-(\d+)$/, ([, d]) => ({ margin: `${Number(d) * 10}px` })]
  17. ]
  18. })
  19. ],
  20. resolve: {
  21. alias: {
  22. '@': fileURLToPath(new URL('./src', import.meta.url))
  23. }
  24. }
  25. })

  1. 第一预设图标库
  1. npm i -D @iconify-json/ic
  2. // 后缀ic是选择的图标库
  1. 第二预设属性语义化 无须class
 <div color="red">left</div>
  1. 第三预设
  1. 默认的 @unocss/preset-uno 预设(实验阶段)是一系列流行的原子化框架的 通用超集,
  2. 包括了 Tailwind CSS,Windi CSS,Bootstrap,Tachyons 等。
  3. 例如,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配置文件中添加
  1. /** @type {import('tailwindcss').Config} */
  2. module.exports = {
  3. content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
  4. theme: {
  5. extend: {}
  6. },
  7. plugins: []
  8. }
  1. 创建index.css文件并且在mian.ts中引入
  1. @tailwind base;
  2. @tailwind components;
  3. @tailwind utilities;
  1. 使用tailwindcss的样式
  1. <script setup lang="ts"></script>
  2. <template>
  3. <div
  4. class="w-screen h-screen bg-red-600 flex justify-center items-center text-8xl text-teal-50"
  5. >
  6. hello tailwind
  7. </div>
  8. </template>
  9. <style scoped lang="less"></style>

面试常用源码

1.app.use()的源码实现
  1. 实现myuse
  1. import type { App } from 'vue'
  2. import { app } from '../main'
  3. interface Use {
  4. install: (app: App, ...options: any[]) => void
  5. }
  6. // 插件注册的数组
  7. const installList = new Set()
  8. export function MyUse<T extends Use>(plugin: T, ...options: any[]) {
  9. if (installList.has(plugin)) {
  10. console.log('插件件已经注册')
  11. return
  12. }
  13. plugin.install(app, ...options)
  14. installList.add(plugin)
  15. }

  1. 使用myuse调用插件
  1. import { createApp } from 'vue'
  2. import { createPinia } from 'pinia'
  3. import App from './App.vue'
  4. import './assets/main.css'
  5. import Loading from './components/Loading'
  6. import { MyUse } from './utils/myuse'
  7. export const app = createApp(App)
  8. // 使用插件
  9. // app.use(Loading)
  10. MyUse(Loading)
  11. app.use(createPinia())
  12. app.mount('#app')
  13. type Lod = {
  14. show: () => void
  15. hide: () => void
  16. }
  17. //编写ts loading 声明文件放置报错 和 智能提示
  18. declare module '@vue/runtime-core' {
  19. export interface ComponentCustomProperties {
  20. $loading: Lod
  21. }
  22. }

移动端适配

1.第一种适配方案
  1. 安装依赖yarn add amfe-flexible postcss postcss-pxtorem@5.1.1
  2. main.ts引入amfe-flexibleimport "amfe-flexible"
  3. 根目录下创建postcss.config.js文件并配置
  1. module.exports = {
  2. plugins: {
  3. 'postcss-pxtorem': {
  4. // 能够把所有元素的px单位转成Rem
  5. // rootValue: 转换px的基准值。
  6. // 编码时, 一个元素宽是75px,则换成rem之后就是2rem
  7. rootValue: 37.5,
  8. propList: ['*']
  9. }
  10. }
  11. }
2.第二种适配方案
  1. 安装依赖yarn add postcss-px-to-viewport -D
  2. vite.config.ts内置postcss.config.js中修改配置
  1. import { fileURLToPath, URL } from 'node:url'
  2. import pxtoViewPort from 'postcss-px-to-viewport'
  3. import { defineConfig } from 'vite'
  4. import vue from '@vitejs/plugin-vue'
  5. // https://vitejs.dev/config/
  6. export default defineConfig({
  7. plugins: [vue()],
  8. css: {
  9. postcss: {
  10. plugins: [
  11. // postcss-px-to-viewport的配置
  12. pxtoViewPort({
  13. unitToConvert: 'px', // 要转化的单位
  14. viewportWidth: 750, // UI设计稿的宽度
  15. unitPrecision: 6, // 转换后的精度,即小数点位数
  16. propList: ['*'], // 指定转换的css属性的单位,*代表全部css属性的单位都进行转换
  17. viewportUnit: 'vw', // 指定需要转换成的视窗单位,默认vw
  18. fontViewportUnit: 'vw', // 指定字体需要转换成的视窗单位,默认vw
  19. selectorBlackList: ['ignore-'], // 指定不转换为视窗单位的类名,
  20. minPixelValue: 1, // 默认值1,小于或等于1px则不进行转换
  21. mediaQuery: true, // 是否在媒体查询的css代码中也进行转换,默认false
  22. replace: true, // 是否转换后直接更换属性值
  23. landscape: false // 是否处理横屏情况
  24. })
  25. ]
  26. }
  27. },
  28. resolve: {
  29. alias: {
  30. '@': fileURLToPath(new URL('./src', import.meta.url))
  31. }
  32. }
  33. })

  1. 创建postcss-px-to-viewport.d.ts的声明文件
  1. declare module 'postcss-px-to-viewport' {
  2. type Options = {
  3. unitToConvert: 'px' | 'rem' | 'cm' | 'em'
  4. viewportWidth: number
  5. viewportHeight: number // not now used; TODO: need for different units and math for different properties
  6. unitPrecision: number
  7. viewportUnit: string
  8. fontViewportUnit: string // vmin is more suitable.
  9. selectorBlackList: string[]
  10. propList: string[]
  11. minPixelValue: number
  12. mediaQuery: boolean
  13. replace: boolean
  14. landscape: boolean
  15. landscapeUnit: string
  16. landscapeWidth: number
  17. }
  18. export default function (options: Partial<Options>): any
  19. }

  1. 在tsconfig.json中引入声明文件
  1. {
  2. "extends": "@vue/tsconfig/tsconfig.web.json",
  3. "include": ["env.d.ts", "src/**/*", "src/**/*.vue", "postcss-px-to-viewport.d.ts"],
  4. "compilerOptions": {
  5. "baseUrl": ".",
  6. "types": ["element-plus/global"],
  7. "paths": {
  8. "@/*": ["./src/*"]
  9. }
  10. },
  11. "references": [
  12. {
  13. "path": "./tsconfig.config.json"
  14. }
  15. ]
  16. }

  1. 注意:如果外面用到了postcss.config.js,在postcss.config.js中添加配置文件
  1. // 要禁用vite.config.ts内置postcss.config.js
  2. module.exports = {
  3. plugins: {
  4. tailwindcss: {},
  5. autoprefixer: {},
  6. 'postcss-px-to-viewport': {
  7. unitToConvert: 'px', // 要转化的单位
  8. viewportWidth: 320 // UI设计稿的宽度
  9. // unitPrecision: 6, // 转换后的精度,即小数点位数
  10. // propList: ['*'], // 指定转换的css属性的单位,*代表全部css属性的单位都进行转换
  11. // viewportUnit: 'vw', // 指定需要转换成的视窗单位,默认vw
  12. // fontViewportUnit: 'vw', // 指定字体需要转换成的视窗单位,默认vw
  13. // selectorBlackList: ['wrap'], // 指定不转换为视窗单位的类名,
  14. // minPixelValue: 1, // 默认值1,小于或等于1px则不进行转换
  15. // mediaQuery: true, // 是否在媒体查询的css代码中也进行转换,默认false
  16. // replace: true, // 是否转换后直接更换属性值
  17. // exclude: [/node_modules/], // 设置忽略文件,用正则做目录名匹配
  18. // landscape: false // 是否处理横屏情况
  19. }
  20. }
  21. }

的其他知识点

1.全局函数和全局变量
  1. 全局函数
  1. import { createApp } from 'vue'
  2. import { createPinia } from 'pinia'
  3. import App from './App.vue'
  4. import './assets/main.css'
  5. const app = createApp(App)
  6. type Fileter = {
  7. format: <T>(str: T) => string
  8. }
  9. declare module '@vue/runtime-core' {
  10. export interface ComponentCustomProperties {
  11. $filters: Fileter
  12. }
  13. }
  14. // 全局函数
  15. app.config.globalProperties.$filters = {
  16. format<T>(str: T): string {
  17. return `真${str}`
  18. }
  19. }
  20. app.use(createPinia())
  21. app.mount('#app')

  1. 全局变量
  1. import { createApp } from 'vue'
  2. import { createPinia } from 'pinia'
  3. import App from './App.vue'
  4. import './assets/main.css'
  5. const app = createApp(App)
  6. declare module '@vue/runtime-core' {
  7. export interface ComponentCustomProperties {
  8. $env: string
  9. }
  10. }
  11. // 全局变量
  12. app.config.globalProperties.$env = '全局变量'
  13. app.use(createPinia())
  14. app.mount('#app')

2.自定义插件
  1. 封装插件的样式,抛出插件的显示隐藏方法
  1. <script setup lang="ts">
  2. import { ref } from 'vue'
  3. const isShow = ref(false)
  4. // 控制load显示
  5. const show = () => {
  6. console.log(111)
  7. isShow.value = true
  8. }
  9. const hide = () => {
  10. isShow.value = false
  11. }
  12. // 这里抛出的东西会在插件声明文件中调用
  13. defineExpose({
  14. show,
  15. hide
  16. })
  17. </script>
  18. <template>
  19. <div v-if="isShow" class="loading">loading....</div>
  20. </template>
  21. <style scoped lang="less"></style>

  1. 创建接收调用插件的方法
  1. import { render, type App, type VNode } from 'vue'
  2. import Loading from './index.vue'
  3. import { createVNode } from 'vue'
  4. export default {
  5. install(app: App) {
  6. // 变成div
  7. const Vnode: VNode = createVNode(Loading)
  8. // 挂载
  9. render(Vnode, document.body)
  10. // console.log(app, Vnode)
  11. // // 读取loading组件中导出的方法
  12. // console.log(Vnode.component?.exposed.show)
  13. // 对插件的方法进行全局挂载
  14. app.config.globalProperties.$loading = {
  15. show: Vnode.component?.exposed?.show,
  16. hide: Vnode.component?.exposed?.hide
  17. }
  18. }
  19. }

  1. main.ts中挂载上面的方方法
  1. import { createApp } from 'vue'
  2. import { createPinia } from 'pinia'
  3. import App from './App.vue'
  4. import './assets/main.css'
  5. import Loading from './components/Loading'
  6. const app = createApp(App)
  7. // 使用插件
  8. app.use(Loading)
  9. app.use(createPinia())
  10. app.mount('#app')
  1. 对插件的方法进行声明
  1. type Lod = {
  2. show: () => void
  3. hide: () => void
  4. }
  5. //编写ts loading 声明文件放置报错 和 智能提示
  6. declare module '@vue/runtime-core' {
  7. export interface ComponentCustomProperties {
  8. $loading: Lod
  9. }
  10. }
  1. 使用插件
  1. <script setup lang="ts">
  2. import { getCurrentInstance } from 'vue'
  3. const instance = getCurrentInstance()
  4. // 调用插件
  5. instance?.proxy?.$loading.show()
  6. // 5秒关闭插件
  7. setTimeout(() => {
  8. instance?.proxy?.$loading.hide()
  9. }, 5000)
  10. </script>
  11. <template>
  12. <div></div>
  13. </template>
  14. <style scoped lang="less"></style>

3.函数式编程
  1. h函数
  1. h 接收三个参数
  2. 1.type 元素的类型
  3. 2.propsOrChildren 数据对象, 这里主要表示(props, attrs, dom props, class 和 style)
  4. 3.children 子节点
  1. h函数的多种组合
  1. // 除类型之外的所有参数都是可选的
  2. h('div')
  3. h('div', { id: 'foo' })
  4. //属性和属性都可以在道具中使用
  5. //Vue会自动选择正确的分配方式
  6. h('div', { class: 'bar', innerHTML: 'hello' })
  7. // props modifiers such as .prop and .attr can be added
  8. // with '.' and `^' prefixes respectively
  9. h('div', { '.name': 'some-name', '^width': '100' })
  10. // class 和 style 可以是对象或者数组
  11. h('div', { class: [foo, { bar }], style: { color: 'red' } })
  12. // 定义事件需要加on 如 onXxx
  13. h('div', { onClick: () => {} })
  14. // 子集可以字符串
  15. h('div', { id: 'foo' }, 'hello')
  16. //如果没有props是可以省略props 的
  17. h('div', 'hello')
  18. h('div', [h('span', 'hello')])
  19. // 子数组可以包含混合的VNode和字符串
  20. h('div', ['hello', h('span', 'hello')])

  1. 使用props传递参数
  1. <template>
  2. <Btn text="按钮"></Btn>
  3. </template>
  4. <script setup lang='ts'>
  5. import { h, } from 'vue';
  6. type Props = {
  7. text: string
  8. }
  9. const Btn = (props: Props, ctx: any) => {
  10. return h('div', {
  11. class: 'p-2.5 text-white bg-green-500 rounded shadow-lg w-20 text-center inline m-1',
  12. }, props.text)
  13. }
  14. </script>

  1. 接收emit
  1. <template>
  2. <Btn @on-click="getNum" text="按钮"></Btn>
  3. </template>
  4. <script setup lang='ts'>
  5. import { h, } from 'vue';
  6. type Props = {
  7. text: string
  8. }
  9. const Btn = (props: Props, ctx: any) => {
  10. return h('div', {
  11. class: 'p-2.5 text-white bg-green-500 rounded shadow-lg w-20 text-center inline m-1',
  12. onClick: () => {
  13. ctx.emit('on-click', 123)
  14. }
  15. }, props.text)
  16. }
  17. const getNum = (num: number) => {
  18. console.log(num);
  19. }
  20. </script>

  1. 定义插槽
  1. <template>
  2. <Btn @on-click="getNum">
  3. <template #default>
  4. 按钮slots
  5. </template>
  6. </Btn>
  7. </template>
  8. <script setup lang='ts'>
  9. import { h, } from 'vue';
  10. type Props = {
  11. text?: string
  12. }
  13. const Btn = (props: Props, ctx: any) => {
  14. return h('div', {
  15. class: 'p-2.5 text-white bg-green-500 rounded shadow-lg w-20 text-center inline m-1',
  16. onClick: () => {
  17. ctx.emit('on-click', 123)
  18. }
  19. }, ctx.slots.default())
  20. }
  21. const getNum = (num: number) => {
  22. console.log(num);
  23. }
  24. </script>

4.vue性能优化
(1)跑分和打包体积
  1. 跑分vue开发工具Lighthouse
  1. 从Performance页的表现结果来看,得分37分,并提供了很多的时间信息,我们来解释下这些选项代表的意思:
  2. FCP (First Contentful Paint):首次内容绘制的时间,浏览器第一次绘制DOM相关的内容,也是用户第一次看到页面内容的时间。
  3. Speed Index: 页面各个可见部分的显示平均时间,当我们的页面上存在轮播图或者需要从后端获取内容加载时,这个数据会被影响到。
  4. LCP (Largest Contentful Paint):最大内容绘制时间,页面最大的元素绘制完成的时间。
  5. TTI(Time to Interactive):从页面开始渲染到用户可以与页面进行交互的时间,内容必须渲染完毕,交互元素绑定的事件已经注册完成。
  6. TBT(Total Blocking Time):记录了首次内容绘制到用户可交互之间的时间,这段时间内,主进程被阻塞,会阻碍用户的交互,页面点击无反应。
  7. CLS(Cumulative Layout Shift):计算布局偏移值得分,会比较两次渲染帧的内容偏移情况,可能导致用户想点击A按钮,但下一帧中,A按钮被挤到旁边,导致用户实际点击了B按钮。
  1. 打包后rollup的插件yarn add rollup-plugin-visualizer
  1. import { fileURLToPath, URL } from 'node:url'
  2. import { defineConfig, loadEnv } from 'vite'
  3. import unocss from 'unocss/vite'
  4. import vue from '@vitejs/plugin-vue'
  5. import { visualizer } from 'rollup-plugin-visualizer'
  6. // https://vitejs.dev/config/
  7. export default ({ mode }: any) => {
  8. console.log(loadEnv(mode, process.cwd()))
  9. return defineConfig({
  10. plugins: [vue(),
  11. // 配置rollup的插件
  12. visualizer({ open: true })],
  13. resolve: {
  14. alias: {
  15. '@': fileURLToPath(new URL('./src', import.meta.url))
  16. }
  17. }
  18. })
  19. }

  1. vite配置文件中vite的优化
  1. import { fileURLToPath, URL } from "node:url";
  2. import { defineConfig } from "vite";
  3. import vue from "@vitejs/plugin-vue";
  4. import vueJsx from "@vitejs/plugin-vue-jsx";
  5. // https://vitejs.dev/config/
  6. export default defineConfig({
  7. ...
  8. build: {
  9. chunkSizeWarningLimit: 2000,
  10. cssCodeSplit: true, //css 拆分
  11. sourcemap: false, //不生成sourcemap
  12. minify: 'terser', //是否禁用最小化混淆,esbuild打包速度最快,terser打包体积最小。
  13. assetsInlineLimit: 5000 //小于该值 图片将打包成Base64
  14. }
  15. })

(2)PWA离线存储技术
  1. 安装依赖yarn add vite-plugin-pwa -D
  2. 配置
  1. import { fileURLToPath, URL } from "node:url";
  2. import { VitePWA } from "vite-plugin-pwa";
  3. import { defineConfig } from "vite";
  4. import vue from "@vitejs/plugin-vue";
  5. import vueJsx from "@vitejs/plugin-vue-jsx";
  6. // https://vitejs.dev/config/
  7. export default defineConfig({
  8. plugins: [
  9. vue(),
  10. vueJsx(),
  11. VitePWA({
  12. workbox: {
  13. cacheId: "key", //缓存名称
  14. runtimeCaching: [
  15. {
  16. urlPattern: /.*\.js.*/, //缓存文件
  17. handler: "StaleWhileRevalidate", //重新验证时失效
  18. options: {
  19. cacheName: "XiaoMan-js", //缓存js,名称
  20. expiration: {
  21. maxEntries: 30, //缓存文件数量 LRU算法
  22. maxAgeSeconds: 30 * 24 * 60 * 60, //缓存有效期
  23. },
  24. },
  25. },
  26. ],
  27. },
  28. }),
  29. ],
  30. ....
  31. });

(3)其他性能优化
  1. 图片懒加载
  1. import { createApp } from 'vue'
  2. import App from './app'
  3. import lazyPlugin from 'vue3-lazy'
  4. const app = createApp(App)
  5. app.use(lazyPlugin, {
  6. loading: 'loading.png',
  7. error: 'error.png'
  8. })
  9. app.mount('#app')
  10. <img v-lazy="user.avatar" >
  1. 虚拟列表实现
  1. 后台返回多数据
  2. 展示可视区的dom
  1. 多线程 使用 new Worker 创建
  1. // worker脚本与主进程的脚本必须遵守同源限制。他们所在的路径协议、域名、端口号三者需要相同
  2. const myWorker1 = new Worker("./calcBox.js");
  3. // 都使用postMessage发送消息
  4. worker.postMessage(arrayBuffer, [arrayBuffer]);
  5. // 都使用onmessage接收消息
  6. self.onmessage = function (e) {
  7. // xxx这里是worker脚本的内容
  8. };
  9. 关闭
  10. worker.terminate();
  1. 防抖节流
本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号