跳至主要內容

reactive 相关

wzCoding大约 5 分钟VueVue3reactive 相关

reactive 相关

reactive 函数是 Vue3 中用于创建响应式数据的方法之一,也是 Vue3 响应式系统的核心功能之一,下面我们将探究 reactive 函数的用法及实现原理。

使用方法

reactive

reactive 函数在 Vue3 中的作用是将非原始值转化为响应式数据,它的用法非常简单,只需要传入一个非原始值作为参数即可。

import { reactive } from 'vue'

const state = reactive({count: 0}) //state便是响应式数据了
const arr = reactive([1, 2, 3]) //arr也是响应式数据了

// 错误用法:不能将原始值转化为响应式数据
const num = reactive(1) //产生警告:value cannot be made reactive: 1

shallowReactive

shallowReactive 函数的作用与 reactive 类似,都是将非原始值转化为响应式数据,但是它与 reactive 不同的是,shallowReactive 只会处理对象的第一层属性,而不会处理对象的深层属性。

import { shallowReactive } from 'vue'

//state的count属性是响应式的,state.info不是响应式的
const state = shallowReactive({count: 0, info: {name: 'John'}})

isReactive

isReactive 函数的作用是判断一个对象是否是响应式数据,它的用法非常简单,只需要传入一个对象作为参数即可。

import { isReactive } from 'vue'

const state = reactive({count: 0})
const arr = reactive([1, 2, 3])

console.log(isReactive(state)) //true
console.log(isReactive(arr)) //true
console.log(isReactive({})) //false

readonly

readonly 函数的作用是将一个对象转化为只读的,它的用法非常简单,只需要传入一个对象作为参数即可。

import { readonly } from 'vue'

const state = reactive({count: 0})
const arr = reactive([1, 2, 3])

const stateReadonly = readonly(state)
const arrReadonly = readonly(arr)

console.log(stateReadonly.count) //0
console.log(arrReadonly[0]) //1

stateReadonly.count = 1 //产生警告:Set operation on key "count" failed: target is readonly.
arrReadonly[0] = 2 //产生警告:Set operation on key "0" failed: target is readonly.

shallowReadonly

shallowReadonly 函数的作用与 isReadonly 类似,都是判断一个对象是否是只读的,但是它与 isReadonly 不同的是,shallowReadonly 只会处理对象的第一层属性,而不会处理对象的深层属性。

import { shallowReadonly } from 'vue'

//state的count属性是只读的,不能进行修改操作,state.info不会被转换成只读的,可以修改
const state = shallowReadonly({count: 0, info: {name: 'John'}})

state.count = 1 //产生警告:Set operation on key "count" failed: target is readonly.
state.info.name = 'Tom' //不会产生警告

isReadonly

isReadonly 函数的作用是判断一个对象是否是只读的,它的用法非常简单,只需要传入一个对象作为参数即可。

import { isReadonly } from 'vue'

const state = reactive({count: 0})
const arr = reactive([1, 2, 3])

console.log(isReadonly(state)) //false
console.log(isReadonly(arr)) //false

实现原理

reactive 函数的实现原理是基于 Proxy 对象,通过 Proxy 可以拦截代理对象的基本操作,从而实现响应式数据。(响应式原理与 Proxy 在 Vue3 响应系统 这一章节有过介绍)

代理对象

reactive 函数中,首先会创建一个 Proxy 对象,用于拦截对象的基本操作。

function reactive(target) {
  return new Proxy(target, { ... })
}

拦截基本操作

reactive 函数在 Proxy 代理对象的配置中,会拦截对代理对象的基本操作,并触发副作用函数执行,它拦截的基本操作有以下几项:

  • 获取属性值(get)
  • 设置属性值(set)
  • 删除属性(deleteProperty)
  • 遍历属性(ownKeys)
  • 判断属性是否存在(has)

为了实现 reactive 函数及其相关函数的功能,Vue3 为代理对象设置了一些额外的标记:

  • __v_isReactive:用于标记代理对象是响应式数据
  • __v_isReadonly:用于标记代理对象是只读的
  • __v_isShallow:用于标记代理对象是浅层的
  • __v_raw:用于标记原始对象

以下是简化版的实现代码:

function createReactive(obj,isShallow = false,isReadonly = false) {
  return new Proxy(obj, {
    // 拦截获取属性值的操作
    get(target, key, receiver) {
      //代理对象可以通过raw属性直接访问原始数据
      if(key === 'raw'){
         return target
      }

      //如果数据不是只读的,就收集相关依赖
      if(!isReadonly){
        track(target, "get", key);
      }

      //通过Reflect方法获取代理对象的属性值
      const res = Reflect.get(target, key, receiver);
      
      //如果数据是浅层响应的,直接返回获取的值
      if(isShallow){
         return res
      }
      //如果获取的值是对象,就递归调用reactive函数将其转化为响应式数据
      if(typeof res === 'object' && res !== null){
        //判断是否只读
        return isReadonly ? readonly(res) : reactive(res)
      }

      return res

    },
    // 拦截设置属性值的操作
    set(target, key, value, receiver) {
      //如果数据是只读的,就会产生警告,无法设置
      if(isReadonly){
        console.warn(`Set operation on key "${key}" failed: target is readonly.`);
        return false
      }

      //获取旧值
      const oldValue = target[key];

      //判断操作类型,是新增属性还是修改属性值,支持数组
      const type = Array.isArray(target) ? 
      //如果原始数据是数组,判断索引值是否小于数组长度
      Number(key) < target.length ? 'set' : 'add' : 
      //如果原始数据不是数组,判断属性是否存在
      Object.prototype.hasOwnProperty.call(target, key) ? 'set' : 'add';

      //通过Reflect方法设置代理对象的属性值
      const res = Reflect.set(target, key, value, receiver);

      //如果设置的值与旧值不同,就触发相关依赖
      if(oldValue !== value){
        trigger(target, key,"set",value);
      }

      return res;
    },

    // 拦截删除属性值的操作
    deleteProperty(target, key) {
      const res = Reflect.set(target, key, value, receiver);
      if (oldValue !== value) {
        trigger(target, "set", key);
      }
      return res;
    },

    // 拦截遍历属性值的操作
    ownKeys(target) {
      track(target, "iterate", isReadonly ? undefined : "value");
      return Reflect.ownKeys(target);
    },

    // 拦截判断属性值是否存在的操作
    has(target, key) {
      track(target, "has", key);
      return Reflect.has(target, key);
    },
  })
}

参考信息

ℹ️提示

  • reactive相关信息

  • track 函数的作用是收集依赖。

  • trigger 在数据发生变化时触发相关依赖的执行。