手摸手学习vue3源码五:toRef

98 阅读2分钟
  • 文件目录:packages/reactivity/src/ref.ts
  • 作用:
    • 可以将值、refs、getters 规范化为 refs
    • 也就可以将响应式对象属性,创建一个对应的ref。这样创建的ref与其源属性保持同步,改变源属性的值,将更新 ref 的值;改变ref的值,也更新源属性的值。
  • 源码
function toRef(source, key?, defaultValue?) {
    if (isRef(source)) {
        // 是ref对象,直接返回
        return source
    } else if (isFunction(source))) {
        // 是函数,创建只读的getter返回 new GetterRefImpl
        return new GetterRefImpl(source)
    } else if (isObject(source) && arguments.length > 1) {
        // 对象属性签名 new ObjectRefImpl
        return 
        
        ```js
        function 
        ```(source, key!, defaultValue)
    } else {
        // 创建 ref 对象返回
        return ref(source)
    }
}
  • 核心类
class ObjectRefImpl{
    constructor(
        private readonly _object,
        private readonly _key,
        private readonly _defaultValue
    ) {}
    
    get value() {
        const val = this._object[this._key]
        return (this._value = val === undefined ? this._defaultValue !: val)
    }
    set value(newValue) {
        this._object[this._key] = newVal
    }
    
    get dep(): Dep | undefined {
        return getDepFromReactive(toRaw(this._object), this._key)
    }
}
function propertyToRef(source, key, defaultValue?) {
    const val = source[key]
    return isRef(val) ? val : (new ObjectRefImpl(source, key, defaultValue))
}

使用

  • 规范化签名:
/** 按原样返回现有的 ref **/
const name = ref("张三");
const nameToRef = toRef(name);
console.log("方法:", nameToRef.value); // 张三

/** 创建 **只读 ref**,当调用 .value 时,会调用此 **getter 函数** **/
function testToRef() {
  const state = () => "张三";

  return toRef(state);
}

console.log("方法:", testToRef().value); // 张三
console.log("方法:", testToRef().value = "lisi"); // 无法为“value”赋值,因为它是只读属性。


/** 非函数值创建ref **/
const valueToRef = toRef(1);
console.log("值:", valueToRef.value); // 1
  • 对象属性签名
const state = reactive({ foo: 1, bar: 2 })
// 双向 ref,会与源属性同步
const fooRef = toRef(state, "foo");
console.log("foo ref对象:", fooRef); // Ref<1>

// 更改 ref 会更新源属性
fooRef.value++;
console.log("foo value", fooRef.value); // 2
console.log("state value", state.foo); // 2

// 更改源属性也会更新 ref
state.foo++;
console.log("foo value", fooRef.value); // 3
console.log("state value", state.foo); // 3

注意

  • 不同于
const fooRef = ref(state.foo)

// 这个 fooRef 不会与 state 建立同步,因为这个 ref() 接收了一个纯数值。
  • 使用 toRef() 这个函数在你想把一个 prop 的 ref 传递给一个组合式函数时会有用
<script setup>
import { toRef } from "vue"
const props = defineProps()

// 将 props.foo 转换为 ref,然后传入一个组合式函数
useSomeFeature(toRef(props, 'foo))

// getter 语法
useSomeFeature(toRef(() => props.foo))
</script>