vue3 ref的使用、问题及源码分析;引用型变量和原始类型变量的复制值

ref定义及作用

可以将 ref 看成 reactive 的一个变形版本,这是由于 reactive 内部采用 Proxy 来实现,而 Proxy 只接受对象作为入参,这才有了 ref 来解决值类型的数据响应(原始数据类型共有7个,分别是:String/ Number /BigInt /Boolean /Symbol /Null /Undefined。),如果传入 ref 的是一个对象,内部也会调用 reactive 方法进行深层响应转换,即ref允许我们创建使用任何值类型的响应式。

用法

ref() 将传入的参数包装为一个带有 value 属性的 ref 对象,对于传入的对象也会用value进行一层包装。

源码

export function ref(value?: unknown) {
  return createRef(value)
}

/**
 * @description: 
 * @param {rawValue} 原始值 
 * @param {shallow} 是否是浅观察 
 */
function createRef(rawValue: unknown, shallow = false) {
  // 如果已经是ref直接返回
  if (isRef(rawValue)) {
    return rawValue
  }

  // 如果是浅观察直接观察,不是则将 rawValue 转换成 reactive ,
  // reactive 的定义在下方 
  let value = shallow ? rawValue : convert(rawValue)

  // ref 的结构
  const r = {
    // ref 标识
    __v_isRef: true,
    get value() {
      // 依赖收集
      track(r, TrackOpTypes.GET, 'value')
      return value
    },
    set value(newVal) {
      if (hasChanged(toRaw(newVal), rawValue)) {
        rawValue = newVal
        value = shallow ? newVal : convert(newVal)
        // 触发依赖
        trigger(
          r,
          TriggerOpTypes.SET,
          'value',
          __DEV__ ? { newValue: newVal } : void 0
        )
      }
    }
  }
  return r
}

// 如是是对象则调用 reactive, 否则直接返回 
const convert = <T extends unknown>(val: T): T =>
  isObject(val) ? reactive(val) : val

实验一 修改原变量和ref后的值

原始数据类型

我们定义一个字符串,使用ref构造响应式对象valueRef,然后修改valueRef的值

let value3 = "123";
let valueRef = ref(value3);
valueRef.value = "456";
console.log(valueRef.value);
console.log(value3);

此时输出是怎样的结果呢?
在这里插入图片描述

会发现valueRef.value发生改变,但是原来的变量value3 并没有改变

那么修改value3的值呢

let value3 = "123";
let valueRef = ref(value3);
// valueRef.value = "456";
value3 = "456";
console.log(valueRef.value);
console.log(value3);

在这里插入图片描述
会发现value3发生了改变,但是valueRef.value并未改变,这是符合刚开始的预期的,但是如果换成对象类型呢

对象类型

定义如下:

let value4 = { value: "123" };
let valueRef2 = ref(value4);
valueRef2.value.value = "456";
console.log(valueRef2.value);
console.log(value4);

修改valueRef2.value.value的值,此时结果如图:
在这里插入图片描述
会发现原来的value4 也会发送改变

修改value4的值

let value4 = { value: "123" };
let valueRef2 = ref(value4);
// valueRef2.value.value = "456";
value4.value = "456";
console.log(valueRef2.value);
console.log(value4);

结果如下:
在这里插入图片描述
发现valueRef2.value.value的值也会发生改变

总结

实验发现,当对原始数据类型使用ref响应后,响应式的变量和原变量已经完全脱钩,修改任一一方的值都不会引起另外一方的改变;然而如果是对对象使用ref响应,则修改其中一方的值都会引起另外一方的改变

分析原理:还是要回到javascript中变量的存储以及复制值,对于原始数据类型的变量会存于中,对于引用类型的变量会存于中。

原始类型的值复制时,原来变量的值会被复制到新变量的位置,即会产生一个新的副本,两者是完全独立的;但是对于引用类型的复制时,存储在原来变量的值也会复制到新变量,但区别在于,这里复制的值其实是一个指针,共同指向存储在堆中的对象。操作完成后,两个变量实际上指向同一个对象,因此其中一个值进行变化,另外的值同样也会反映出来。而传递给ref函数和createRef函数时相当于值的复制,自然出现了以上两种不同的结果

