简述
最近把vue网课看完了,vue2,vue3大体过了一遍,目前准备这几天水两篇博客,整理笔记,然后学习cloud
不过学习cloud之前,我觉得还是应该过一下后端的内容,学vue学了两个月,很多东西忘记了,不过记了不少
笔记,回顾起来没有太大困难,下面介绍下本篇的具体内容:
vue响应式原理
在基础原理上篇我们讲过,vue实现响应式原理,主要解决两个问题:
- 如何拦截到数据的变化,即数据劫持的实现
在上篇中,我们知道vue2实现数据劫持就是通过Object.defineProperty方法实现 - 当数据发生改变时,如何让使用该数据的页面发生改变?
发布订阅者模式
下面主要讲讲vue中发布订阅者模式,也就是说页面如何跟着数据改变而改变,以下是个人理解,不妥之处还望见谅
//先来一个简单的案例,仅仅只展示姓名,年龄
<template>
<div id="app">
{{name}}
{{name}}
{{name}}
{{age}}
{{age}}
</div>
</template>
<script>
export default {
name: 'App',
data(){
return {
name: '万一',
age: 23
}
}
}
</script>
如上,当我们data中存在name,age数据时,vue2底层会使用Object.defineProperty方法,对这些数据进行
数据劫持,也就是说,当我们访问name,修改name,都能劫持到操作,进入到name的get,set方法中去
这些经过”加工”的数据被称为响应式数据,因为这些数据都有对应的get,set方法,vue底层将这些响应式数据
保存到一个Observer观察者对象中,然后根据每个响应式数据,生成对应的Dep对象,生成name的Dep对象和age的Dep对象,
当页面中使用到了name,例如{{name}}
,就会执行name的get方法,进入get方法后
生成一个Watcher对象,每使用一个name就会生成一个Watcher对象,像上面例子中,name会生成3个name的Watcher对象
并且,这三个Watcher对象会被放到name对应的Dep对象的数组中保存起来,这是在name的get方法发生的事情,同理age也是如此。
而当我们修改数据时,例如修改name数据后,会进入到name的set方法中,会调用name对应的Dep对象
遍历其中每一个Watcher对象,调用watcher对象中的update方法,页面中使用到的name数据的地方就会发生改变。
以上就是发布订阅者模式:watcher对象就是订阅者,对应的Dep对象就是发布者,当数据更新,发布者通知所有
订阅该数据的watcher对象进行页面更新。
下面是来自网课中响应式原理的图片:
vue3中数据代理/数据劫持
在vue3中,如果想让数据是响应式的,需要使用ref函数,reactive函数,ref函数可以将普通数据,对象数据包装成响应式的
而reactive函数则只能将对象数据包装成响应式,当然,其实ref函数对于对象数据的处理也是使用reactive函数。
vue3中,对于基本类型使用Object.defineProperty方法实现响应式数据,ref函数内部对于基本数据就是如此,
对于对象类型数据,使用ES6的Proxy对象实现
下面来看看ref函数和reactive的使用:
ref函数的使用
ref案例之处理基本类型
<template> 我的名字:{{name}}<br/> 我的年纪:{{age}}<br/> <button @click="changeName">更改姓名</button> </template> <script> import {ref} from 'vue' export default { name: 'App', setup(){ let name = ref("夜归风似雪") //使用ref函数创建的对象是ref对象,这里的数据实现了响应式 let age = ref(23) function changeName() { name.value="晚来天欲雪" //通过ref函数创建的响应式数据,使用时需要加上.value才是真正数据 } return { name, age, changeName } } } </script>
ref案例之处理对象类型
<template> person信息:{{person.sex}} <button @click="changeName">更改性别</button> </template> <script> import {ref} from 'vue' export default { name: 'App', setup(){ let person = ref({color: 'yellow',sex: 'boy'}) function changeName() { person.value.sex='girl' } return { changeName, person } } } </script>
总结
经过ref函数调用,生成的name,age,person都是refImpl引用对象,数据都保存在refImpl对象的value属性中
如果是普通数据类型,value属性保存的就是定义时的数据,例如”夜归风似雪”,23等等, 这些数据使用
Object.defineProperty实现响应式的(有setter,getter方法)如果是对象类型,则value属性中保存的是proxy对象
如果想修改对象类型中的数据:person.value.sex="girl"
, 对象类型的数据,内部使用vue3中的reactive函数实现响应式
reactive函数的使用
<template> 性别:{{person.sex}} <button @click="changeSex">更改性别</button> </template> <script> import {reactive} from 'vue' export default { name: 'App', setup(){ let person = reactive({color: 'yellow',sex: 'boy'}) function changeSex() { person.sex='girl' //访问,修改由reactive包装的响应式对象,不需要使用.value } return { changeSex, person } } } </script>
作用:定义一个对象类型的响应式数据(基本类型不要用它,要用ref函数)
语法:const 代理对象 = reactive(源对象),接收一个对象(或数组),返回一个代理对象(Proxy实例对象,简称proxy对象)
reactive定义的响应式数据是”深层次的”,reactive函数内部基于ES6的Proxy实现,通过代理对象操作源对象
内部数据进行操作,ref函数之所以能对对象类型数据进行响应式,底层也是通过reactive函数
既然reactive函数内部基于ES6的Proxy实现,那么我们来看看proxy是如何使得数据是响应式的吧
通过Proxy(代理),我们可以拦截对象中任意属性的变化,即对属性增删改查操作都能够拦截,还记得以前
通过Object.defineProperty方法只有get,set方法。
下面是一个使用Proxy实现数据响应式的案例
/*
数据代理和数据拦截本质上差不多,非要区分的话,下面是个人理解:
数据代理指的是,如果A代理B,对A的操作就会使得B更改,
而响应式原理指的是,在A对B更改的过程中可以做点事,即劫持到对数据的更改操作
*/
//源对象
let person = {
name: '万一',
age: 23
}
/*代理对象,通过对代理对象增删改查,已经实现了数据代理,但没实现响应式(数据劫持),即我不能拦截到对数据的增删改查操作*/
//let p = new Proxy(person,{});
/*做到数据劫持*/
let p = new Proxy(person,{
get(target,propName){
console.log("已经成功劫持到对数据读取操作")
//return target[propName]
return Reflect.get(target,propName) //vue3中使用Reflect对属性进行真正操作,好处是:使得框架不必写太多try,catch
},
set(target,propName,value){ //target是源对象,propName是拦截到的属性,value是接收的新值
console.log("已经成功劫持到对数据修改/添加操作")
//return target[propName]=value
return Reflect.set(target,propName,value)
},
deleteProperty(target,propName) {
console.log("已经成功劫持到对数据删除操作")
//return delete target[propName]
return Reflect.deleteProperty(target,propName)
}
});
vue3中生命周期函数
vue3中的生命周期函数与vue2中的有一点小小的变化
将vue2中的beforeDestroy,destroyed函数名改成beforeUnmount,unmounted
由通过配置属性的方式,改成composition API的方式,不过原来vue2通过属性配置生命周期函数的方式
在vue3中也能使用,Vue3.0也提供了 Composition API 形式的生命周期钩子,与Vue2.x中钩子对应关系如下:beforeCreate===>setup() created=======>setup() beforeMount ===>onBeforeMount mounted=======>onMounted beforeUpdate===>onBeforeUpdate pdated =======>onUpdated beforeUnmount ==>onBeforeUnmount unmounted =====>onUnmounted 注意:使用composition API的方式没有beforeCreate,created这两个钩子 如果你想要使用,你可以通过配置项方式使用,另外setup钩子比beforeCreate钩子更早执行。
setup函数的学习
setup函数是所有Composition API(组合API)表演的舞台,我们可以在该函数中·,定义数据
创建函数
setup函数的两种返回值:
- 若返回一个对象,则对象中的属性、方法, 在模板中均可以直接使用。
- 若返回一个渲染函数:则可以自定义渲染内容。
注意点:
- 尽量不要与Vue2.x配置混用
- Vue2.x配置(data、methods、computed…)中可以访问到setup中的属性、方法。
- 但在setup中不能访问到Vue2.x配置(data、methods、computed…)。
- 如果有重名, setup优先。
- setup不能是一个async函数,因为返回值不再是return的对象,而是promise,模板看不到return对象中的属性。
(后期也可以返回一个Promise实例,但需要Suspense和异步组件的配合) - setup函数中this是undefined,因为setup函数在beforeCreate函数前面执行,此时组件中的data和methods还没有初始化。
setup的参数:
- props:值为对象,包含:组件外部传递过来(组件标签上传递的数据),且组件内部声明接收了的属性(使用配置项prop接收的属性)
- context:上下文对象
- attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于
this.$attrs
。 - slots: 收到的插槽内容, 相当于
this.$slots
。 - emit: 分发自定义事件的函数, 相当于
this.$emit
。
- attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于
toRef函数
作用:创建一个 ref 对象,其value值指向另一个对象中的某个属性。简单点说就是创建一个响应式数据,指向某个对象中的属性。
语法:const name = toRef(person,'name')
应用: 要将响应式对象中的某个属性单独提供给外部使用时。
扩展:toRefs
与toRef
功能一致,但可以批量创建多个 ref 对象,语法:toRefs(person)
toRefs专业定义: toRefs()函数可以将reactive()创建出来的响应式对象,转换为普通对象,
只不过这个对象上的每个属性节点,都是ref()类型的响应式数据
toRef,toRefs的使用:
<template>
person信息:{{sex}}
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
import {ref,reactive,toRef,toRefs} from 'vue'
export default {
name: 'App',
components: {
HelloWorld
},
setup(){
let person = reactive({color: 'yellow',sex: 'boy'})
console.log(toRef(person,'color')) //ObjectRefImpl {_object: Proxy, _key: 'color', __v_isRef: true}
console.log(toRefs(person)); //{color: ObjectRefImpl, sex: ObjectRefImpl}
return {
...toRefs(person) //...是扩展运算符,将对象中的属性加到另一个对象中,解构赋值
}
}
}
</script>
/*
不要对一个响应式数据进行解构,会破坏响应式,但上面...refs(person)中refs(person)返回的普通对象
*/