Skip to content

Commit 07919e0

Browse files
authored
perf(reactivity): improve ref performance by using class-based implementation (#1900)
1 parent 0f8c991 commit 07919e0

File tree

2 files changed

+112
-74
lines changed

2 files changed

+112
-74
lines changed

packages/reactivity/src/computed.ts

+47-34
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { effect, ReactiveEffect, trigger, track } from './effect'
22
import { TriggerOpTypes, TrackOpTypes } from './operations'
33
import { Ref } from './ref'
44
import { isFunction, NOOP } from '@vue/shared'
5-
import { ReactiveFlags } from './reactive'
5+
import { ReactiveFlags, toRaw } from './reactive'
66

77
export interface ComputedRef<T = any> extends WritableComputedRef<T> {
88
readonly value: T
@@ -20,6 +20,47 @@ export interface WritableComputedOptions<T> {
2020
set: ComputedSetter<T>
2121
}
2222

23+
class ComputedRefImpl<T> {
24+
private _value!: T
25+
private _dirty = true
26+
27+
public readonly effect: ReactiveEffect<T>
28+
29+
public readonly __v_isRef = true;
30+
public readonly [ReactiveFlags.IS_READONLY]: boolean
31+
32+
constructor(
33+
getter: ComputedGetter<T>,
34+
private readonly _setter: ComputedSetter<T>,
35+
isReadonly: boolean
36+
) {
37+
this.effect = effect(getter, {
38+
lazy: true,
39+
scheduler: () => {
40+
if (!this._dirty) {
41+
this._dirty = true
42+
trigger(toRaw(this), TriggerOpTypes.SET, 'value')
43+
}
44+
}
45+
})
46+
47+
this[ReactiveFlags.IS_READONLY] = isReadonly
48+
}
49+
50+
get value() {
51+
if (this._dirty) {
52+
this._value = this.effect()
53+
this._dirty = false
54+
}
55+
track(toRaw(this), TrackOpTypes.GET, 'value')
56+
return this._value
57+
}
58+
59+
set value(newValue: T) {
60+
this._setter(newValue)
61+
}
62+
}
63+
2364
export function computed<T>(getter: ComputedGetter<T>): ComputedRef<T>
2465
export function computed<T>(
2566
options: WritableComputedOptions<T>
@@ -42,37 +83,9 @@ export function computed<T>(
4283
setter = getterOrOptions.set
4384
}
4485

45-
let dirty = true
46-
let value: T
47-
let computed: ComputedRef<T>
48-
49-
const runner = effect(getter, {
50-
lazy: true,
51-
scheduler: () => {
52-
if (!dirty) {
53-
dirty = true
54-
trigger(computed, TriggerOpTypes.SET, 'value')
55-
}
56-
}
57-
})
58-
computed = {
59-
__v_isRef: true,
60-
[ReactiveFlags.IS_READONLY]:
61-
isFunction(getterOrOptions) || !getterOrOptions.set,
62-
63-
// expose effect so computed can be stopped
64-
effect: runner,
65-
get value() {
66-
if (dirty) {
67-
value = runner()
68-
dirty = false
69-
}
70-
track(computed, TrackOpTypes.GET, 'value')
71-
return value
72-
},
73-
set value(newValue: T) {
74-
setter(newValue)
75-
}
76-
} as any
77-
return computed
86+
return new ComputedRefImpl(
87+
getter,
88+
setter,
89+
isFunction(getterOrOptions) || !getterOrOptions.set
90+
) as any
7891
}

packages/reactivity/src/ref.ts

+65-40
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ const convert = <T extends unknown>(val: T): T =>
2323

2424
export function isRef<T>(r: Ref<T> | unknown): r is Ref<T>
2525
export function isRef(r: any): r is Ref {
26-
return r ? r.__v_isRef === true : false
26+
return Boolean(r && r.__v_isRef === true)
2727
}
2828

2929
export function ref<T extends object>(
@@ -44,26 +44,34 @@ export function shallowRef(value?: unknown) {
4444
return createRef(value, true)
4545
}
4646

47+
class RefImpl<T> {
48+
private _value: T
49+
50+
public readonly __v_isRef = true
51+
52+
constructor(private _rawValue: T, private readonly _shallow = false) {
53+
this._value = _shallow ? _rawValue : convert(_rawValue)
54+
}
55+
56+
get value() {
57+
track(toRaw(this), TrackOpTypes.GET, 'value')
58+
return this._value
59+
}
60+
61+
set value(newVal) {
62+
if (hasChanged(toRaw(newVal), this._rawValue)) {
63+
this._rawValue = newVal
64+
this._value = this._shallow ? newVal : convert(newVal)
65+
trigger(toRaw(this), TriggerOpTypes.SET, 'value', newVal)
66+
}
67+
}
68+
}
69+
4770
function createRef(rawValue: unknown, shallow = false) {
4871
if (isRef(rawValue)) {
4972
return rawValue
5073
}
51-
let value = shallow ? rawValue : convert(rawValue)
52-
const r = {
53-
__v_isRef: true,
54-
get value() {
55-
track(r, TrackOpTypes.GET, 'value')
56-
return value
57-
},
58-
set value(newVal) {
59-
if (hasChanged(toRaw(newVal), rawValue)) {
60-
rawValue = newVal
61-
value = shallow ? newVal : convert(newVal)
62-
trigger(r, TriggerOpTypes.SET, 'value', newVal)
63-
}
64-
}
65-
}
66-
return r
74+
return new RefImpl(rawValue, shallow)
6775
}
6876

6977
export function triggerRef(ref: Ref) {
@@ -103,21 +111,32 @@ export type CustomRefFactory<T> = (
103111
set: (value: T) => void
104112
}
105113

106-
export function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {
107-
const { get, set } = factory(
108-
() => track(r, TrackOpTypes.GET, 'value'),
109-
() => trigger(r, TriggerOpTypes.SET, 'value')
110-
)
111-
const r = {
112-
__v_isRef: true,
113-
get value() {
114-
return get()
115-
},
116-
set value(v) {
117-
set(v)
118-
}
114+
class CustomRefImpl<T> {
115+
private readonly _get: ReturnType<CustomRefFactory<T>>['get']
116+
private readonly _set: ReturnType<CustomRefFactory<T>>['set']
117+
118+
public readonly __v_isRef = true
119+
120+
constructor(factory: CustomRefFactory<T>) {
121+
const { get, set } = factory(
122+
() => track(this, TrackOpTypes.GET, 'value'),
123+
() => trigger(this, TriggerOpTypes.SET, 'value')
124+
)
125+
this._get = get
126+
this._set = set
127+
}
128+
129+
get value() {
130+
return this._get()
131+
}
132+
133+
set value(newVal) {
134+
this._set(newVal)
119135
}
120-
return r as any
136+
}
137+
138+
export function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {
139+
return new CustomRefImpl(factory) as any
121140
}
122141

123142
export function toRefs<T extends object>(object: T): ToRefs<T> {
@@ -131,19 +150,25 @@ export function toRefs<T extends object>(object: T): ToRefs<T> {
131150
return ret
132151
}
133152

153+
class ObjectRefImpl<T extends object, K extends keyof T> {
154+
public readonly __v_isRef = true
155+
156+
constructor(private readonly _object: T, private readonly _key: K) {}
157+
158+
get value() {
159+
return this._object[this._key]
160+
}
161+
162+
set value(newVal) {
163+
this._object[this._key] = newVal
164+
}
165+
}
166+
134167
export function toRef<T extends object, K extends keyof T>(
135168
object: T,
136169
key: K
137170
): Ref<T[K]> {
138-
return {
139-
__v_isRef: true,
140-
get value(): any {
141-
return object[key]
142-
},
143-
set value(newVal) {
144-
object[key] = newVal
145-
}
146-
} as any
171+
return new ObjectRefImpl(object, key) as any
147172
}
148173

149174
// corner case when use narrows type

0 commit comments

Comments
 (0)