实验二 props的ref

父组件中定义一个columns数组

<template>
  <div>
    <div>{{ value }}</div>
    <div>{{ value2 }}</div>
    <child v-model:value="value" :value2="value2" :columns="columns"></child>
  </div>
</template>

<script lang="ts" setup>
import { ref } from "vue";
import child from "@/views/test/vmodel-test/child.vue";
import { ColumnProps } from "@/components/ProTable/interface";
let value = ref("111");
let value2 = ref({ value: 222 });
const columns: ColumnProps[] = [
  { type: "index", label: "#", width: 150 },
  { prop: "meta.title", label: "菜单名称", align: "left", search: { el: "input" } },
  { prop: "meta.icon", label: "菜单图标" },
  { prop: "name", label: "菜单 name", search: { el: "input" } },
  { prop: "path", label: "菜单路径", width: 300, search: { el: "input" } },
  { prop: "component", label: "组件路径", width: 300 },
  { prop: "operation", label: "操作", width: 250, fixed: "right" }
];
</script>

在子组件中接收:

<script lang="ts" setup name="child">
import { ref, computed } from "vue";
import { ColumnProps } from "@/components/ProTable/interface";

const props = defineProps<{ value: string; value2: { value: number }; columns: ColumnProps[] }>();

const columnsRef = ref(props.columns);
columnsRef.value[1].isShow = false;
const addAttrFunc = (columns: ColumnProps[]) => {
  columns.forEach(col => {
    col.isShow = col.isShow ?? true;
  });
};
addAttrFunc(columnsRef.value);
console.log(columnsRef.value);
console.log(props.columns);
</script>

使用ref(props.columns)生成一个响应式变量columnsRef ,此时对columnsRef进行修改,直接赋值修改和调用foreach函数进行修改,结果如下
在这里插入图片描述
发现props.columns的值也发生了改变即props的值发生了变化
但是如果是对value使用ref函数获得的响应对象进行修改

const valueRef = ref(props.value);
valueRef.value = "456";
console.log(valueRef.value);
console.log(props.value);

结果如图,props.value是不会发生改变的,原因同与实验一相同,产生一个独立的副本

在这里插入图片描述

对于props值本身的修改,不论是原始类型还是引用型都是会直接报错的

props.value = "456";
props.columns[1].isShow = false;

在这里插入图片描述
但是如果调用foreach方法,则不会报错

props.columns.forEach(col => {
  col.isShow = col.isShow ?? true;
});

但却会对props.columns造成改变
这样的话岂不是违法了props的单向数据流原则?

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue 3 使用 ref 定义基本变量是为了实现响应式数据的自动追踪和触发更新。 在Vue 3之前的版本中,Vue 可以自动追踪和更新对象和数组的变化,但对于基本类型的数据(如字符串、数字等)却无法进行自动更新。这是因为基本类型的数据在赋时是按传递的,而不是引用传递,Vue无法在赋时感知到数据的变化。 为了解决这个问题Vue 3 引入了 ref 函数来包装基本类型ref 函数会返回一个带有 value 属性的响应式对象,这个 value 属性才是真正存储的地方。当基本类型发生变化时,通过 ref 返回的对象会自动更新,从而触发组件的重新渲染。 使用 ref 定义基本变量的好处是,我们不需要手动地调用 Vue 的更新机制来触发重新渲染。Vue 3 会自动监视 ref 返回的对象的变化,并在需要的时候进行更新。这大大简化了我们编写组件的过程,提高了开发效率。 另外,ref 还提供了一个 .value 属性,可以通过该属性获取或设置基本变量。在模板中访问 ref 变量时,Vue 3 会自动帮我们解包 ref 对象,只返回其中的 value 属性所指向的。这使得我们可以在模板中直接使用 ref 变量,而无需额外的操作。 综上所述,Vue 3 采用 ref 来定义基本变量是为了实现对基本类型数据的追踪和自动更新,简化了组件开发,并提高了开发效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值