看到标题, 小伙伴们是不是很惊讶? 也许会联想到 Reactivity 是不是设计上有缺陷? 其实是醉翁之意不在酒! Reactivity 可以说是 vue3 中最复杂的一个地方, 当然也是功能最强大的一个点, 听起来是块硬骨头哈, 这让我想起亮剑中李云龙面对小日本挑衅说的话: 就是阎王爷来了, 我也得搂他几根胡子下来. 那么今天咱们也搂搂 Reactivity 的胡子.
在 vue3 当中, 它是把数据响应式 API 全都暴露出来了, 在 vue2 中是没有这样做的, 那 vue2 是怎么做的呢? 它是将数据配置到 data 当中, data 中的数据会自动变成响应式数据, 我们把这个过程叫做注入, 它会注入到组件实例当中去, 如下:
- {
- //data 中的数据都是响应式的, 会被注入到组件实例当中
- data(){
- return{
- name:"法医",
- idol:"乔布斯",
- publicNumber:"前端猎手"
- }
- }
- }
然而在 vue3 中, 它不再是 data 了, 而是 setup 函数, 我们把它称之为 composition API, 我们要在 setup 函数中使用响应式数据, 就不可避免需要暴露出响应式的 API 供我们使用, 接下来, 我们来看看 vue3 提供了哪些跟数据响应式相关的函数.
获取响应式数据
可以获取响应式数据的 API:
reactive
这个 API 会传入一个对象, 然后返回一个对象代理 proxy, 并且它是可以深度代理对象中的所有成员的, 也就是说对象里面再嵌套一个对象, 也是响应式的数据
举个栗子:
- import {
- reactive
- } from 'vue'
- const state = reactive({
- name:"法医",age:18
- })
- Windows.state = state;
效果展示:
现在这个对象就变成响应式的了, 对对象进行操作, vue 就可以收到通知了
readonly
这个 API 需要传入一个对象或者是一个代理, 同样也会返回一个对象代理, 它只能读取代理对象中的成员, 而不能修改, 也就是只能 get, 不可以 set, 它也是可以深度代理对象中所有成员的.
举个栗子:
- import {
- reactive,readonly
- } from 'vue'
- const imState = readonly({
- name:"法医",age:18
- })
- Windows.imState = imState;
效果展示:
看见没, 报了一个错误, 提示说要修改的目标是只读的, 修改失败
刚才有说 readonly 可以传一个对象, 也可以传一个代理, 我们再看看传一个代理会怎么样
- import {
- reactive,readonly
- } from 'vue'
- const state = reactive({
- name:"法医",age:18
- })
- const imState = readonly(state);// 传一个代理进去
- Windows.imState = imState;
效果展示:
传一个代理的时候也是不可以进行赋值操作的, 反正只要经过 readonly 后就只能读不能赋值, 但是, 我们可以在 readonly 之前修改值, 然后让它进行代理就可以了.
注意: reactive 和 readonly, 这两个 API 是代理对象的, 是没办法代理普通值的, 会报错
举个栗子:
- import {
- reactive,readonly
- } from 'vue'
- const state = reactive("法医")
效果展示:
![image.PNG](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/54cc2bca665c491cb14ede3e3c3919e1~tplv-k3u1fbpfcp-watermark.image)
报了一个警告, 原始值是没法代理的, 那么如果需要代理普通值, 那该咋办呢? 那就要用到 `ref`API 了
ref
ref 中可以传入任何数据, 最终会将数据放到一个对象中的 value 中, 比如这种{ value : ...}, 对 value 的访问是响应式的, 如果给 value 的值是一个对象的话, 它会通过 reactive 函数进行代理
举个栗子:
- import {
- reactive,readonly,ref
- } from 'vue'
- const state = ref({
- name:"法医",age:18
- })
- Windows.state = state;
效果展示:
![image.PNG](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/41013bd555294aa3b53adc1c6602998e~tplv-k3u1fbpfcp-watermark.image)
还有一种情况是: 如果说传入的 value 值已经是代理了, 那么会直接使用代理
** 举个栗子:**
- ```JS
- import { reactive,readonly,ref } from 'vue'
- const state = reactive({name:"法医",age:18});
- const imState = ref(state)
- Windows.imState = imState;
- ```
** 效果展示:**
- ![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9becafd7f0ab41c7b5d4f69c63a26d94~tplv-k3u1fbpfcp-watermark.image)
- computed
computed 需要传入一个函数 function, 它返回的值跟 ref 一样, 也是{value:...}, 当读取 value 值的时候, 它会根据情况决定是否要运行函数, 这个情况就是有没有用到这个函数, 并且它是有缓存的, 当依赖的响应式数据没有变化的时候, 拿到的是缓存里面的值, 只有当 state.name 或者 state.age 发生改变时才会重新运行.
举个栗子:
- import { reactive,readonly,ref ,computed} from 'vue'
- const state = reactive({name:"法医",age:18})
- const result = computed(()=>{
- console.log("computed");
- return state.name + state.age
- })
- console.log(result.value);
效果展示:
![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d0f14b8654a94861a9100de3088923b6~tplv-k3u1fbpfcp-watermark.image)
反正以上四个 API 不管它怎么进行处理, 就一个目的, 那就是把数据变成响应式数据.
那么在开发中到底用哪个?
1. 如果想要一个对象变为响应式数据, 可以使用 `reactive` 或者 `ref`
2. 如果说让对象所有属性只能读, 就用 `readonly`
3. 如果想让一个非对象数据变成响应式数据, 就用 `ref`
4. 如果想要根据已知的响应式数据得到一个新的响应式数据, 就用 `computed`
注意: 用 `ref` 的时候要拿到数据必须是 `ref.value` 哈, 不然拿不到, 切记!
监听数据变化
- watchEffect
- const stop = watchEffect(() => {
- //watchEffect 函数会立即执行, 然后监听函数中会用到的响应式数据, 响应式数据变化后会再次执行
- })
- // 通过调用 stop 函数就会停止监听
- stop();// 停止监听
举个栗子:
- import { reactive, ref, watchEffect } from "vue";
- const state = reactive({a: 1,b: 2,});
- const count = ref(0);
- watchEffect(() =>{
- console.log(state.a,count.value);// 先会立即执行一次
- })
- state.a++;// 这里依赖数据改变再次执行一次
效果展示:
watchEffect 函数之所以知道依赖变化了, 是因为里面的数据是响应式的, 当读取数据的时候用的是 get 方法, get 会收集依赖. 还要注意的是如果说依赖的数据同时改变很多次, 最终结果是会显示一次, 因为运行过程是异步的, 是会到微队列中执行的, 等数据变完之后才会运行, 如下例子:
举个栗子:
- import { reactive, ref, watchEffect } from "vue";
- const state = reactive({a: 1,b: 2,});
- const count = ref(0);
- watchEffect(() =>{
- console.log(state.a,count.value);
- })
- // 运行多次
- state.a++;
- state.a++;
- state.a++;
- state.a++;
- state.a++;
- state.a++;
- state.a++;
- state.a++;
效果展示:
从最终结果可以看出最终只运行两次, 一次是立即执行, 第二次是数据改变后
watch
这个 watch 相当于 vue2 的 $watch, 这个 watch 有点麻烦, 因为它需要手动去指定监控哪些值的变化, 当变化的时候, 它会把新的值和旧的值同时给你
举个栗子:
- import { reactive, ref, watch } from "vue";
- const state = reactive({a: 1,b: 2,});
- const count = ref(0);
- watch(()=>state.a,(newValue,oldValue) =>{
- console.log("新值",newValue,"旧值",oldValue);
- })
- state.a++;// 修改依赖
效果展示:
注意: 在这里需要注意的是 watch 跟 watchEffect 不同的是 watch 不会立即运行函数, 只有当依赖的值改变时才会执行, watch 中会传两个参数, 在上面例子中传的是()=>state.a, 那为什么不直接传 state.a 呢? 如果直接传 state.a 的话, 那么相当于传了一个 1 进去, 这样数据就不是响应式的了, 如下:
举个栗子: 直接传 state.a
- import { reactive, ref, watch } from "vue";
- const state = reactive({a: 1,b: 2,});
- const count = ref(0);
- watch(state.a,(newValue,oldValue) =>{
- console.log("新值",newValue,"旧值",oldValue);
- })
- state.a++;// 修改依赖
效果展示:
如果直接传 state.a, 这里就会报警告, 翻译过来是: 无效的监视源: 1 监视源只能是 getter/effect 函数, ref, 被动对象或这些类型的数组, 意思就是说这里参数只能是响应式数据.
当传一个()=>state.a 函数进去, 它是在 watch 里面运行的, 这样就会收集依赖. 当使用 reactiveapi 的时候就要传一个这样函数进去, 当使用 refapi 的时候, watch 中第一个参数可以写成 count, 因为 count 是一个对象, 如下:
举个栗子: 直接传 count
- import { reactive, ref, watch } from "vue";
- const state = reactive({a: 1,b: 2,});
- const count = ref(0);
- watch(count,(newValue,oldValue) =>{
- console.log("新值",newValue,"旧值",oldValue);
- })
- count.value++;// 修改依赖
效果展示:
如果说传了一个 count.value 进去, 那么也会报错, 因为 count.value 拿到的也是属性值了, 如下:
举个栗子: 直接传 count
- import { reactive, ref, watch } from "vue";
- const state = reactive({a: 1,b: 2,});
- const count = ref(0);
- watch(count.value,(newValue,oldValue) =>{
- console.log("新值",newValue,"旧值",oldValue);
- })
- count.value++;// 修改依赖
效果展示:
从运行结果看, 也报了一个警告, 所以说这块得注意下
对了, 差点忘了, watch 是可以监控多个数据的, 如下:
举个栗子: 传 count 和()=>state.a
- import { reactive, ref, watch } from "vue";
- const state = reactive({a: 1,b: 2,});
- const count = ref(0);
- watch([()=>state.a,count],([newValue1,newValue2],[oldValue1,oldValue2]) =>{
- console.log("新值",newValue1,newValue2,"旧值",oldValue1,oldValue2);
- })
- count.value++;
- state.a++;
效果展示:
注意: 无论是 watch 还是 watchEffect, 当依赖变化时, 回调函数都是异步执行的, 当然也会到微队列等待执行.
总得来说 watchEffect 是最方便的, 因为它会自动跟踪依赖的变化, 不需要手动指定, 但是有时候却不得不使用 watch, 比如说: 我们不希望回调函数一开始就执行, 只想让它当数据改变的时候才执行, 这时候就只能用 watch 了, 还有一种就是数据改变时, 我们需要知道旧值是什么? 这时也需要使用 watch, 最后一种就是我们需要监控一些回调函数中用不到的数据, 比方说, 输出 console.log("我要变了"), 这在 watchEffect 中是做不到的
判断
在获取响应式数据的时候有四种 API, 分别是 reactive,readonly,ref,computed, 返回有两种形式, 一个是对象代理, 另一个是 {value:...} 这种形式, 有时候我们可能没睡醒, 大脑混乱, 于是 vue3 提供了 4 种 API 用于区分到底是哪种方式获取的响应式数据:
isProxy : 用于判断某个数据是否是由 reactive 或 readonly 获取的
isReative : 判断某个数据是否是通过 reative 创建的, 具体可以看这个链接:
isReadonly : 判断某个数据是否是通过 readonly 创建的
isRef : 判断某个数据是否是一个 ref 对象
## 转换
有时候我们拿到一个数据, 我们也不知道它是个啥, 可能有时候也顾不上, 到底是 ref 呢, 还是 proxy, 鬼都不知道它是个啥!
这时我们可以用 unref
unref
unref 等同于: isRef(val) ? val.value : val, 如果是 ref 的话那就把 val.value 值拿出来, 如果不是就拿 val 值
举个栗子:
- function useNewTodo(todos){
- todos = unref(todos);
- //... 其它代码
- }
- toRef
toRef 会把一个响应式对象中的某个属性变成 ref 格式的数据
举个栗子:
- import {
- reactive, toRef
- } from "vue";
- const state = reactive({
- name: "法医", age: 18
- });
- const nameRef = toRef(state,"name");
- console.log(nameRef);// 最终是 {
- value:...
- }这种形式
- nameRef.value = "前端猎手";// 当修改这个值后, state 里面的数据也会受到影响
- console.log(state.name);
效果展示:
toRefs
把一个响应式对象的所有属性转换为 ref 格式, 然后包装到 plain-object 普通对象中返回
举个栗子:
- import { reactive, toRefs } from "vue";
- const state = reactive({ name: "法医", age: 18 });
- const stateAsRefs = toRefs(state);
- console.log(stateAsRefs);
- /*
- stateAsRefs 它不是一个代理对象了, 而是一个普通对象, 如下格式:
- {
- name:{value:ObjectRefImpl},
- age:{value:ObjectRefImpl}
- }
- */
效果展示:
为什么要这么做?
举个栗子:
我们需要把两个响应式数据混合在一起, 如果直接使用展开运算符那么就完蛋了, 数据会失去响应式
- setup() {
- const state1 = reactive({a:1,b:2});
- const state2 = reactive({c:3,d:4});
- return {
- ...state1,// 失去响应式, 相当于在这写了一个 a:1,b:2
- ...state2,// 失去响应式, 相当于在这写了一个 c:3,d:4
- };
- },
那么如何解决呢? 外面套一个 toRefs 就好了
- setup() {
- const state1 = reactive({a:1,b:2});
- const state2 = reactive({c:3,d:4});
- return {
- ...toRefs(state1),// 具有响应式
- ...toRefs(state2),// 具有响应式
- };
- },
好了, 以上就是我的分享, 希望能对大家有所帮助, 欢迎大家在评论区讨论鸭~
希望小伙伴们点赞 支持一下哦~ , 我会更有动力的 , 晚安!
来源: https://segmentfault.com/a/1190000040110494