一、Vue3
1、vite 构建工具
-
优势如下:
- 开发环境中,无需打包操作,可快速的冷启动。
- 更加轻量快速的热重载(HMR)
- 真正的按需编译,不再等待整个应用编译完成。
- **没有打包操作,需要什么动态加载什么。
-
项目构建过程
npm init vite-app <project-name> cd <project-name> npm i npm run dev
2、响应式原理
1)vue2
-
实现原理
- 对象类型:通过 Object.defineProperty() 对属性的读取、修改进行拦截(数据劫持)。
- 数组类型:对数组的变更方法进行了包裹。
-
模拟实现
// 无法实现新增属性、删除属性 let person = { name:'张三', age:18 } let p = {} Object.defineProperty( p,'name',{ get(){ return person.name }, set(value){ person.name = value } }) Object.defineProperty( p,'age',{ get(){ return person.age }, set(value){ person.age = value } })
-
存在问题
-
新增属性、删除属性,界面不会更新;
-
补充:新增属性
this.$set(this.person,'sex','女')
-
补充:删除属性
this.$delete(this.person,'sex')
-
-
直接通过下标修改数组,界面不会自动更新。
-
补充:修改属性
hobby = ['吃饭','学习','学习'] this.$set(this.hobby,0,'学习') console.log(this.hobby) // 学习,学习,学习
-
-
2)vue3
-
实现原理
- 通过 Proxy 代理:拦截对象中任意属性的变化,包括:属性值的读写、添加、删除等。
- 通过 Reflect 反射:对被代理对象的属性进行操作
-
模拟实现
let person = { name:'张三', age:18 } // 建立映射关系 const p = new Proxy( person ,{ //读取 get(target,propName){ return Reflect.get(target,propName) }, //修改 新增 set(target,propName,value){ Reflect.get(target,propName,value) }, //删除 deleteProperty(target,propName){ return Reflect.deleteProperty(target,propName) } })
3、生命周期
- beforeMount 读不到 Dom 元素,返回值是 undefined
- beforeUpdate 获取的是更新之前的 Dom 元素,updated 获取的是 更新之后的 Dom 元素。
1)Vue2
- 此阶段初始化 生命周期。
- beforeCreate
- 此时无法访问 data 中的数据和 method 中的方法。
- 此阶段进行数据监测与数据代理。
- created
- 可以访问到 data 中的数据和 method 中的方法。
- 此阶段解析模板,生成虚拟DOM,但是页面还不能显示解析好的内容。
- beforeMount
- 页面呈现的是未经 Vue 编译的DOM结构。
- 所有对DOM的操作,最终都不奏效。
- 此阶段将虚拟DOM转为真实DOM插入页面。
- mounted
- 页面呈现的是经过 Vue 编译的DOM结构。
- 所有对DOM的操作,均奏效。
- 一般在此进行初始化操作:开启定时器、发送网络请求、订阅消息、绑定自定义事件等。
- 此阶段数据发生改变。
- beforeUpdate
- 数据是新的,页面是旧的。
- 此阶段根据新数据生成新的虚拟DOM,随后与旧的虚拟DOM比较,最终完成页面更新。
- updated
- 数据是新的,页面也是新的。
- beforeDestroy
- 此时的 data、method 都处于可用状态,可以修改但页面不再更新。
- 一般在此进行收尾工作:关闭定时器、取消订阅消息、解绑自定义事件等。
- destroy
- 销毁后自定义事件会失效,但是原生DOM事件依旧有效。
2)Vue3
- Vue3 使用 unmount 代替 destroy。
- Vue3 可以在 setup 中使用组合式生命周期钩子,命名在钩子前面添加 “ on ”。
- 组合式生命周期钩子的执行时机优先于配置项的钩子。
4、setup 函数-旧式写法
1)概述
-
Vue3的一个新的配置项,与 data、method 等同级,值是一个函数。
-
setup 执行时机:在 beforeCreate 之前执行一次,this 是 undefined。
-
尽量不要与Vue2的配置混用
- Vue2的配置,如 data、method、computed 可以访问 setup 的属性与方法;
- 但是 setup 不能访问到 Vue2 的配置。
- 如果重名,以 setup 优先。
2)参数
-
setup 的参数
-
props:值为对象,包含:组件外部传递过来,且组件内部声明接收的属性。
// 父组件 <template> <Demo msg='hello' school='daan'> </template> // 子组件 name:'Demo', props:['msg','school'], setup(props){ }
-
context:上下文对象
-
attrs:值为对象,包含:组件外部传递过来,但没有在 Props 配置中声明的属性,相当于 this.$attrs
-
slots:收到的插槽内容,相当于 this.$slots
-
emit:分发自定义事件的函数,相当于 this.$emit
// 父组件 <template> <Demo @hello='sayHello' msg='hello' school='daan'> </template> setup(){ function sayHello(value){ alert("你好啊",value) } } // 子组件 name:'Demo', props:['msg','school'], eimts:['hello'] setup(props,context){ function test(){ context.emit('hello',666) } }
-
-
5、Ref 全家桶
1)ref 函数
- 支持所有类型;取值赋值都需要添加 .value。
(1)定义响应式对象
-
setup 本身不能实现响应式,需要借助 ref 函数,将变量封装成一个引用对象,并使用
引用对象.value
的方式修改变量的值。 -
该函数的参数可以是基本类型,也可以是对象类型。
-
基本类型:响应式依旧借助 object.defineProperty( ) 的 get 与 set 完成。
<h1> {{ name }} </h1> <script setup lang=ts> // 需要导入 ref import {ref} from 'vue' let name = ref("zhangsan") const changeInfo() =>{ name.value = 'lisi' }
-
对象类型:内部 “求助” 了 Vue3 中的一个新函数,reactive 函数。
let job = ref({ type:'前端工程师', salary:30 }) function changeInfo(){ job.value.type = "UI设计", job.value.salary = 60 }
-
(2)获取DOM元素
-
ref 可以用于获取节点
<div ref='h1'> </div> const change = () =>{ let h1 = ref<HTMLDivElement>(); log(h1.value?.innerText); }
2)isRef 函数
- 判断参数是否为 ref实例对象
3)shallowRef 函数
-
只处理基本数据类型的响应式,不进行对象的响应式处理。
-
如果需要处理对象,则需要用新的对象来替换。
<h1> {{ name }} </h1> <script setup lang=ts> // 需要导入 ref import { shallowRef } from 'vue' let obj = shallowRef({ name:'zhanshan' }) const changeInfo() =>{ obj.value.name = 'lisi' // 无法实现响应式 } const changeInfo2() =>{ obj.value = { name:'lisi' // 可以实现响应式 } }
-
shallowRef 函数不能与 ref 函数同时使用,否则 shallowRef 等效于 ref
let obj1 = ref({ name:'zhanshan' }) let obj2 = shallowRef({ name:'lisi' }) // 此时,obj2 也会变成 深层次的响应式对象 const changeInfo = () =>{ obj1.value.name = 'obj1'; obj2.value.name = 'obj2'; }
-
应用场景:如果有一个对象数据,后续功能不会修改该对象中的属性,而是新的对象来替换。
4)triggerRef 函数
- ref = shallowRef + triggerRef
5)customRef 函数
-
作用:创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。
<template> <input type="text" v-model="keyword"> <h3>{{keyword}}</h3> </template> <script setup lang='ts'> import {customRef} from 'vue' //自定义一个myRef function myRef(value,delay){ let timer //通过customRef去实现自定义 return customRef((track,trigger)=>{ return{ get(){ track() //告诉Vue这个value值是需要被“追踪”的 return value }, set(newValue){ clearTimeout(timer) timer = setTimeout(()=>{ value = newValue trigger() //告诉Vue去更新界面 },delay) } } }) } let keyword = myRef('hello',500) //使用程序员自定义的ref </script>
6、reactive 全家桶
1)reactive 函数
-
reactive 函数的参数只能是引用类型,因为在源码中参数使用了泛型约束。
reactive<T extends object>(target:T)
-
reactive 函数的参数可以是多层次的对象。
import { reactive } from 'vue' let job = reactive({ type:'前端工程师', salary:30, a:{ b:{ c:666 } } }) function changeInfo(){ job.type = "UI设计", job.salary = 60, job.a.b.c = 999 }
-
reactive 函数的参数也可以是数组,可以通过数组下标修改数据。
import { reactive } from 'vue' let hobby = reactive(['抽烟','喝酒','烫头']) function changeInfo(){ hobby[0] = '学习' }
-
reactive 实例对象不能直接赋值,否则破坏响应式对象。
let list = reactive([]) let res = ['A','B'] list = res; // 破坏响应式对象。 list.push(...res); // 有效赋值。
2)shallowReactive 函数
- 只处理对象最外层属性的响应式(浅响应式)
- 应用场景:如果有一个对象数据,结构比较深,但变化时只是外层属性变化。
- shallowReactive 函数不能与 reactive 函数同时使用,否则 shallowReactive 等效于 reactive。
- 原因:收集本次宏任务中的所有更改,最后主线程空闲,只会重新渲染一次。
3)readonly 函数
-
让一个响应式数据变为只读的(深只读)
let person = reactive({ name:'zhan', age:18, job:{ j1:'ptn' } }) person = readonly( person )
4)shallowReadonly 函数
-
让一个响应式数据变为只读的(浅只读)
let person = reactive({ name:'zhan', age:18, job:{ j1:'ptn' } }) person = shallowReadonly( person )
7、to 全家桶
1)toRef 函数
-
创建一个 ref 对象,将响应式对象中的某个属性单独提供给外部使用。
const man = reactive({ name:'zhan',age:23 }) cont name = toRef(man,'name') const change = () =>{ name.value = 'lisi' }
-
非响应式对象用了 toRef 函数,视图毫无变化。
2)toRefs 函数
-
将响应式对象的每个属性都单独提供给外部使用,实质是内部为每个属性调用了 toRef 函数。
const man = reactive({ name:'zhan',age:23 }) cont {name,age} = toRefs(man) const change = () =>{ name.value = 'lisi'; age.value = 30 }
3)toRaw 函数
- 作用:将一个由 reactive 生成的响应式对象转为普通对象。
- 应用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。
4)markRaw 函数
- 标记一个对象,使其永远不会再成为响应式对象。
- 应用场景
- 有些值不应被设置为响应式,如复杂的第三方类库。
- 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。
8、computed 计算属性
-
依赖项的某一项发生改变时,就会触发重新计算。
import { reactive,computed } from 'vue' let person = reactive({ firstname:'zhan', lastname:'shan' }) let fullname = computed(() => { return person.firstname + person.lastname })
9、watch 侦听器
1)参数
- 参数1是监视的变量
- 参数2是回调函数
- 参数3是配置
2)ref 类型
-
监视 ref 所定义的一个响应式数据
let sum = ref(0) watch( sum,(newValue,oldValue)=>{ ... },{ immediate:true })
-
监视 ref 所定义的多个响应式数据,此时 newValue 和 oldValue 也变成数组。
watch( [ sum,msg ],(newValue,oldValue)=>{ ... })
-
监听 ref 类型的数据只是浅层次的,可以开启深度监听,但是深度监听后返回的新旧值是一样的。
let sum = ref({ foo:{ bar:'zhanshan' } }) // 开启了深度监听,newval和oldVal 一致。 watch( sum,(newValue,oldValue)=>{ ... },{ deep:true })
3)reactive 类型
-
监视 reactive 所定义的一个响应式数据的全部属性。
- 底层强制开启了深度监视,即 deep 配置无效,且无法正确的获取 OldVal。
let person = reactive({ name:'zhan', age:18 }) watch( person,(newValue,oldValue)=>{ ... },{ immediate:true })
-
监视 reactive 所定义的一个响应式数据的单个属性,可以使用回调函数返回一个值的形式。
- 可以正确获取 OldVal
watch( ()=>person.age ,(newValue,oldValue)=>{ ... },{ immediate:true })
-
监视 reactive 所定义的一个响应式数据的多个属性。
watch( [()=>person.age,()=>person.name ],(newValue,oldValue)=>{ ... })
-
监视 reactive 所定义的一个响应式数据的对象属性,配置有效。
let person = reactive({ name:'zhan', job:{ j1:'tx' } }) // 监视 j1的变化 watch( ()=>person.job ,(newValue,oldValue)=>{ ... },{ immediate:true, deep:true })
4)watchEffect 函数
(1)定义
-
watch:指明监视的属性,也要指明监视的回调。
-
watchEffect:不用指明监视哪个属性;监视的回调中用到哪个属性,就监视哪个属性。
watchEffect(()=>{ const x1 = sum.value const x2 = person.job.j1.salary })
-
watchEffect 对比 computed
- computed 注重的是计算出来的值,所以必须写返回值
- watchEffect 注重的是过程,不用写返回值。
- ==watchEffect 默认是立即执行的。
(2)高级配置
-
回调函数的参数为 oninvalidate 函数,在回调执行之前会触发这个函数,可以做防抖之类的操作。
-
watchEffect 的第二个参数是配置项
- flush 是执行时机,有 pre,async,post;
-
watchEffect 函数的返回值可以用于停止监听。
const stop = watchEffect((oninvalidate) =>{ log('this is stop'); oninvalidate(() =>{ log('before') }) },{ flush:'post', // 先执行 DOM视图 再执行 监听器 onTrigger(e){ debugger; } }) // 停止监听 const stopWatch = () => stop()
10、父子组件传参
1)父传子 - defineProps
-
通过 defineProps API
// 父组件 <Child :num='num' /> // 子组件 <p> {{num}} </p> import { defineProps } from 'vue' const props = defineProps({ num:{ type:Number, default:30 } }) log(pros.num)
2)子传父 - defineEmits
-
通过 defineEmits API
// 父组件 <Child @fn='count' /> let num = ref(20) const count = ()=>{ num.value++ } // 子组件 <p @click='handleCount'> {{num}} </p> import { defineEmits } from 'vue' const emit = defineEmits(['fn']) const handleCount=()=>{ emit('fn') }
3)依赖注入
-
作用:实现组件的跨任意级别的自顶向下的通信,同时后代组件也可以修改值,祖先组件也可以响应。
-
套路:父组件有一个
provide
选项来提供数据,后代组件有一个inject
选项来开始使用这些数据 -
具体写法:
-
祖先组件中:
let car = reactive({name:'奔驰',price:'40万'}) provide('car',car);
-
后代组件中:
const car = inject<Ref<string>>('car') <style> .box { color: V-bind(color); } </style>
-
-
防止后代组件修改数据,可以在祖先组件中使用 readonly 限制。
provide('color',readonly( colorVal ));
4)Mitt
- 使用第三方库 Mitt,代替事件总线。
5)v-model
-
可以在自定义组件中双向通信。
-
v-model 其实是一个语法糖,通过 props 和 emit 组合而成。
-
Vue3 新增支持多个 v-model,支持自定义 Modifiers 修饰符。
-
父组件
<vModelVue v-model:textVal.isBt='text' v-modelValue='isShow'></vModelVue> const isShow = ref(true) const text = ref('zhanshan')
-
子组件
const props = defineProps<{ modelValue:boolean, textVal:string, texyValModifiers?:{ isBt:boolean } }> const emit = defineEmits(['update:modelValue','update:textVal']) const change = ()=>{ emit('update:modelValue',false); emit('update:textVal',props?.texyValModifiers?.isBt ? a++ : a-- ) }
11、自定义指令
-
属于破坏性更新,指令的生命周期被完全改变,指令的生命周期与Vue的生命周期一致。
-
格式
// 自定义指令名:参数.修饰符="{}" <A v-move:aa.man="{ background: 'red' }"></A>
-
指令的生命周期有两个参数,参数1是元素本身,参数2可以找到指令的参数、修饰符等。
const vMove: Directive = { mounted(el: HTMLElement, dir: DirectiveBinding) { el.style.background = dir.value.background }, updated() { }, }
12、自定义 hooks
-
用于处理复用代码逻辑的一些封装,类似于Vue2的 mixins。
-
mixins 的弊端是存在覆盖,组件的 data、methods、filter等会覆盖 mixins 同名的data、methods。
-
本质是一个函数,把 setup 函数中使用的 Composition API 进行了封装。
// 组件 import usePoint from '../hooks/userPoint' export default { setup(){ let point = usePoint(); return {point} } } // hooks 函数 import {reactive,onMounted,onBeforeMount } from 'vue' export default function(){ let point = reactive({ x:0, y:0 }) function savePoint(event){ point.x = event.pageX point.y = event.pageY } onMounted(() => { window.addEventListener('click',savePoint) }) onBeforeMount(() => { window.removeEventListener('click',savePoint) }) return point }
13、全局函数与变量
-
Vue3 移除了 filter,但是可以在 globalProperties 追加属性,添加全局函数和变量,达到相同效果。
// main.js app.config.globalProperties.$filters = { format<T>(str:T){ return '123--'+str } } app.config.globalProperties.$env = 'dev' // App.vue <div>{{$filters.format('456')}}</div> <div> {{ $env }} </div> const app = getCurrentInstance() app?.proxy.$filters.format(('ts'))
-
这种形式需要声明文件
type Filter = { format<T>(str:T):string } declare module 'vue'{ export interface ComponentCustomProperties{ $filters:Filter, $env:string } }
14、CSS 新特性
-
样式穿透
.demo{ :deep(.el-input__inner){ color:red } }
-
插槽选择器
:slotted(div){ color:red }
-
动态 class
const color = ref('red'); <style> div{ color:v-bind('color') } </style>
二、对比 Vue2
-
Vue3 自带 treeShaking,因为组合式API不引入就不会被打包。而 Vue2 属于 options API,无法做到。组合式API将同一功能的代码同一管理,不至于像之前那样分散。
-
生命周期的 beforeCreate 和 created 被 setup 代替。
-
生命周期的 destroyed 被重命名为 unmounted;beforeDestroy 被重命名为 beforeUnmount。
-
新增了3个组件:Fragment 支持多个根结点、Suspense 可以在组件渲染之前的等待时间显示指定内容、Teleport 可以让子组件在布局上跳出父组件。
-
Proxy 代替 Object.defineProperty 重构了响应式系统,可以监听到数组下标变化和对象新增属性,因为监听的不是对象属性,而是对象本身。
-
优化了Diff 算法,虚拟DOM生成速度提升了 200%
-
支持在 style 标签中使用 v-bind,给 CSS 绑定 JS 变量。
-
$set 和 $delete 移除。
-
事件总线Bus被移除,但是可以使用第三方库 Mitt。
-
父组件使用子组件,通过 import 引入后不再需要 component 配置项声明。
-
移除了过滤器(filter),但是可以在 app.config.globalProperties追加。
app.config.globalProperties.$filters = {}
-
自定义指令破坏性更新,Vue3中指令的生命周期与Vue实例的生命周期一致。