Vue3 - 浅谈 ref 和 reactive 及 reactive 响应数据问题

204 阅读3分钟

我正在参与掘金创作者训练营第5期,点击了解活动详情

今年来换了新工作,也在之前的Vue2进军到Vue3,日常工作是边写边摸,rookie小白,随笔欢迎指导,向大佬学习,一起奔向自由的前端银儿。

简单介绍

ref

 * 作用: 定义一个数据的响应式
 * 语法: const xxx = ref(initValue):
 *  - 创建一个包含响应式数据的引用(reference)对象
 *  - js中操作数据: xxx.value
 *  - 模板中操作数据: 不需要.value
 * 一般用来定义一个『基本类型』的响应式数据

reactive

 * 作用: 定义多个数据的响应式
 * 语法:const proxy = reactive(obj)
 *  - 接收一个普通对象然后返回该普通对象的响应式代理器对象
 *  - 响应式转换是“深层的”:会影响对象内部所有嵌套的属性
 *  - 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据都是响应式的

ref与reactive-细节

  • 是Vue3的 composition API中2个最重要的响应式API
  • ref用来处理基本类型数据, reactive用来处理对象(递归深度响应式)
  • 如果用ref对象/数组, 内部会自动将对象/数组转换为reactive的代理对象
  • ref内部: 通过给value属性添加getter/setter来实现对数据的劫持
  • reactive内部: 通过使用Proxy来实现对对象内部所有数据的劫持, 并通过Reflect操作对象内部数据
  • ref的数据操作: 在js中要.value, 在模板中不需要(内部解析模板时会自动添加.value)

相关源码

"version": "3.2.37",

ref

当我们这么写时 const ** = ref()

其实源码是这么做的:

function ref(value) {
    return createRef(value, false);
}
function createRef(rawValue, shallow) {
    if (isRef(rawValue)) {
        return rawValue;
    }
    return new RefImpl(rawValue, shallow);
}

createRef 函数返回的是 RefImpl 类的实例,换句话说,ref 创建出来的响应式就是 RefImpl 实例对象。

例子:

const str = ref('hello world')
console.log(str);

1659603807899.jpg

reactive

当我们这么写时 const ** = reactive()

其实源码是这么做的:

function reactive(target) {
    // if trying to observe a readonly proxy, return the readonly version.
    if (isReadonly(target)) {
        return target;
    }
    return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap);
}
function createReactiveObject() {
    ...
    const proxy = new Proxy(target, targetType === 2 /* COLLECTION */ ? collectionHandlers : baseHandlers);
    proxyMap.set(target, proxy);
    return proxy;
}

reactive 最终会通过 Proxy 来进行实现响应拦截,返回的是一个 Proxy 对象

例子:

const obj = reactive({
    name: '小张',
    age: 8
})
console.log(obj);

image.png

小玩一下

通过ref创建对象

const obj = reactive({
    name: '小张',
    age: 8
})
console.log(obj);

image.png

结果显然,让对 ref 传入对象作为参数时和传入基本类型作为参数返回结果情况是不一样的。

基本类型返回值value就是具体的值,对象类型返回值value是 reactive 方法创建的 proxy 对象。

通过reactive创建基本类型

const str = reactive('hello world')
console.log(str);

image.png

遇到的问题

将接口请求到的列表数据赋值给响应数据arr

    let arr = reactive([])
    ...
    ajax => data: [1,2,3]
    
    // arr = [1, 2, 3]
    // 此时 arr 会失去响应式 
    // vue3 使用 proxy,对于对象和数组都不能直接整个赋值
    
    // arr.concat([1, 2, 3])
    // 失败 concat 不改变原数组
    
    [1, 2, 3].forEach(i => {arr.push(i)})
    // 可以
    
    // 创建一个响应式对象,对象的属性是数组 可以
    let data = reactive({
        arr: []
    })
    data.arr = [1, 2, 3]
    
    // 使用ref函数 可以
    let arr = ref([])
    arr.value = [1, 2, 3]
    
    // 使用数组的push方法 可以
    let arr = reactive([])
    arr.push(...[1, 2, 3])

解决方法:

1 - 使用ref函数
2 - 使用数组的push方法
3 - 创建一个响应式对象,对象的属性是数组

纯属小记,不喜勿喷。