Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf(ref): improve ref performance #1900

Merged
merged 5 commits into from
Aug 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 47 additions & 34 deletions packages/reactivity/src/computed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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>
Expand All @@ -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
}
105 changes: 65 additions & 40 deletions packages/reactivity/src/ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>(
Expand All @@ -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) {
Expand Down Expand Up @@ -103,21 +111,32 @@ export type CustomRefFactory<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 = {
__v_isRef: true,
get value() {
return get()
},
set value(v) {
set(v)
}
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(this, TrackOpTypes.GET, 'value'),
() => trigger(this, TriggerOpTypes.SET, 'value')
)
this._get = get
this._set = set
}

get value() {
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> {
Expand All @@ -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
Expand Down