官方对ref的说明是

vue2的ref是用来操作某个元素节点的,可以说是document.getElementById的语法糖。vue3的ref延续了vue2的用法,还增加了一个作用就是创建响应式数据。相比reactive,ref可以监听基本类型数据。尤大大也是建议创建响应式数据更多使用ref来代替reactive。
this.$ref使用this.$refs.form.validate()第一种使用方式:操作元素节点
let form = ref();form.value.validate()需要注意的是在vue2中,通过ref是可以获取子组件的所有内容的,而vue3中如果是在组件上声明ref,是无法用ref获取子组件内部的元素属性的,子组件需要通过
defineExpose手动暴露给外部组件
第二种使用方式:声明响应式数据
如下例子:
<div>{{ count }}</div>
<button @click="add"></button>
<button @click="subtract">-</button>
let count = ref(0);
const add =()=>{count.value++;}
const subtract = ()=>{count.value--;}
<div>{{obj}}</div>
<input v-model="key">
<button @click="add">+</button>
let obj = ref({});
let key = ref('');
const add =()=>{
obj.value[key.value] = key.value;
}
结果如下

当然这里的obj也可以用reactive响应式,但是key就不能使用了,原因参考之前讲的reactive原理。
下面看看ref的实现原理
function ref(value) {
return createRef(value, false);
}
function isRef(r) {
return !!(r && r.__v_isRef === true);
}
//判断当前数据是否已经是ref数据,是则直接返回。否则返回一个新new的RefImpl实例
function createRef(rawValue, shallow) {
if (isRef(rawValue)) {
return rawValue;
}
return new RefImpl(rawValue, shallow);
}
class RefImpl {
constructor(value, __v_isShallow) {
this.__v_isShallow = __v_isShallow;//标志位来表明是否是浅响应数据(shallowRef时该值为true)
this.dep = undefined;
this.__v_isRef = true;//标志位来表明是ref类型数据。
this._rawValue = __v_isShallow ? value : toRaw(value);//toRaw返回变量的__v_raw属性或者变量本身
this._value = __v_isShallow ? value : toReactive(value);//如果是浅响应对象(shallowRef)则返回对象本身,否则进行监听,返回代理对象。
}
get value() {
trackRefValue(this);//依赖收集
return this._value;
}
set value(newVal) {
newVal = this.__v_isShallow ? newVal : toRaw(newVal);
if (hasChanged(newVal, this._rawValue)) { //判断数据是否发生变化
this._rawValue = newVal;
this._value = this.__v_isShallow ? newVal : toReactive(newVal);
triggerRefValue(this, newVal); //通知各依赖更新数据
}
}
}
//可以看到 返回的跟reactive的数据是一样的。
const toReactive = (value) => isObject(value) ? reactive(value) : value;
function trackRefValue(ref) {
if (shouldTrack && activeEffect) {
ref = toRaw(ref);
if ((process.env.NODE_ENV !== 'production')) {
trackEffects(ref.dep || (ref.dep = createDep()), {
target: ref,
type: "get" /* GET */,
key: 'value'
});
}
else {
trackEffects(ref.dep || (ref.dep = createDep()));
}
}
}
function triggerRefValue(ref, newVal) {
ref = toRaw(ref);
if (ref.dep) {
if ((process.env.NODE_ENV !== 'production')) {
triggerEffects(ref.dep, {
target: ref,
type: "set" /* SET */,
key: 'value',
newValue: newVal
});
}
else {
triggerEffects(ref.dep);
}
}
}
可以看到实例内部的value属性的get,set方法,这也就是为什么使用ref数据时要用.value来获取其数据。
在返回的实例的构造函数内对当前数据进行了reactive返回了代理对象给_value,在get value时直接返回_value。
再看他的get方法,是先进行了依赖收集,再返回实例的_value属性
set方法中判断数据是否改变,如果改变对数据再次reactive响应,并调用triggerEffects,更新相关依赖。
那么是如何区分ref创建的是元素节点还是纯响应式数据呢。
在patch时,会从标签的props中拿到ref属性。然后把当前节点挂载到ref的value属性
const patch = (n1, n2, container, ...){
//...
const { type, ref, shapeFlag } = n2;
//...
// set ref
if (ref != null && parentComponent) {
setRef(ref, n1 && n1.ref, parentSuspense, n2 || n1, !n2);
}
}
function setRef(rawRef, oldRawRef, parentSuspense, vnode, isUnmount = false) {
//...
const refValue = vnode.shapeFlag & 4 /* STATEFUL_COMPONENT */
? getExposeProxy(vnode.component) || vnode.component.proxy
: vnode.el;
const value = isUnmount ? null : refValue;
// ...
ref.value = value;
}
在声明基础类型的响应式数据或者操作元素节点时ref都是一个很好的使用方式,需要注意的是在对子组件进行操作时,如果获取不到子组件的某些属性,就要查看子组件内部是否有将该属性通过defineExpose暴露出去。