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

文章目录

  • ref定义及作用
    • 用法
    • 源码
  • 实验一 修改原变量和ref后的值
    • 原始数据类型
    • 对象类型
    • 总结
  • 实验二 props的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函数进行修改,结果如下
vue3 ref的使用、问题及源码分析;引用型变量和原始类型变量的复制值_第1张图片
发现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的单向数据流原则?

你可能感兴趣的:(vue,vue.js,javascript)