Skip to content

Commit

Permalink
feat(reactivity): add support for customRef API
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Apr 15, 2020
1 parent 4046f0b commit b83c580
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 4 deletions.
31 changes: 30 additions & 1 deletion packages/reactivity/__tests__/ref.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
isReactive
} from '../src/index'
import { computed } from '@vue/runtime-dom'
import { shallowRef, unref } from '../src/ref'
import { shallowRef, unref, customRef } from '../src/ref'

describe('reactivity/ref', () => {
it('should hold a value', () => {
Expand Down Expand Up @@ -208,4 +208,33 @@ describe('reactivity/ref', () => {
expect(dummyX).toBe(4)
expect(dummyY).toBe(5)
})

test('customRef', () => {
let value = 1
let _trigger: () => void

const custom = customRef((track, trigger) => ({
get() {
track()
return value
},
set(newValue: number) {
value = newValue
_trigger = trigger
}
}))

let dummy
effect(() => {
dummy = custom.value
})
expect(dummy).toBe(1)

custom.value = 2
// should not trigger yet
expect(dummy).toBe(1)

_trigger!()
expect(dummy).toBe(2)
})
})
11 changes: 10 additions & 1 deletion packages/reactivity/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
export { ref, unref, shallowRef, isRef, toRefs, Ref, UnwrapRef } from './ref'
export {
ref,
unref,
shallowRef,
isRef,
toRefs,
customRef,
Ref,
UnwrapRef
} from './ref'
export {
reactive,
isReactive,
Expand Down
25 changes: 25 additions & 0 deletions packages/reactivity/src/ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,31 @@ export function unref<T>(ref: T): T extends Ref<infer V> ? V : T {
return isRef(ref) ? (ref.value as any) : ref
}

export type CustomRefFactory<T> = (
track: () => void,
trigger: () => void
) => {
get: () => T
set: (value: T) => void
}

export function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {
const { get, set } = factory(
() => track(r, TrackOpTypes.GET, 'value'),
() => trigger(r, TriggerOpTypes.SET, 'value')
)
const r = {

This comment has been minimized.

Copy link
@jods4

jods4 Apr 15, 2020

Contributor

Ah, you made me brush up my knowledge of temporal dead zone.
I was surprised you could reference r before it's defined! (For other readers: you can from a function, as long as the function isn't invoked before it's defined.)

Random thought: would this result in two less functions and more direct access? (get/set functions are the native implementation of properties, rather than a nested call)

const define = factory(...)
const r = Object.defineProperty({ _isRef: true }, 'value', define)

This comment has been minimized.

Copy link
@yyx990803

yyx990803 Apr 15, 2020

Author Member

I think that makes the code harder to understand. It also means the user can pass in anything that is a valid property descriptor... also, in general inline functions are quite cheap in modern engines and ref creation is hardly the hot spot so we don't really need to micro-optimize here.

_isRef: true,
get value() {
return get()
},
set value(v) {
set(v)
}
}
return r as any
}

export function toRefs<T extends object>(
object: T
): { [K in keyof T]: Ref<T[K]> } {
Expand Down
5 changes: 3 additions & 2 deletions packages/runtime-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ export {
shallowRef,
isRef,
toRefs,
customRef,
reactive,
isReactive,
readonly,
isReadonly,
shallowReactive,
toRaw,
markNonReactive
markNonReactive,
toRaw
} from '@vue/reactivity'
export { computed } from './apiComputed'
export { watch, watchEffect } from './apiWatch'
Expand Down

0 comments on commit b83c580

Please sign in to comment.