1. vue2与vue3 的区别
- 性能提升
Proxy代替defineProerty实现响应式- 重写虚拟DOM算法和 Tree-Shaking(术语表)
- 更好的支持
TypeScript- 新特性
Composition API(组合式API)- 新的内置组件
- 其他改变
2. 创建Vue3工程
2.1 使用vue-cli 创建
- vue-cli 版本必须在 4.5.0 以上
// vue-cli 创建初始化工程
// 需要装 vue-cli 手脚架
vue create vue_demo
2.2 使用vite 创建
vite是新一代构建工具(老的是webpack)
- 优点
- 开发环境中,无需打包,可快速冷启动
- 更好的热重载(HMR)代码更新,页面刷新(
webpack也有)- 按需编译,不再等待整个应用变异
// vite 创建初始化工程
保证 node 12.0 以上 及 npm/yarn 版本即可
npm init vite@latest my-vue-app (没有装依赖)
3.vue2 与 vue3 变化分析
3.1 .vue文件中的template 可以没有 根标签
3.2 main.js 变化
//* vue3 main.js
// 引入不再是Vue构造函数了(要用到new) ,而是 createApp工厂函数
import { createApp } from 'vue'
import App from './App.vue'
// 1. 创建应用实例对象,(类似 vue2 中的 vm, 但是app 体量比vm 更加轻遍)
const app = createApp(App)
// 2.挂载
app.mount('#app')
//* vue2 main.js
// 1. 创建 vm 实例
const vm = new Vue({
render:h => h(App)
})
// 2. 挂载
vm.$mount('#app')
3.3 安装支持vue3 的 新的chrome 插件
无VPN:极简插件 vue2插件 与vue3 同时开启,都会亮,但实际上vue3 插件起作用
4. Composition API(组合式API)详解
4.1 setup 函数
- 新的配置项,值是一个函数
- 函数内部写数据,方法等数据,可以在模版中直接使用
- 我的理解是 将vue2 中的
data、methods、watch、钩子函数 以函数的形式 写在setup函数内部
- 我的理解是 将vue2 中的
- 注意事项
- 函数返回值
- 如果返回了一个对象,模版可以直接使用
- 如果返回 渲染函数,则会替换当前模版的内容,使用 渲染函数的内容
- 可以写vue2 配置,不建议(不深入了)
setup不能是一个async函数setup函数在beforeCreate函数执行前 ,且其中的this的值为undefinedsetup参数props: 组件外部传递过来,组件内部接收的属性(proxy对象)context: 上下文对象attr: 相当于vue2 中的this.$attr,用于 接收父组件传递过来的变量,但在子组件中没有接收到slots: 相当于 vue2 中的this.$slots- vue3 在父组件 中使用时,最好使用
v-slot:aaa的写法
- vue3 在父组件 中使用时,最好使用
emit: 相当于 vue2 中的this.$emit- 子组件使用时,要在
emits对象进行配置,否则有警告
- 子组件使用时,要在
- 函数返回值
import { setup ,ref } from 'vue'
...
props:{}, // 与vue2 相同 ,父组件传递了 就要接收,没有接收会有警告
emits:['事件名1','事件名2'], // 方法中用到了 就要在此声明,没有就会警告
setup(props,context){
// 数据 通过 ref 将数据变为响应式
let name = ref('zs') ;
let age = ref(18);
// 事件
function change(){
context.emit('事件名1')
}
return {
name,
age,
}
}
4.2 ref 函数(reference)
-
定义一个响应类型的数据,可以定义基本类型 和 复杂数据类型(与
reactive不同)- 使数据变成响应式的数据,将数据变成引用对象
-
只有
setup函数,当其中的变量并不是响应式 -
ref将数据 包装了一层
- 简单数据类型:还是使用
defineProerty数据劫持- 包装成了
RefImpl对象实例,修改的时候的时候需要str.value = 1
- 包装成了
- 复杂数据类型: 使用了
reactive函数 见 4.3- 包装成了
RefImpl对象实例 进一步 使用了reactive函数 包装成了Proxy对象实例,修改的时候需要obj.value.str = '1'
- 包装成了
- 简单数据类型:还是使用
-
简单数据类型 使用
ref()
import { setup ,ref } from 'vue'
...
setup(){
// 数据 通过 ref 将数据变为响应式
let name = ref('zs') ;
let age = ref(18);
// 不建议包裹 复杂数据类型。推荐用 reactive
let obj = ref({
a:1,
chilren:{
b:2
}
})
// 方法
function change(){
// 修改数据
name.value = 'ls'
obj.value.a = 2
obj.value.children.b = 3
}
return {
name,
age,
change
}
}
4.3 reactive 函数
- 定义一个对象类型的响应式数据,只能使用复杂数据类型,不能使用简单数据类型
const 代理对象 = reactive(源对象)- 代理对象:Proxy实例对象,简称proxy 对象
4 内部基于ES6的 Proxy 实现,通过
代理对象操作源对象内部数据 见 4.4
- 代理对象:Proxy实例对象,简称proxy 对象
4 内部基于ES6的 Proxy 实现,通过
import { setup ,ref } from 'vue'
...
setup(){
// 数据 通过 ref 将数据变为响应式
let name = ref('zs') ;
let age = ref(18);
// 不建议包裹 复杂数据类型。推荐用 reactive
let obj = ref({
a:1,
chilren:{
b:2
}
})
// 方法
function change(){
// 修改数据
name.value = 'ls'
obj.value.a = 2
obj.value.children.b = 3
}
return {
name,
age,
change
}
}
4.4 vue2与vue3响应式原理
4.4.1 vue2 响应式原理
- 实现原理
- 对象类型: 通过
Object.defineProperty()对属性的读取,修改进行拦截(数据劫持) - 数组类型: vue 通过包裹 Array 构造函数上的 push、shift等7个方法进行包装
- 对象类型: 通过
- 存在问题
- 对象新增属性、删除属性,界面不会更新
- 通过下标修改数组,界面不会更新
// 源数据
const person = {
name: 'zs',
age: 18
}
let p = {}
// 数据劫持: 劫持 源对象 身上的所有属性
for (let k in person) {
Object.defineProperty(p, k, {
// 访问对象属性时触发
get() {
return person[k]
},
// 修改对象身上的 属性时触发
set(value) {
console.log('数据发生了修改,更新视图');
person[k] = value
}
})
}
4.4.2 vue3 响应式原理
- 实现原理
- 通过
Proxy(代理):拦截对象中任意属性的变化 (监听到数据变化) - 通过
Reflect(反射):对 被代理的对象(源对象) 进行操作 (对源数据进行修改) - 总结来说,就是 通过
proxy代理源对象,通过操作 代理对象,监听到代理对象的变化,响应视图,并使用 反射(Reflect) 进行修改源数据
- 通过
// vue3 响应式 简易实现
//#region
// 源数据
const person = {
name: 'zs',
age: 18
}
// 代理对象 Reflect 反射对象
let p = new Proxy(person, {
// 新增或修改 对象属性时触发 target 为源数据 就是person propName就时访问对象
// 访问 对象属性时触发
get(target, propName) {
// return target[propName]
return Reflect.get(target, propName)
},
set(target, propName, value) {
console.log('数据变化,更新视图');
// 新增/修改源数据
// target[propName] = value
Reflect.set(target, propName, value)
},
// 删除 对象属性时触发
deleteProperty(target, propName) {
console.log('数据变化,更新视图');
// 删除 源数据
// delete target[propName]
return Reflect.defineProperty(target, propName)
}
})
4.5 computed 计算属性
- 与vue2 基本一样,不过是
组合式API写法
import { setup ,reactive ,computed } from 'vue'
...
setup(){
// 响应式数据
let person = reactive({
name:'zs',
age:18
})
// 计算属性 - 简写
person.full = computed(()=>{
return person.name +'-' +person.age
)
// 计算属性 - 完整写法
person.full = computed({
set(value){
const arr = value.spilt('-')
person.name = arr[0]
},
get(){
return person.name +'-' +person.age
}
})
return {
person
}
}
4.6 watch 监听属性
ref和reactive监听语法有 5种基本语法+1种特殊语法。reactive deep坑点,代码如下
import { setup ,ref,reactive ,watch } from 'vue'
...
setup(){
// 响应式数据
let person = reactive({
name:'zs',
age:18,
job:{
j1:{
salary:20
}
}
})
let sum = ref(0)
let msg = ref('hello')
// 情况一: 监听到ref 定义的一个响应式数据
watch(sum,(newValue,oldValue)=>{
console.log(newValue,oldValue); // 0 undefined
},{immediate:true})
// 情况二: 监听到ref 定义的多个响应式数据
watch([sum,msg],(newValue,oldValue)=>{
console.log(newValue,oldValue); // [0,'hello'] []
},{immediate:true})
// 情况三: 监听到reactive 定义的一个响应式数据的全部属性,
/*
注意:此时无法获取 oldValue
注意:强制开始起 deep:true 无法关闭
*/
watch(person,(newValue,oldValue)=>{
console.log(newValue,oldValue); // newValue 和 oldValue 是一个东西
},{immediate:true})
// 情况四: 监听到reactive 定义的一个响应式数据的一个 简单属性,
/*
注意:此时无法获取 oldValue
注意:强制开始起 deep:true 无法关闭
*/
watch(()=> person.name ,(newValue,oldValue)=>{
console.log(newValue,oldValue); //
},{immediate:true})
// 情况五: 监听到reactive 定义的一个响应式数据的多个 简单属性,
/*
注意:此时无法获取 oldValue
注意:强制开始起 deep:true 无法关闭
*/
watch([()=> person.name,()=> person.age] ,(newValue,oldValue)=>{
console.log(newValue,oldValue); //
},{immediate:true})
// 特殊情况: 监听到reactive 定义的一个响应式数据的一个 复杂数据类型 属性,
/*
注意:此时无法获取 oldValue
注意:deep 没开启 就无法检测到
*/
watch(()=> person.job ,(newValue,oldValue)=>{
console.log(newValue,oldValue); //
},{immediate:true,deep:true})
return {
person,
sum,
msg
}
}
4.7 watchEffect 监听
- 智能监听:不指定监视那个数据,回调中用到了哪个变量,就监听了哪些,默认开启了
immediate
import { setup ,ref,reactive ,watchEffect } from 'vue'
...
setup(){
// 响应式数据
let person = reactive({
name:'zs',
age:18,
job:{
j1:{
salary:20
}
}
})
let sum = ref(0)
let msg = ref('hello')
// watchEffect 不指定监视那个数据,回调中用到了哪个变量,就监听了哪些,默认开启了 immediate
watchEffect(()=>{
const x1 = sum.value
const x2 =
console.log('data change'); // 0 undefined
})
return {
person,
sum,
msg
}
}
4.8 生命周期钩子函数
- vue3 变更的钩子,其他的都一样
beforeDestroy=>beforeUnmounteddestroy=>unmounted
- 支持 vue2 写法 (与 setup 平级)
- 支持 组合式API 写法 (在setup内部)
- 名称变更
beforeCreate=>setupcreated=>setupbeforeMount=>onBeforeMountmounted=>onMountedbeforeUpdate=>onBeforeUpdateupdated=>onUpdatedbeforeUnmount=>onBeforeUnmountunmounted=>onUnmounted
- 组合式API 没有
beforeCreate与created了 ,由setup代替了
- 名称变更
- 支持 vue2 写法 和 组合API 写法 ,两种钩子函数有顺序(不建议混用)
4.9 自定义 hook 函数
hook: 本质是一个函数,把setup中的Composition API进行了封装- 类似 vue2 中的
mixin - 自定义
hook可以使setup中的逻辑更加清晰