# Refs

本节使用单文件组件语法作为代码示例

# ref

接受一个参数值并返回一个响应式且可改变的 ref 对象。ref 对象拥有一个指向内部值的单一属性 .value

示例:

const count = ref(0)
console.log(count.value) // 0

count.value++
console.log(count.value) // 1
1
2
3
4
5

如果传入 ref 的是一个对象,将调用 reactive 方法进行深层响应转换。

类型定义:

interface Ref<T> {
	value: T
}

function ref<T>(value: T): Ref<T>
1
2
3
4
5

有时我们可能需要为 ref 做一个较为复杂的类型标注。我们可以通过在调用 ref 时传递泛型参数来覆盖默认推导:

const foo = ref<string | number>('foo') // foo's type: Ref<string | number>

foo.value = 123 // ok!
1
2
3

如果泛型的类型未知,建议将 ref 转换为 Ref<T>

function useState<State extends string>(initial: State) {
  const state = ref(initial) as Ref<State> // state.value -> State extends string
  return state
}
1
2
3
4

# unref

如果参数是一个 ref 则返回它的 value,否则返回参数本身。它是 val = isRef(val) ? val.value : val 的语法糖。

function useFoo(x: number | Ref<number>) {
	const unwrapped = unref(x) // unwrapped 一定是 number 类型
}
1
2
3

# toRef

ref 可以用来为一个 reactive 对象的属性创建一个 ref。这个 ref 可以被传递并且能够保持响应性。

const state = reactive({
	foo: 1,
	bar: 2,
})

const fooRef = toRef(state, 'foo')

fooRef.value++
console.log(state.foo) // 2

state.foo++
console.log(fooRef.value) // 3
1
2
3
4
5
6
7
8
9
10
11
12

当您要将一个 prop 中的属性作为 ref 传给组合逻辑函数时,toRef 就派上了用场:

export default {
	setup(props) {
		useSomeFeature(toRef(props, 'foo'))
	},
}
1
2
3
4
5

# toRefs

把一个响应式对象转换成普通对象,该普通对象的每个 property 都是一个 ref ,和响应式对象 property 一一对应。

const state = reactive({
	foo: 1,
	bar: 2,
})

const stateAsRefs = toRefs(state)
/*
stateAsRefs 的类型如下:

{
  foo: Ref<number>,
  bar: Ref<number>
}
*/

// ref 对象 与 原属性的引用是 "链接" 上的
state.foo++
console.log(stateAsRefs.foo.value) // 2

stateAsRefs.foo.value++
console.log(state.foo) // 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

当想要从一个组合逻辑函数中返回响应式对象时,用 toRefs 是很有效的,该 API 让消费组件可以 解构 / 扩展(使用 ... 操作符)返回的对象,并不会丢失响应性:

function useFeatureX() {
	const state = reactive({
		foo: 1,
		bar: 2,
	})

	// 对 state 的逻辑操作

	// 返回时将属性都转为 ref
	return toRefs(state)
}

export default {
	setup() {
		// 可以解构,不会丢失响应性
		const { foo, bar } = useFeatureX()

		return {
			foo,
			bar,
		}
	},
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# isRef

检查一个值是否为一个 ref 对象。

# customRef

customRef 用于自定义一个 ref,可以显式地控制依赖追踪和触发响应,接受一个工厂函数,两个参数分别是用于追踪的 track 和用于触发响应的 trigger,并返回一个带有 getset 属性的对象。

  • 使用 v-model 使用自定义 ref 实现 debounce 的示例:

    <input v-model="text" />
    
    1
    function useDebouncedRef(value, delay = 200) {
    	let timeout
    	return customRef((track, trigger) => {
    		return {
    			get() {
    				track()
    				return value
    			},
    			set(newValue) {
    				clearTimeout(timeout)
    				timeout = setTimeout(() => {
    					value = newValue
    					trigger()
    				}, delay)
    			},
    		}
    	})
    }
    
    export default {
    	setup() {
    		return {
    			text: useDebouncedRef('hello'),
    		}
    	},
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26

类型定义:

function customRef<T>(factory: CustomRefFactory<T>): Ref<T>

type CustomRefFactory<T> = (
	track: () => void,
	trigger: () => void
) => {
	get: () => T
	set: (value: T) => void
}
1
2
3
4
5
6
7
8
9

# shallowRef

创建一个 ref ,将会追踪它的 .value 更改操作,但是并不会对变更后的 .value 做响应式代理转换(即变更不会调用 reactive)。

const foo = shallowRef({})
// 更改对操作会触发响应
foo.value = {}
// 但上面新赋的这个对象并不会变为响应式对象
isReactive(foo.value) // false
1
2
3
4
5

参考为 refs 创建独立的响应式值

# triggerRef

手动执行与 shallowRef 关联的任何效果。

const shallow = shallowRef({
	greet: 'Hello, world',
})

// 第一次运行打印一次 "Hello, world"
watchEffect(() => {
	console.log(shallow.value.greet)
})

// 这不会触发效果,因为ref是浅层的
shallow.value.greet = 'Hello, universe'

// 打印 "Hello, universe"
triggerRef(shallow)
1
2
3
4
5
6
7
8
9
10
11
12
13
14

参考:计算属性和侦听器 - watchEffect