可以将 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构造响应式对象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函数时相当于值的复制,自然出现了以上两种不同的结果
父组件中定义一个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;
props.columns.forEach(col => {
col.isShow = col.isShow ?? true;
});
但却会对props.columns造成改变
这样的话岂不是违法了props的单向数据流原则?