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 { TriggerOpTypes, TrackOpTypes } from './operations'
|
||||||
import { Ref } from './ref'
|
import { Ref } from './ref'
|
||||||
import { isFunction, NOOP } from '@vue/shared'
|
import { isFunction, NOOP } from '@vue/shared'
|
||||||
import { ReactiveFlags } from './reactive'
|
import { ReactiveFlags, toRaw } from './reactive'
|
||||||
|
|
||||||
export interface ComputedRef<T = any> extends WritableComputedRef<T> {
|
export interface ComputedRef<T = any> extends WritableComputedRef<T> {
|
||||||
readonly value: T
|
readonly value: T
|
||||||
@ -20,6 +20,47 @@ export interface WritableComputedOptions<T> {
|
|||||||
set: ComputedSetter<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>(getter: ComputedGetter<T>): ComputedRef<T>
|
||||||
export function computed<T>(
|
export function computed<T>(
|
||||||
options: WritableComputedOptions<T>
|
options: WritableComputedOptions<T>
|
||||||
@ -42,37 +83,9 @@ export function computed<T>(
|
|||||||
setter = getterOrOptions.set
|
setter = getterOrOptions.set
|
||||||
}
|
}
|
||||||
|
|
||||||
let dirty = true
|
return new ComputedRefImpl(
|
||||||
let value: T
|
getter,
|
||||||
let computed: ComputedRef<T>
|
setter,
|
||||||
|
isFunction(getterOrOptions) || !getterOrOptions.set
|
||||||
const runner = effect(getter, {
|
) as any
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
@ -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<T>(r: Ref<T> | unknown): r is Ref<T>
|
||||||
export function isRef(r: any): r is Ref {
|
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>(
|
export function ref<T extends object>(
|
||||||
@ -44,26 +44,34 @@ export function shallowRef(value?: unknown) {
|
|||||||
return createRef(value, true)
|
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) {
|
function createRef(rawValue: unknown, shallow = false) {
|
||||||
if (isRef(rawValue)) {
|
if (isRef(rawValue)) {
|
||||||
return rawValue
|
return rawValue
|
||||||
}
|
}
|
||||||
let value = shallow ? rawValue : convert(rawValue)
|
return new RefImpl(rawValue, shallow)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function triggerRef(ref: Ref) {
|
export function triggerRef(ref: Ref) {
|
||||||
@ -103,21 +111,32 @@ export type CustomRefFactory<T> = (
|
|||||||
set: (value: T) => void
|
set: (value: T) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {
|
class CustomRefImpl<T> {
|
||||||
const { get, set } = factory(
|
private readonly _get: ReturnType<CustomRefFactory<T>>['get']
|
||||||
() => track(r, TrackOpTypes.GET, 'value'),
|
private readonly _set: ReturnType<CustomRefFactory<T>>['set']
|
||||||
() => trigger(r, TriggerOpTypes.SET, 'value')
|
|
||||||
)
|
public readonly __v_isRef = true
|
||||||
const r = {
|
|
||||||
__v_isRef: true,
|
constructor(factory: CustomRefFactory<T>) {
|
||||||
get value() {
|
const { get, set } = factory(
|
||||||
return get()
|
() => track(this, TrackOpTypes.GET, 'value'),
|
||||||
},
|
() => trigger(this, TriggerOpTypes.SET, 'value')
|
||||||
set value(v) {
|
)
|
||||||
set(v)
|
this._get = get
|
||||||
}
|
this._set = set
|
||||||
}
|
}
|
||||||
return r as any
|
|
||||||
|
get value() {
|
||||||
|
return this._get()
|
||||||
|
}
|
||||||
|
|
||||||
|
set value(newVal) {
|
||||||
|
this._set(newVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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> {
|
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
|
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>(
|
export function toRef<T extends object, K extends keyof T>(
|
||||||
object: T,
|
object: T,
|
||||||
key: K
|
key: K
|
||||||
): Ref<T[K]> {
|
): Ref<T[K]> {
|
||||||
return {
|
return new ObjectRefImpl(object, key) as any
|
||||||
__v_isRef: true,
|
|
||||||
get value(): any {
|
|
||||||
return object[key]
|
|
||||||
},
|
|
||||||
set value(newVal) {
|
|
||||||
object[key] = newVal
|
|
||||||
}
|
|
||||||
} as any
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// corner case when use narrows type
|
// corner case when use narrows type
|
||||||
|
Loading…
Reference in New Issue
Block a user