perf(reactivity): improve ref performance by using class-based implementation (#1900)
This commit is contained in:
parent
0f8c9917f8
commit
07919e0065
@ -2,7 +2,7 @@ import { effect, ReactiveEffect, trigger, track } from './effect'
|
||||
import { TriggerOpTypes, TrackOpTypes } from './operations'
|
||||
import { Ref } from './ref'
|
||||
import { isFunction, NOOP } from '@vue/shared'
|
||||
import { ReactiveFlags } from './reactive'
|
||||
import { ReactiveFlags, toRaw } from './reactive'
|
||||
|
||||
export interface ComputedRef<T = any> extends WritableComputedRef<T> {
|
||||
readonly value: T
|
||||
@ -20,6 +20,47 @@ export interface WritableComputedOptions<T> {
|
||||
set: ComputedSetter<T>
|
||||
}
|
||||
|
||||
class ComputedRefImpl<T> {
|
||||
private _value!: T
|
||||
private _dirty = true
|
||||
|
||||
public readonly effect: ReactiveEffect<T>
|
||||
|
||||
public readonly __v_isRef = true;
|
||||
public readonly [ReactiveFlags.IS_READONLY]: boolean
|
||||
|
||||
constructor(
|
||||
getter: ComputedGetter<T>,
|
||||
private readonly _setter: ComputedSetter<T>,
|
||||
isReadonly: boolean
|
||||
) {
|
||||
this.effect = effect(getter, {
|
||||
lazy: true,
|
||||
scheduler: () => {
|
||||
if (!this._dirty) {
|
||||
this._dirty = true
|
||||
trigger(toRaw(this), TriggerOpTypes.SET, 'value')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
this[ReactiveFlags.IS_READONLY] = isReadonly
|
||||
}
|
||||
|
||||
get value() {
|
||||
if (this._dirty) {
|
||||
this._value = this.effect()
|
||||
this._dirty = false
|
||||
}
|
||||
track(toRaw(this), TrackOpTypes.GET, 'value')
|
||||
return this._value
|
||||
}
|
||||
|
||||
set value(newValue: T) {
|
||||
this._setter(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
export function computed<T>(getter: ComputedGetter<T>): ComputedRef<T>
|
||||
export function computed<T>(
|
||||
options: WritableComputedOptions<T>
|
||||
@ -42,37 +83,9 @@ export function computed<T>(
|
||||
setter = getterOrOptions.set
|
||||
}
|
||||
|
||||
let dirty = true
|
||||
let value: T
|
||||
let computed: ComputedRef<T>
|
||||
|
||||
const runner = effect(getter, {
|
||||
lazy: true,
|
||||
scheduler: () => {
|
||||
if (!dirty) {
|
||||
dirty = true
|
||||
trigger(computed, TriggerOpTypes.SET, 'value')
|
||||
}
|
||||
}
|
||||
})
|
||||
computed = {
|
||||
__v_isRef: true,
|
||||
[ReactiveFlags.IS_READONLY]:
|
||||
isFunction(getterOrOptions) || !getterOrOptions.set,
|
||||
|
||||
// expose effect so computed can be stopped
|
||||
effect: runner,
|
||||
get value() {
|
||||
if (dirty) {
|
||||
value = runner()
|
||||
dirty = false
|
||||
}
|
||||
track(computed, TrackOpTypes.GET, 'value')
|
||||
return value
|
||||
},
|
||||
set value(newValue: T) {
|
||||
setter(newValue)
|
||||
}
|
||||
} as any
|
||||
return computed
|
||||
return new ComputedRefImpl(
|
||||
getter,
|
||||
setter,
|
||||
isFunction(getterOrOptions) || !getterOrOptions.set
|
||||
) as any
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ const convert = <T extends unknown>(val: T): T =>
|
||||
|
||||
export function isRef<T>(r: Ref<T> | unknown): r is Ref<T>
|
||||
export function isRef(r: any): r is Ref {
|
||||
return r ? r.__v_isRef === true : false
|
||||
return Boolean(r && r.__v_isRef === true)
|
||||
}
|
||||
|
||||
export function ref<T extends object>(
|
||||
@ -44,26 +44,34 @@ export function shallowRef(value?: unknown) {
|
||||
return createRef(value, true)
|
||||
}
|
||||
|
||||
class RefImpl<T> {
|
||||
private _value: T
|
||||
|
||||
public readonly __v_isRef = true
|
||||
|
||||
constructor(private _rawValue: T, private readonly _shallow = false) {
|
||||
this._value = _shallow ? _rawValue : convert(_rawValue)
|
||||
}
|
||||
|
||||
get value() {
|
||||
track(toRaw(this), TrackOpTypes.GET, 'value')
|
||||
return this._value
|
||||
}
|
||||
|
||||
set value(newVal) {
|
||||
if (hasChanged(toRaw(newVal), this._rawValue)) {
|
||||
this._rawValue = newVal
|
||||
this._value = this._shallow ? newVal : convert(newVal)
|
||||
trigger(toRaw(this), TriggerOpTypes.SET, 'value', newVal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createRef(rawValue: unknown, shallow = false) {
|
||||
if (isRef(rawValue)) {
|
||||
return rawValue
|
||||
}
|
||||
let value = shallow ? rawValue : convert(rawValue)
|
||||
const r = {
|
||||
__v_isRef: true,
|
||||
get value() {
|
||||
track(r, TrackOpTypes.GET, 'value')
|
||||
return value
|
||||
},
|
||||
set value(newVal) {
|
||||
if (hasChanged(toRaw(newVal), rawValue)) {
|
||||
rawValue = newVal
|
||||
value = shallow ? newVal : convert(newVal)
|
||||
trigger(r, TriggerOpTypes.SET, 'value', newVal)
|
||||
}
|
||||
}
|
||||
}
|
||||
return r
|
||||
return new RefImpl(rawValue, shallow)
|
||||
}
|
||||
|
||||
export function triggerRef(ref: Ref) {
|
||||
@ -103,21 +111,32 @@ export type CustomRefFactory<T> = (
|
||||
set: (value: T) => void
|
||||
}
|
||||
|
||||
export function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {
|
||||
class CustomRefImpl<T> {
|
||||
private readonly _get: ReturnType<CustomRefFactory<T>>['get']
|
||||
private readonly _set: ReturnType<CustomRefFactory<T>>['set']
|
||||
|
||||
public readonly __v_isRef = true
|
||||
|
||||
constructor(factory: CustomRefFactory<T>) {
|
||||
const { get, set } = factory(
|
||||
() => track(r, TrackOpTypes.GET, 'value'),
|
||||
() => trigger(r, TriggerOpTypes.SET, 'value')
|
||||
() => track(this, TrackOpTypes.GET, 'value'),
|
||||
() => trigger(this, TriggerOpTypes.SET, 'value')
|
||||
)
|
||||
const r = {
|
||||
__v_isRef: true,
|
||||
this._get = get
|
||||
this._set = set
|
||||
}
|
||||
|
||||
get value() {
|
||||
return get()
|
||||
},
|
||||
set value(v) {
|
||||
set(v)
|
||||
return this._get()
|
||||
}
|
||||
|
||||
set value(newVal) {
|
||||
this._set(newVal)
|
||||
}
|
||||
return r as any
|
||||
}
|
||||
|
||||
export function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {
|
||||
return new CustomRefImpl(factory) as any
|
||||
}
|
||||
|
||||
export function toRefs<T extends object>(object: T): ToRefs<T> {
|
||||
@ -131,19 +150,25 @@ export function toRefs<T extends object>(object: T): ToRefs<T> {
|
||||
return ret
|
||||
}
|
||||
|
||||
class ObjectRefImpl<T extends object, K extends keyof T> {
|
||||
public readonly __v_isRef = true
|
||||
|
||||
constructor(private readonly _object: T, private readonly _key: K) {}
|
||||
|
||||
get value() {
|
||||
return this._object[this._key]
|
||||
}
|
||||
|
||||
set value(newVal) {
|
||||
this._object[this._key] = newVal
|
||||
}
|
||||
}
|
||||
|
||||
export function toRef<T extends object, K extends keyof T>(
|
||||
object: T,
|
||||
key: K
|
||||
): Ref<T[K]> {
|
||||
return {
|
||||
__v_isRef: true,
|
||||
get value(): any {
|
||||
return object[key]
|
||||
},
|
||||
set value(newVal) {
|
||||
object[key] = newVal
|
||||
}
|
||||
} as any
|
||||
return new ObjectRefImpl(object, key) as any
|
||||
}
|
||||
|
||||
// corner case when use narrows type
|
||||
|
Loading…
Reference in New Issue
Block a user