vue3带来了什么
- 性能提升(打包体积减少, 内存减少, 渲染更快)
- 源码升级(proxy代替defineProperty实现响应式)
- 拥抱TS(更好支持TS)
- 新特性
创建vue3工程
使用vue-cli创建
- 查看版本 vue --version
- 安装或升级 npm install -g @vue/cli
- 创建 vue create vue_test
- 启动 cd vue_test
npm run serve
分析工程结构
//引入不再是vue构造函数,这就是与vue2区别, 引入一个createApp的工厂函数
import { createApp } from 'vue'
import App from './App.vue'
// createApp(App).mount('#app')
// 创建应用实例对象--app(类似于之前vue2里面的vm, app比vm更加轻盈)
const app = createApp(App)
// 挂载
app.mount('#app')
// const vm = new Vue({
// render: h => h(App)
// })
// vm.$mount('#app')
当出现这种错误,表示没有挂载, 但是在测试的时候可以通过以下来改变
在vue.config.js中添加关闭语法检查即可
常用Composition API
1. setup
vue3的一个新配置, 值为一个函数.
组件中所用到的: 数据, 方法等均配置在setup中
setup函数有两个返回值:
- 若返回一个对象, 则对象中的属性和方法在模板中均可以直接使用.
- 若返回一个渲染函数, 则可以自定义渲染内容.
>`import { h } from 'vue'
> export default {
> render() {
return h('div')//h('div', 'hhhhhh')可以接收两个参数,
//第一个是渲染的标签, 第二个是值
}
}`
注意:
- 不要配合vue2使用, 因为vue2配置的data, methods等可以访问setup中的属性和方法, 而setup不能访问vue2配置, 若有重名,以setup优先.
- setup不能是一个async函数.因为返回值为一个promise,不再是一个对象,模板无法访问到return对象中的属性.
2. ref函数
ref标签属性打标识(vue2)
作用: 定义一个响应式数据.
语法: const xxx = ref(initValue)
- 创建一个包含响应式数据的引用对象(reference对象, 简称ref对象).
- JS中操作数据: xxx.value
- 模板中读取数据,不需要.value,因为vue会自动解析加上.value
注意:
- 接收数据类型可以是: 基本数据类型, 对象类型.
- 基本数据类型: 响应式数据依靠Object.defineProperty()的get和set完成的
- 对象类型: 内部求助vue3中的reactive函数.
3. reactive函数
作用: 定义一个对象类型的响应式数据(基本数据类型不要, 用ref函数)
语法: const 代理对象 = reactive(源对象) 接收一个对象(或数组), 返回一个代理对象(Proxy的实例对象, 简称Proxy对象)
注意:
- reactive定义的响应式数据是深层次的
- 内部基于ES6Proxy实现, 通过代理对象操作源对象内部数据进行操作.
4. vue3中的响应式原理
1. vue2的响应式
实现原理:
- 对象类型: 通过Object.defineProperty()对属性的读取, 修改进行拦截(数据劫持)
- 数组类型: 通过重写更新数组的一系列方法来实现拦截.
存在问题:
- 新增属性, 删除属性, 界面不会更新
- 直接通过下标改变数据,界面不会自动更新
若想响应式添加:
this.$set(添加到某个位置中, 属性名, 属性值) --(this.person, ‘sex’, 女)
Vue.set(this.person, ‘sex’, 女)
响应式删除:
this.$delete(删除数据的位置, 属性名, 属性值) --(this.person, ‘sex’, 女)
Vue.delete()
修改数组中的数据:
this.$set(this.person.hobby, 0, ‘学习’) —第一个值为修改的位置, 第二个为下标, 第三个为修改的值.
this.person.hobby.splice(0, 1, ‘学习’) —第一个参数为下标, 第二个为删除个数, 第三个为修改的值
注意: splice()函数可以删除, 增加(第二个参数为0), 替换(先删除,再添加)
2. vue3的响应式
实现原理:
- 通过Proxy(代理) : 拦截对象中任意属性的变化. 包括属性值读写, 添加, 删除等 (Proxy是一个vue中的构造函数)
- 通过Reflect(反射) : 对源对象的属性进行操作.(响应式数据)
注意:
[ ]运算符可以使用字符串变量的内容作为属性名, 点运算符不能.
eg: obj = { name: ‘ly’}
读取到"name"字符串
obj[name] //输出ly , 不能通过obj.name
const person = {
name:'ly',
age:20
}
//p为代理对象, person为源对象
const p = new Proxy(person, {
// 拦截读取属性值
get(target, propName){
// target为源对象, propName为属性名
return Reflect.get(target, propName)
},
// 拦截修改或增加新属性
set(target, propName, value){
return Reflect.set(target, propName, value)
},
// 拦截删除属性
deleteProperty(target, propName){
return Reflect.deleteProperty(target, propName)
}
})
5. reactive对比ref
-
从定义数据角度:
- ref用来定义: 基本数据类型
- reactive用来定义: 对象(或数组)类型数据
注意: ref也可以用来定义对象(或数组)类型数据, 内部会自动通过reactive转为代理对象.
-
从原理角度对比:
- ref通过Object.defineProperty()的get与set来实现响应式(数据劫持)
- reactive通过使用Proxy来实现响应式(数据劫持), 并通过reflect操作源对象内部的数据.
-
使用角度对比:
- ref定义的数据: 操作数据需要.value, 读取数据时模板中直接读取不需要.value
- reactive定义的数据: 操作数据与读取数据: 均不需要.value.
6. setup注意点
-
setup执行的时机
- 在beforeCreate之前执行一次, this是undefined.
-
setup的参数(只有两个参数)
-
props: 值为对象, 包含: 组件外部传递过来, 且组件内部声明接收了的属性
-
context: 上下文对象
- attrs : 值为对象, 包含: 组件外部传递过来, 但没有props配置中声明的属性, 相当于vue2中this.$attrs.
- slots : 收到的插槽内容, 相当于this.$slots, (若想使用具名插槽, 最好用v-slot:qqq,若用slot = “qqq”, 在子组件中会出现警告)
- emit : 分发自定义事件的函数, 相当于this.$emit, 需要在子组件中进行emits配置, 否则会出现警告.
-
7. 计算属性和监视
1. computed函数
- 与vue2中computed配置功能一致
setup(){
const person = reactive({
fName : 'l',
lName : 'y'
})
// 简写的计算属性, 只是可读, 不能修改
// person.fullName = computed(() => {
// return person.fName + '-' + person.lName
// })
// 完整写法, 可以修改
person.fullName = computed({
get(){
return person.fName + '-' + person.lName
},
set(value){
const nameArr = value.split('-')
person.fName = nameArr[0]
person.lName = nameArr[1]
}
})
return {
person
}
}
2. watch函数
与vue2 中watch配置一样.
注意:
- 监视reactive定义的响应式数据时 : oldValue无法正确获取, 强制开启深度监视(deep配置失效)
- 监视reactive定义的响应式数据中某个属性时: deep配置有效.
watch(sum, (newValue, oldValue) => {
console.log(newValue, oldValue);
}, {immediate: true})
//可以带有三个参数,第一个是监视的属性, 当属性值发生变化的时候,
//自动调用第二个回调函数, 第三个参数是{immediate 表示立即监视, deep 表示深度监视}
// 监视多个属性的时候第一个参数可以写成[sum, a, b] ref定义的响应式数据
watch(person, (newValue, oldValue) => {
console.log(newValue, oldValue);
}, {immediate: true})
// 当监视reactive定义的响应式数据, 此时无法获取oldValue,
//监视的是reactive响应式数据, 强制开启深度监视.
watch(() => person.name, (newValue, oldValue) => {
console.log(newValue, oldValue);
}, {immediate: true})
// 当监视的是reactive定义的响应式数据中某个属性的时候,
//deep才有效果, 并且第一个参数为一个函数,
//当监视多个属性的时候也可以用数组
3. watchEffect函数
-
watch的套路是: 既要指明监视的属性, 也要指明监视的回调.
-
watchEffect的套路是 : 不用指明监视那个属性, 监视的回调中用到那个属性, 就要监视哪个属性.
-
watchEffect有点像computed:
- 但computed注重的计算出来的值(回调函数的返回值), 所以必须要写返回值.
- watchEffect更注重的是过程(回调函数的函数体), 所以不用写返回值.
watchEffect(() => {
const x1 = sum.value
console.log('回调执行了');
})
8. 生命周期
在vue3中都是组合式API
beforeCreate ==> setup
create ==> setup
beforeMounted ==> onBeforeMounted
mounted ==> onMounted
beforeUpdate ==> onBeforeUpdate
updated ==> onUpdated
beforeUnmount ==> onBeforeUnmounted
unmounted ==> onUnmounted
9. 自定义hook函数
- 本质是一个函数, 将setup函数中使用的Composition API进行封装.
- 自定义hook优势: 复用代码, 让setup中的逻辑更清楚易懂
10. toRef
- 作用: 创建一个ref对象, 其value值指向另一个对象中的某个属性.
- 语法: const name = toRef(person, ‘name’)
- 应用: 要将响应式对象中某个属性单独提供给外部使用时.
- 扩展: toRefs与toRef功能一致, 但可以批量创建多个ref对象, toRefs(person)
其他Composition API
1. shallowReactive 与 shallowRef
-
shallowReactive : 只处理对象最外层属性的响应式(浅响应式).
-
shallowRef : 只处理基本数据类型的响应式, 不进行对象的响应式处理.
-
什么时候使用?
- 如果有一个对象数据, 结构比较深, 但变化只是外层属性变化 ==> shallowReactive
- 如果有一个对象数据, 后续功能不会修改该对象中的属性, 而是新生成的对象来替换 ===> shallowRef
2. readonly 与 shallowReadonly
- readonly: 让一个响应式数据变为只读的(深只读).
- shallowReadonly: 让一个响应式数据变为只读的(浅只读)
- 当希望数据不被修改时.
3. toRaw 与 markRaw
-
toRaw:
- 作用: 将一个由reactive生成的响应式对象转为普通对象.
- 使用场景: 用于读取响应式对象对应的普通对象, 对这个普通对象的所有操作, 不会引起页面更改.
-
markRaw:
-
作用: 标记一个对象, 使其永远不会再成为响应式对象.
-
应用场景:
- 有些值不应被设置为响应式的, 例如复杂的第三方类库等.
- 当渲染具有不可变的数据源的大列表时, 跳过响应式转换可以提高性能.
-
4. customRef
- 作用: 创建一个自定义的ref, 并对其依赖项跟踪和更新触发进行显示控制.
- 实现:
function myref(value){
return customRef((track, trigger) => {
return {
get(){
track()//追踪
return value
},
set(newValue){
value = newValue
trigger()//重新解析模板
}
}
})
}
let p = myref('hhh')
5. provide 与 inject
- 作用: 实现祖与后代组件通信
- 父组件有一个provide选项来提供数据, 后代组件有一个inject选项来开始使用这些数据.
//父组件
let car = reactive({
name :'ly',
age: 20
})
provide('cat', car)
//孙组件
setup(){
const cat = inject('cat')
console.log(cat);
}
6. 响应式数据的判断(返回都为布尔值)
- isRef : 检查一个值是否为一个ref对象.
- isReactive : 检查一个对象是否由reactive创建的响应式代理.
- isReadonly : 检查一个对象是否是由readonly创建的只读对象.
- isProxy : 检查一个对象是否是由reactive或者readonly方法创建的代理.
新组件
1. Fragment
- 在vue2中 : 组件必须有一个根标签.
- 在vue3中 : 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中
- 好处 : 减少标签层次, 减小内存占用
2. Teleport
- 作用 : 将我们的组件html结构移动到指定位置的技术.
//to = "移动位置"
<teleport to = "body">
<div>
<h3>哈哈哈哈</h3>
</div>
</teleport>
3. Suspense
- 等待异步组件时渲染一些额外内容, 让应用更好的用户体验.
<!-- 使用Suspense包裹组件, 并配置好default, fallback -->
<Suspense>
<template v-slot:default>
<child/>
</template>
<template v-slot:fallback>
<child/>
</template>
</Suspense>
// 异步引入 组件
import {defineAsyncComponent} from 'vue'
const child = defineAsyncComponent(() => import ('./components/Child.vue'))
vue3改变
- data选项应始终被声明一个函数.
- 类名修改: 过渡 v-enter改为v-enter-from, v-leave改为v-leave-from
- 自定义别名按键keyup Vue.config.keyCodes.HH = 13 @keyup.hh = “show”, vue3中移除keyup, 并且不再支持config.keyCodes
- 移除v-on.native修饰符
- 移除过滤器…