1. vue2.0与vue3.0响应式原理对比
1. vue2.0响应式原理
vue2.0中使用ES5中的Object.defineProperty方法实现响应式数据
缺点:
- 无法监听到对象属性的动态添加和删除
- 无法监听数组的下标和length属性的变更
解决方案:
- vue2.0提供vue.set方法用于动态给对象添加属性
- vue2.0提供vue.delete方法动态的删除对象的属性
- 重写vue中数组的方法,用于检测数组的变更
1.1 Object.defineProperty
const data = {
name: 'ly',
age: 18
}
for (let k in data) {
let temp = data[k]
Object.defineProperty(data, k, {
get() {
console.log(`我劫持了${k}的获取`)
return temp
},
set(value) {
console.log(`我劫持了${k}的设置,值${value}`)
temp = value
}
})
}
在控制台中访问
1. data.name
我劫持了name的获取
"ly"
2. 修改:
data.name = ‘ls’
我劫持了name的设置,值ls
"ls"
缺点举例:
无法监视到新增的属性的变更和删除
- 当修改data中修改一个不存在的属性:
data.gender = '男'
“男”
这个时候并没有做数据的劫持
- 当删除data中的属性时:
delete data.name
true
虽然删掉了但是也没有进行劫持
1.2. vue2中响应式数据
vue中需要使用$set方法动态的增加一个响应式属性
vue中需要使用$delete方法动态的删除对象的属性
// price在data中没有定义
<p>{{car.brand}}----{{car.color}}-----{{car.price}}</p>
data: {
car: {
brand: '奔驰',
color: 'blue'
}
}
演示:
1. vue中需要使用$set方法动态的增加一个响应式属性
原来页面展示:奔驰----blue-----
vm.car.brand = '宝马'
修改后页面展示:宝马----blue-----
这个时候页面展示的奔驰会改成宝马
vm.car.price = '100'
修改后页面展示:宝马----blue-----
这个时候price并没有添加到页面
解决方法:
vm.$set(vm.car, 'price', 100)
修改后页面展示: 宝马----blue-----100
2. vue中需要使用$delete方法动态的删除对象的属性
原来页面展示:奔驰----blue-----
delete vm.car.brand
修改后页面展示:奔驰----blue-----
这个时候页面并没有更新删除掉brand
解决方法:
vm.$delete(vm.car, 'brand')
修改后页面展示: ----blue-----
2. vue3.0响应式原理
vue3.0中使用ES6中的proxy语法实现响应式数据
优点
- 可以检测到代理对象属性的动态添加和删除
- 可以检测到数组的下标和length属性的变更
缺点
- ES6的proxy语法对于低版本浏览器不支持IE11
- vue3.0会针对IE11出一个特殊的版本用于支持IE11
2.1. es6中proxy语法
const data = {
name: 'ly',
age: 18
}
const proxyData = new Proxy(data, {
get(target, name) {
console.log(`检测到${name}的获取`)
return target[name]
},
set(target, name, value) {
console.log(`检测到${name}的设置`,值为${value})
target[name] = value
},
deleteProperty(target, key) {
console.log(`监测到删除${key}`)
return delete target[key]
}
})
演示:(都会拦截到)
proxyData.name
检测到name的获取
"ly"
proxyData.name = 'ww'
检测到name的设置,值为ww
“ww”
delete proxyData.name
监测到删除name
true
2.2. vue3中响应式数据
直接新增和删除就可以了,不用delete。
// price在data中没有定义
<p>{{car.brand}}----{{car.color}}-----{{car.price}}</p>
data: {
car: {
brand: '奔驰',
color: 'blue'
}
}
演示:
原来页面展示:奔驰----blue-----
vm.car.price = 100
修改后页面展示:奔驰----blue-----100
delete vm.car.brand
修改后页面展示:----blue-----100
2. 初始化项目
如果想把自己2.0项目升级为3.0项目通过该命令升级即可: vue add vue-next
2.1 使用vue-cli创建vue3.0项目
- 安装vue-cli3
npm install -g @vue/cli
- 创建项目
vue create my-project
- 在项目中安装composition-api体验vue3新特性
npm install @vue/composition-api --save
- 在使用任何@vue/compositon-api提供的能力前,必须先通过Vue.use()进行安装
import Vue from 'vue'
import VueCompositionApi from '@vue/composition-api'
Vue.use(VueCompositionApi)
2.2 使用vite创建vue3.0项目
-
Vite是一个由原生ESM驱动的Web开发构建工具,在开发环境下基于浏览器原生ESimports开发,在生产环境下基于Rollup打包。
-
Vite目前仅支持vue3.X,不兼容vue2.X项目。
-
Vite基本使用
npm init vite-app 项目名称
cd 项目名称
npm install
npm run dev
- 扩展阅读: 有了vite,还需要webpack吗?
3. Composition API的使用
3.1 Composition API对比 options API
options API:
- 优点是容易学习和使用,代码有明确的书写位置。
- 缺点就是相似的逻辑不容易复用,在大项目尤为明显。
- 可以通过mixins提取相同的逻辑,但是容易发生命名冲突且来源不清晰。
Composition API:
- Composition API是根据逻辑功能来组织代码的,一个功能所有的api放到一起 。
- 即便项目很大,功能很多,能够快速的定位到该功能所有的api。
- Composition API提供了代码可读性和可维护性。
Vue3.0中推荐使用Composition API,也保留了options API。
options API:
Composition API:
3.2 setup的使用
- setup函数是一个新的组件选项,作为组件Composition API的起点
- 从生命周期钩子的角度来看,setup会在beforeCreate钩子函数之前执行
- setup中不能使用this,this的指向undefined
3.3 reactive的使用
reactive函数接受一个普通对象,返回该对象的响应式代理。
- setup中无返回的数据,这时就会报错。
举例说明:
<p>{{car.brand}}</p>
setup() {
// 定义数据
const car = {
brand: '宝马',
price: 1000
}
}
- 所以,setup中需要返回值,setup中返回的值才能在模版中使用。但是这时的数据并不是响应式的。
举例说明:
<p>{{car.brand}}</p>
setup() {
// 定义数据
const car = {
brand: '宝马',
price: 1000
}
return {car}
}
- reactive中传入一个普通对象,返回一个代理对象。
import {reactive} from 'vue'
setup() {
// 定义数据
const car = reactive({
brand: '宝马',
price: 1000
})
return {car}
}
这时的数据就是响应式的。
3.4 ref的使用
-
ref函数接受一个简单类型的值,返回一个可改变的ref对象。返回的对象有唯一的属性value.
-
在setup函数中,通过ref对象的value属性可以访问到值。
-
在模版中,ref属性会自动解套,不需要额外的 .value。
-
如果ref接受的是一个对象,会自动调用reactive。
模板中:
// 在模板中使用ref,会自动解套,会自动调用value。
<div>我的金钱{{money}}</div>
import {ref} from 'vue'
setup() {
// 定义数据
// ref函数接受一个简单类型,返回一个响应式的对象
let money = ref(100)
// 这个响应式对象只有一个属性value
// 通过ref接受简单的数据类型之后,返回新的值其实是一个对象。在下面中如果想使用需要.value访问。但是在模版中就不用.value
money.value++
return {money}
}
3.5 toRefs的使用
-
把一个响应时对象转换成普通对象, 该对象的每一个属性都是一个ref
-
Reactive 的响应式功能是赋予给对象的,但是如果对象解构或者展开的时候,会让数据丢失响应式的能力
-
使用toRefs可以保证该对象展开的每一个属性都是响应式的
1. 数据都是分开写的,零散
setup() {
const money = ref(100)
const car = reactive({
brand: '宝马',
price: 100000
})
const XXXX
return {money, car,.....}
}
这样会有个问题: 数据越来越多
2. 将数据都放在state中
setup() {
const state = reactive({
money: 100,
car: {
brand: '宝马',
price: 100000
}
})
return {...state}
}
这样会有个问题: 对象解构或者展开的时候,会让数据丢失响应式的能力
这时,money就丢失响应式的能力,car没有丢失,因为它还是一个对象。
如果再继续对car进行结构,那么car也丢失响应式的能力
3. 使用toRefs解决响应式能力丢失的问题
import {toRefs} from 'vue'
setup() {
const state = reactive({
money: 100,
car: {
brand: '宝马',
price: 100000
}
})
return {
...toRefs(state)
}
}
使用toRefs传入对象,再解构,这时数据就是响应式的
3.6 readonly的使用
- 传入一个对象(响应式或普通)或ref,返回一个原始对象的只读代理
- 一个只读的代理是“深层的”,对内部任何嵌套的属性也都是只读的
- 可以防止对象被修改
模板:
<div>我的钱:{{money}}</div>
<div>{{car.brand}}----{{car.price}}</div>
<button @click=“money++”>按钮</button>
<button @click=“car.price = 200”>按钮</button>
setup() {
const money = ref(100)
const car = readonly({
brand: '宝马',
price: 100000
})
return {
money:readonly(money)
car
}
}
这个时候执行click事件,就是不能修改的money的值的。也不能修改price的值
3.7 computed计算属性的使用
1. 概述
- computed函数用于创建一个计算属性
- 如果传入的是一个getter函数,会返回一个不允许修改的计算属性
- 如果传入的是一个带有getter和setter函数的对象,会返回一个允许修改的计算属性
2. 实现计算属性的方式
- 第一种实现计算属性的方式: 传入一个函数getter返回一个不允许修改的计算属性
setup() {
const age = ref(18)
const nextAge = computed(() => {
return parseInt(age.value) + 1
})
return {
age,
nextAge
}
}
不允许修改nextAge
- 第二种实现计算属性的方式(可以获取、修改): 传入一个对象,包括get和set,可以创建一个可以修改的计算属性
setup() {
const nextAge2 = computed({
get() {
reutrn parseInt(age.value) + 2
}
set(value) {
// value 就是修改的值
age.value = value -2
}
})
return {
nextAge2
}
}
3.8 watch监听属性的使用
1. 概述
-
watch函数接受3个参数。
- 参数1 : 数据源,可以是ref或者getter函数
- 参数2 : 回调函数
- 参数3 : 额外选项,{deep: true, immediate: true}
-
watch可以监听一个ref或者一个带有返回值的getter函数
-
watch可以监听单个数据源,也可以监听多个数据源
-
watch函数会有返回值,用于停止监听
2. 监听属性的使用
- 想要监听money的错误演示
模板:
<div>我的钱:{{money}}</div>
<div>{{car.brand}}----{{car.price}}</div>
<button @click=“money++”>按钮</button>
<button @click=“car.price = 200”>按钮</button>
setup() {
const state = reactive({
money: 100,
car: {
brand: '宝马',
price: 100000
}
})
watch(state.money, (value, oldValue) => {
console.log('money变化了', value, oldValue)
})
return {
...toRefs(state)
}
}
这样写会警告⚠️:可以监听一个ref或者一个带有返回值的getter函数
正确写法:() => state.money
watch(() => state.money, (value, oldValue) => {
console.log('money变化了', value, oldValue)
})
- 如果money是放在外面的,用到了ref。watch的写法
setup() {
const money = ref(100)
watch(money, (value, oldValue) => {
console.log('money变化了', value, oldValue)
})
return {
money
}
}
- 监听复杂数据类型 {deep: true}
setup() {
const state = reactive({
car: {
brand: '宝马',
price: 100000
}
})
watch(() => state.car, (value, oldValue) => {
console.log('车变化了', value, oldValue)
}, {deep: true, immediate: true})
return {
...toRefs(state)
}
}
- 监听多个值
setup() {
const state = reactive({
money: 100,
car: {
brand: '宝马',
price: 100000
}
})
watch([() => state.money, () => state.car], ([newMoney,newCar], [oldMoney,oldCar]) => {
console.log('数据变化了', newMoney, newCar, oldMoney, oldCar)
})
return {
...toRefs(state)
}
}
- 直接监听state
setup() {
const state = reactive({
money: 100,
car: {
brand: '宝马',
price: 100000
}
})
watch(state, (value) => {
console.log('数据变化了', value)
},{deep: true, immediate: true})
return {
...toRefs(state)
}
}
3.9 生命周期钩子函数
- vue3提供的生命周期钩子函数注册函数只能在setup()期间同步使用
- vue3生命周期钩子函数与vue2对比 beforeCreate ==> setup()
created ==> setup()
beforeMount ==> onBeforeMount
Mounted ==> onMounted
beforeUpdate ==> onBeforeUpdate
updated ==> onUpdated
beforeDestroy ==> onBeforeUnmount
destroyed ==> onUnmount
errorCaptured ==> onErrorCaptured
3.10 provide和inject使用
- vue3提供了provide和inject提供依赖注入,用于实现组件之间的通讯
- vue3提供的provide和inject可以用于跨多级组件惊醒通讯
父传子:
父组件
将money传递给子组件
模板:
<h1>我的钱:{{money}}</h1>
子组件
<Demo></Demo>
import {provide} from 'vue'
setup() {
const money = ref(100)
// 父组件向子组件传递数据
provide('money', money)
return {
money
}
}
子组件
接受传递过来的money
import {inject} from 'vue'
const money = inject('money')
子传父:
父组件:
setup() {
const money = ref(100)
// 定义事件传递给子组件
const changeMoney = (m) => {
money.value = m
}
provide('changeMoney', changeMoney)
return {
money
}
}
模板:
<button @click=“fn”>按钮</button>
子组件:
setup() {
const changeMoney = inject('changeMoney')
const fn = () => {
// 调用父组件的事件,并传值
changeMoney(200)
}
return {
fn
}
}
说明:依旧可以用props传值,但是子组件有点区别
父组件
<Demo :money="money"></Demo>
子组件: 得用props参数接受(setup里面没有props)
setup(props) {
console.log(props)
}
props: {
money: Number
}
3.11 模板ref使用
- 为了获得对模板内元素或组件实例的引用,我们可以像往常一样在setup()中声明一个ref并返回它
模板:
// 绑定ref
<h1 ref="hRef"></h1>
setup() {
// 创建了一个空的ref
const hRef = ref(null)
onMounted() {
console.log(hRef.value)
}
return {
hRef
}
}