1.Vue3简介
-
相关阅读:
- Vue3 中文文档 vue3js.cn/docs/zh/
- Vue3 设计理念 vue3js.cn/vue-composi…/
2.vue3--你知道哪些
2.1 性能的提升
-
打包大小减少41%
-
初次渲染快55%, 更新渲染快133%
-
内存减少54%
......
2.源码的升级
-
使用Proxy代替defineProperty实现响应式
-
重写虚拟DOM的实现和Tree-Shaking
......
3.拥抱TypeScript
- Vue3可以更好的支持TypeScript
4.新的特性
-
Composition API(组合API)
- setup配置
- ref与reactive
- watch与watchEffect
- provide与inject
- ......
-
新的内置组件
- Fragment
- Teleport
- Suspense
-
其他改变
- 新的生命周期钩子
- data 选项应始终被声明为一个函数
- 移除keyCode支持作为 v-on 的修饰符
- ......
5. Composition API 的优势
-
Options API 存在的问题
使用传统OptionsAPI中,新增或者修改一个需求,就需要分别在data,methods,computed里修改 。
-
Composition API 的优势
我们可以更加优雅的组织我们的代码,函数。让相关功能的代码更加有序的组织在一起。
3.常用 Composition API
3.1 setup
理解:Vue3.0中一个新的配置,值为一个函数
-
组件中所用到的:数据、方法等等,均要配置在setup中
-
setup函数的两种返回值:
- 若返回一个对象,则对象中的属性、方法, 在模板中均可以直接使用。(重点关注!)
- 若返回一个渲染函数:则可以自定义渲染内容。(了解)
-
steup 执行时机
- 在beforeCreate之前执行一次,this是undefined。
-
setup的参数
-
props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性。
-
context:上下文对象
- attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于
this.$attrs
。 - slots: 收到的插槽内容, 相当于
this.$slots
。 - emit: 分发自定义事件的函数, 相当于
this.$emit
。
- attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于
-
-
注意点:
-
尽量不要与Vue2.x配置混用
- Vue2.x配置(data、methos、computed...)中可以访问到setup中的属性、方法。
- 但在setup中不能访问到Vue2.x配置(data、methos、computed...)。
- 如果有重名, setup优先。
-
setup不能是一个async函数,因为返回值不再是return的对象,而是promise,模板看不到return对象中的属性。(后期也可以返回一个Promise实例,但需要Suspense和异步组件的配合)
-
3.2 ref
-
作用:定义一个响应式的数据
-
语法:
const xxx = ref(initValue)
- 创建一个包含响应式数据的引用对象(reference对象,简称ref对象)
- JS中操作数据:
xxx.value
- 模板中读取数据: 不需要.value,直接:
<div>{{xxx}}</div>
-
其他
- 接收的数据可以是:基本类型、也可以是对象类型。
- 基本类型的数据:响应式依然是靠
Object.defineProperty()
的get
与set
完成的。 - 对象类型的数据:内部 “ 求助 ” 了Vue3.0中的一个新函数——
reactive
函数(也就是ES6的Proxy 代理)。
3.3 reactive函数
- 作用: 定义一个
对象类型
的响应式数据(基本类型不要用它,要用ref
函数) - 语法:
const 代理对象= reactive(源对象)
接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象) - reactive定义的响应式数据是“深层次的”。
- 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作
3.4 computed函数
-
配置与vue2.x中computed配置功能一致
-
写法
import {computed} from 'vue' setup(){ ... //计算属性——简写(没有考虑写的情况) let fullName = computed(()=>{ return person.firstName + '-' + person.lastName }) //计算属性——完整(考虑读和写) let fullName = computed({ get(){ return person.firstName + '-' + person.lastName }, set(value){ const nameArr = value.split('-') person.firstName = nameArr[0] person.lastName = nameArr[1] } }) } 复制代码
3.5 watch函数
-
与vue2.x中的watch配置功能一致
-
支持三个参数
-
两个小坑
- 监视
reactive
定义的响应式数据时:oldValue
无法正确获取,强制开启尝试监视 - 监视
reactive
内某个属性时:deep配置有效,(如对象类型有意义,基本数据类型无意义)
import {ref,reactive,watch} from "vue" setup(){ let person=reactive({ name:"张三", age:18, job:{ j1:{ salary:20 } } }) //情况一:监视ref定义的响应式数据 支持第三个参数 watch(sum,(newValue,oldValue)=>{ console.log('sum变化了',newValue,oldValue) },{immediate:true}) //情况二:监视多个ref定义的响应式数据 watch([sum,msg],(newValue,oldValue)=>{ console.log('sum或msg变化了',newValue,oldValue) },) /* 情况三:监视reactive定义的响应式数据 若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue!! 若watch监视的是reactive定义的响应式数据,则强制开启了深度监视 */ watch(person,(newValue,oldValue)=>{ console.log('person变化了',newValue,oldValue) },{immediate:true,deep:false}) //此处的deep配置不再奏效 //情况四:监视reactive定义的响应式数据中的某个属性 watch(()=>person.job,(newValue,oldValue)=>{ console.log('person的job变化了',newValue,oldValue) },{immediate:true,deep:true}) //情况五:监视reactive定义的响应式数据中的某些属性 watch([()=>person.job,()=>person.name],(newValue,oldValue)=>{ console.log('person的job变化了',newValue,oldValue) },{immediate:true,deep:true}) //特殊情况 watch(()=>person.job,(newValue,oldValue)=>{ console.log('person的job变化了',newValue,oldValue) },{deep:true}) //此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效 } 复制代码
- 监视
3.6 watchEffect函数
-
watch的套路是:既要指明监视的属性,也要指明监视的回调
-
watchEffect
的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。 -
watchEffect
有点像computed):- 但
computed
注重的计算出来的值(回调函数的返回值),所以必须要写返回值。 - 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。
//watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。 watchEffect(()=>{ const x1 = sum.value const x2 = person.age console.log('watchEffect配置的回调执行了') }) 复制代码
- 但
3.7 hook函数
- 什么是hook?—— 本质是一个函数,把setup函数中使用的Composition API进行了封装。
- 类似于vue2.x中的mixin。缺点是组件的data,methods会覆盖mixins里的同名data,methods
- 自定义hook的优势: 复用代码, 让setup中的逻辑更清楚易懂。
- Vue3 hook 库Get Started | VueUse
案例 本地图片转 Base64
import { onMounted } from 'vue'
type Options = {
el: string
}
type Return = {
Baseurl: string | null
}
export default function (option: Options): Promise<Return> {
return new Promise((resolve) => {
onMounted(() => {
const file: HTMLImageElement = document.querySelector(option.el) as HTMLImageElement;
file.onload = ():void => {
resolve({
Baseurl: toBase64(file)
})
}
})
const toBase64 = (el: HTMLImageElement): string => {
const canvas: HTMLCanvasElement = document.createElement('canvas')
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D
canvas.width = el.width
canvas.height = el.height
ctx.drawImage(el, 0, 0, canvas.width,canvas.height)
console.log(el.width);
return canvas.toDataURL('image/png')
}
})
}
复制代码
使用
import useBase64 from './hooks'
useBase64({el:'#img'}).then(res=>{
console.log(res)
})
复制代码
3.8 toRef
-
作用:创建一个 ref 对象,其value值指向另一个对象中的某个属性。
-
语法:
const name = toRef(obj, 'bar')
-
应用: 要将响应式对象中的某个属性单独提供给外部使用时。
<template> <div> <button @click="change">按钮</button> {{state}} </div> </template> <script setup lang="ts"> import { reactive, toRef } from 'vue' const obj = { foo: 1, bar: 1 } const state = toRef(obj, 'bar') // bar 转化为响应式对象 const change = () => { state.value++ console.log(obj, state); } </script> 复制代码
3.9 toRefs
-
扩展:
toRefs
与toRef
功能一致,但可以批量创建多个 ref 对象,语法:toRefs(person)
-
应用:批量创建ref对象主要 是方便我们解构使用
import { reactive, toRefs } from 'vue' const obj = reactive({ foo: 1, bar: 1 }) let { foo, bar } = toRefs(obj) foo.value++ console.log(foo, bar); 复制代码
4 . 其他 Composition API
4.1shallowReactive 与 shallowRef
-
shallowReactive
:只处理对象最外层属性的响应式(浅响应式) -
shallowRef
:只处理基本数据类型的响应式, 不进行对象的响应式处理。 -
什么时候使用?
-
如果有一个对象数据,结构比较深,但变化 时只是外层属性变化==>
shallowReactive
-
如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换===> shallowRef
-
4.2 readonly 与shallowReadonly
- readonly: 让一个响应式数据变为只读的(深只读)。
- shallowReadonly:让一个响应式数据变为只读的(浅只读)。
- 应用场景: 不希望数据被修改时。
4.3 toRaw 与 markRaw
-
toRaw
- 作用:将一个由
reactive
生成的 响应式对象 转为 普通对象 - 使用场景:用于读取响应式对象=>对应的普通对象,对这个普通对象的所有操作,不会引起页面的更新
- 作用:将一个由
-
markRaw
-
作用:标记一个对象,使用其永远不会再成为响应式对象
-
应用场景:
- 有些值不应被设置为响应式的,例如复杂的第三方类库
- 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能
-
4.4 customRef
-
作用:创建一个自定义的ref、并对其依赖项跟踪和更新触发进行显示控制
-
实现防抖效果:
<template> <input type="text" v-model="keyword"> <h3>{{keyword}}</h3> </template> <script> import {ref,customRef} from 'vue' export default { name:'Demo', setup(){ // let keyword = ref('hello') //使用Vue准备好的内置ref //自定义一个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 return { keyword } } } </script> 复制代码
4.5 provide 与 inject
-
作用:实现
祖与后代
组件间通信 (如是父子
之间通信 建议使用props) -
套路: 父组件有一个
provide
选项来提供数据,后代组件有一个inject
选项来开始使用这些数据 -
具体写法:
注意:如果传递普通的值 是不具有响应式的 需要通过ref reactive 添加响应式
-
祖组件中:
setup(){ ...... let car = reactive({name:'奔驰',price:'40万'}) provide('car',car) //可以用来传递响影数据 ...... } 复制代码
-
后代组件中:
setup(props,context){ ...... const car = inject('car') return {car} ...... } 复制代码
4.6响应式数据的判断
- isRef: 检查一个值是否为一个 ref 对象
- isReactive: 检查一个对象是否是由
reactive
创建的响应式代理 - isReadonly: 检查一个对象是否是由
readonly
创建的只读代理 - isProxy: 检查一个对象是否是由
reactive
或者readonly
方法创建的代理
5.语法糖
5.1 setup
<script setup> </script>
复制代码
-
在
<script setup>
包裹 的任何在内部声明的顶级绑定(包括变量,函数声明和导入)都可以直接在模板中使用 -
setup()
函数返回的值 ,引用在模板中引用时会自动展开<script setup> // variable const msg = 'Hello!' // functions function log() { console.log(msg) } </script> <template> <div @click="log">{{ msg }}</div> </template> 复制代码
-
使用组件
建议PascalCase 命名的组件标签以保持一致性,它有助于区分原生自定义元素
<script setup> import MyComponent from './MyComponent.vue' </script> <template> <MyComponent /> //不使用my-Component是因为区别是否为原生元素 </template> 复制代码
-
动态组件
在setup的语法糖的便携下,由于组件被引用为变量而不是在字符串键下注册,因此
:is
在内部使用动态组件时应该使用动态绑定<script setup>
:<script setup> import Foo from './Foo.vue' import Bar from './Bar.vue' </script> <template> <component :is="Foo" /> <component :is="someCondition ? Foo : Bar" /> </template> 复制代码
-
组件之间的传值
要声明具有完整类型推断支持的选项
props
,emits
我们可以使用defineProps
和defineEmits
API,它们在内部自动可用<script setup>
:<script setup> const props = defineProps({ foo: String }) const emit = defineEmits(['change', 'delete']) // setup code </script> 复制代码
-
顶层 await
顶层
await
可以在里面使用<script setup>
。生成的代码将编译为async setup()
:<script setup> const post = await fetch(`/api/post/1`).then((r) => r.json()) </script> 复制代码
-
useSlots()
&
useAttrs()- 插槽
useSlots()
==setupContext.slots
- useAttrs() 相当于
setupContext.attrs
您可以直接在模板
$slots
中访问它们。$attrs
在您确实需要它们的极少数情况下,请分别使用useSlots
和useAttrs
助手: - 插槽
-
对外暴露属性 (defineExpose)
在vue3.x的setup语法糖中定义的变量默认不会暴露出去,需要使用defineExpose({}) 来暴露组件内部属性给父组件使用为了在
<script setup> import { ref } from 'vue' const a = 1 const b = ref(2) defineExpose({ a, b }) </script> 复制代码
当父组件通过模板
ref
的方式获取到当前组件的实例,获取到的实例会像这样{a:1
,b:2
}<script setup> import DefinExpose from './components/DefinExpose.vue' import {ref,onMounted} from 'Vue' let child= ref(null) onMounted(()=>{ console.log(child.a) //1 }) </script> <template> <DefinExpose ref="child"/> </template> 复制代码
7.组件
7.1 Fragment
- 在Vue2中: 组件必须有一个根标签
- 在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中
- 好处: 减少标签层级, 减小内存占用
7.2 Teleport
-
什么是
Teleport
——Teleport
是一种能够将我们的组件html结构移动到指定位置的技术<teleport to="移动位置"> <div v-if="isShow" class="mask"> <div class="dialog"> <h3>我是一个弹窗</h3> <button @click="isShow = false">关闭弹窗</button> </div> </div> </teleport> 复制代码
7.3 Suspense
-
等待异步组件时渲染一些额外内容,让应用有更好的用户体验
-
使用步骤:
-
异步引入组件
import {defineAsyncComponent} from 'vue' const Child = defineAsyncComponent(()=>import('./components/Child.vue')) 复制代码
-
使用
Suspense
包裹组件,并配置好default
与fallback
<template> <div class="app"> <h3>我是App组件</h3> <Suspense> <template v-slot:default> <Child/> </template> <template v-slot:fallback> <h3>加载中.....</h3> </template> </Suspense> </div> </template> 复制代码
-
8实现函数式组件
-
前置知识
-
createVNode 生成虚拟dom节点
v3.cn.vuejs.org/guide/rende…虚拟-dom-树
-
render 把虚拟dom节点渲染到真实的dom中
基础思路
- 以Message组件为基础创建虚拟dom - createVNode
- 准备一个等待渲染Message组件的容器 - container容器
- 把Message组件生成的虚拟dom渲染到准备好的容器中 - render
代码落地
1)函数调用改写
components/Message/index.js
// 实现使用函数调用xtx-message组件的逻辑 import { createVNode, render } from 'vue' import Message from './index.vue' //引用组件 export function Message ({ type, text }) { // 生成vnode const messageVNode = createVNode(Messsage, { type, text }) // 动态生成一个容器 const divContainer = document.createElement('div') // 添加到容器中 document.body.appendChild(divContainer) //渲染成真实Dom render(messageVNode, divContainer) setTimeout(() => { //消失 render(null, divContainer) }, 1500) } 复制代码
2)验证使用
import Message from '@/components/Message' Message({ type: 'error', text: '登录失败' }) 复制代码
-
9生命周期
vue2.x
vue3.x
-
Vue3.0 中可以继续使用Vue2.x中的生命周期钩子,但有两个钩子被更名
beforeDestroy
改名为beforeUnmount
destroyed
改名为unmounted
-
Vue3.0也提供了 Composition API 形式的生命周期钩子,与Vue2.x中钩子对应关系如下:
beforeCreate
===>setup()
created
=======>setup()
beforeMount
===>onBeforeMount
mounted
=======>onMounted
beforeUpdate
===>onBeforeUpdate
updated
=======>onUpdated
beforeUnmount
==>onBeforeUnmount
unmounted
=====>onUnmounted
9 其他
9.1 全局API 的转移
-
Vue3.0中对这些API做出了调整:
-
将全局的API,即:
Vue.xxx
调整到应用实例(app
)上2.x 全局 API( Vue
)3.x 实例 API ( app
)Vue.config.xxxx app.config.xxxx Vue.config.productionTip 移除 Vue.component app.component Vue.directive app.directive Vue.mixin app.mixin Vue.use app.use Vue.prototype app.config.globalProperties
-
9.2 TemplateRef(操作dom)
setup 函数中 如何使用ref 获取 dom节点或者组件实例对象
-
vue2.x 中用有三种使用场景
- ref + 普通dom标签 获取真实dom对象
<div ref="box"></div> this.$refs.box
- ref + 组件标签 获取组件实例对象
<Form ref="form"/> this.$refs.form.validate()
- ref + v-for 获取由dom对象(实例对象)组成的数组 (不经常使用)
- ref + 普通dom标签 获取真实dom对象
-
veu3 中
- 使用ref函数传入null创建 ref对象 =>
const hRef = ref(null)
- 模板中通过定义ref属性等于1中创建的ref对象名称建立关联 =>
<h1 ref="hRef"></h1>
- 把
hRef
return出去 - 使用 =>
hRef.value
<template> <h1 ref="h1Ref">我是普通dom标签</h1> <ref-comoonent ref="comRef"></ref-comoonent> </template> <script> import { onMounted, ref } from 'vue' import RefComoonent from '@/components/RefComponent' export default { components: { RefComoonent }, setup() { const h1Ref = ref(null) const comRef = ref(null) onMounted(() => { console.log(h1Ref.value) console.log(comRef.value) }) // 必须return return { h1Ref, comRef } } } </script> 复制代码
- 使用ref函数传入null创建 ref对象 =>
9.2 其他改变
-
data选项应始终被声明为一个函数。
-
过度类名的更改:
-
Vue2.x写法
.v-enter, .v-leave-to { opacity: 0; } .v-leave, .v-enter-to { opacity: 1; } 复制代码
-
Vue3.x写法
.v-enter-from, .v-leave-to { opacity: 0; } .v-leave-from, .v-enter-to { opacity: 1; } 复制代码
-
-
移除keyCode作为 v-on 的修饰符,同时也不再支持
config.keyCodes
-
移除
v-on.native
修饰符-
父组件中绑定事件
<my-component v-on:close="handleComponentEvent" v-on:click="handleNativeClickEvent" /> 复制代码
-
子组件中声明自定义事件
<script> export default { emits: ['close'] } </script> 复制代码
-
-
移除过滤器(filter)
过滤器虽然这看起来很方便,但它需要一个自定义语法,打破大括号内表达式是 “只是 JavaScript” 的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器。
9.8 语法兼容问题
-
过滤器filter移除 (插值表达式里不能再使用过滤器 可以使用methods替代)
{{ name | filtername }} 这语法不是一个合格的表达式 不符合表达式的语义 而且完全可以通过methods替代 被干掉了 复制代码
-
.sync语法移除 (dialog) (和v-model语法合并)
<el-dialog :visible.sync> //所以使用vue3时使用 element-plus 复制代码
-
eventBus $on emit
事件总线 实现非父子关系的组件通信 常见于兄弟之间 1. 往Vue.prototype.$eventBus = new Vue() 2. 在接受数据的组件中 this.$eventBus.$on('get-msg',(msg)=>{}) // 方法被干掉了 3. 在发送数据的组件中 this.$eventBus.$emit('get-msg','this is msg') 复制代码