From dde6c151e44d7de6b4e57a1cb8b6ff0976a65e25 Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 29 May 2019 22:11:33 +0800 Subject: [PATCH] wip: test + typing for value --- packages/observer/__tests__/value.spec.ts | 54 ++++++++++++++++++++++ packages/observer/src/index.ts | 10 ++-- packages/observer/src/value.ts | 56 +++++++++++++++++++++++ packages/runtime-core/src/component.ts | 10 +--- 4 files changed, 117 insertions(+), 13 deletions(-) diff --git a/packages/observer/__tests__/value.spec.ts b/packages/observer/__tests__/value.spec.ts index e69de29bb..fc49e8c3b 100644 --- a/packages/observer/__tests__/value.spec.ts +++ b/packages/observer/__tests__/value.spec.ts @@ -0,0 +1,54 @@ +import { value } from '../src/value' +import { effect, observable } from '../src/index' + +describe('observer/value', () => { + it('should hold a value', () => { + const a = value(1) + expect(a.value).toBe(1) + a.value = 2 + expect(a.value).toBe(2) + }) + + it('should be reactive', () => { + const a = value(1) + let dummy + effect(() => { + dummy = a.value + }) + expect(dummy).toBe(1) + a.value = 2 + expect(dummy).toBe(2) + }) + + it('should make nested properties reactive', () => { + const a = value({ + count: 1 + }) + let dummy + effect(() => { + dummy = a.value.count + }) + expect(dummy).toBe(1) + a.value.count = 2 + expect(dummy).toBe(2) + }) + + it('should work like a normal property when nested in an observable', () => { + const a = value(1) + const obj = observable({ + a, + b: { + c: a + } + }) + let dummy + effect(() => { + dummy = obj.a + }) + expect(dummy).toBe(1) + a.value++ + expect(dummy).toBe(2) + obj.a++ + expect(dummy).toBe(3) + }) +}) diff --git a/packages/observer/src/index.ts b/packages/observer/src/index.ts index 423126fc1..f3aea4e6d 100644 --- a/packages/observer/src/index.ts +++ b/packages/observer/src/index.ts @@ -24,11 +24,13 @@ import { DebuggerEvent } from './effect' +import { UnwrapBindings } from './value' + export { ReactiveEffect, ReactiveEffectOptions, DebuggerEvent } export { OperationTypes } from './operations' export { computed, ComputedValue } from './computed' export { lock, unlock } from './lock' -export { value, isValue, Value } from './value' +export { value, isValue, Value, UnwrapBindings } from './value' const collectionTypes: Set = new Set([Set, Map, WeakMap, WeakSet]) const observableValueRE = /^\[object (?:Object|Array|Map|Set|WeakMap|WeakSet)\]$/ @@ -42,7 +44,7 @@ const canObserve = (value: any): boolean => { ) } -type identity = (target?: T) => T +type ObservableFactory = (target?: T) => UnwrapBindings export const observable = ((target: any = {}): any => { // if trying to observe an immutable proxy, return the immutable version. @@ -60,7 +62,7 @@ export const observable = ((target: any = {}): any => { mutableHandlers, mutableCollectionHandlers ) -}) as identity +}) as ObservableFactory export const immutable = ((target: any = {}): any => { // value is a mutable observable, retrive its original and return @@ -75,7 +77,7 @@ export const immutable = ((target: any = {}): any => { immutableHandlers, immutableCollectionHandlers ) -}) as identity +}) as ObservableFactory function createObservable( target: any, diff --git a/packages/observer/src/value.ts b/packages/observer/src/value.ts index 534ee352a..c274eb026 100644 --- a/packages/observer/src/value.ts +++ b/packages/observer/src/value.ts @@ -9,6 +9,62 @@ export interface Value { value: T } +type UnwrapValue = T extends Value ? V : T extends {} ? U : T + +// A utility type that recursively unwraps value bindings nested inside an +// observable object. Unfortunately TS cannot do recursive types, but this +// should be enough for practical use cases... +export type UnwrapBindings = { + [key in keyof T]: UnwrapValue< + T[key], + { + [k2 in keyof T[key]]: UnwrapValue< + T[key][k2], + { + [k3 in keyof T[key][k2]]: UnwrapValue< + T[key][k2][k3], + { + [k4 in keyof T[key][k2][k3]]: UnwrapValue< + T[key][k2][k3][k4], + { + [k5 in keyof T[key][k2][k3][k4]]: UnwrapValue< + T[key][k2][k3][k4][k5], + { + [k6 in keyof T[key][k2][k3][k4][k5]]: UnwrapValue< + T[key][k2][k3][k4][k5][k6], + { + [k7 in keyof T[key][k2][k3][k4][k5][k6]]: UnwrapValue< + T[key][k2][k3][k4][k5][k6][k7], + { + [k8 in keyof T[key][k2][k3][k4][k5][k6][k7]]: UnwrapValue< + T[key][k2][k3][k4][k5][k6][k7][k8], + { + [k9 in keyof T[key][k2][k3][k4][k5][k6][k7][k8]]: UnwrapValue< + T[key][k2][k3][k4][k5][k6][k7][k8][k9], + { + [k10 in keyof T[key][k2][k3][k4][k5][k6][k7][k8][k9]]: UnwrapValue< + T[key][k2][k3][k4][k5][k6][k7][k8][k9][k10] + > + } + > + } + > + } + > + } + > + } + > + } + > + } + > + } + > + } + > +} + const convert = (val: any): any => (isObject(val) ? observable(val) : val) export function value(raw: T): Value { diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index a17289392..99416c562 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -1,19 +1,11 @@ import { VNode, normalizeVNode, VNodeChild } from './vnode' -import { ReactiveEffect, observable } from '@vue/observer' +import { ReactiveEffect, UnwrapBindings, observable } from '@vue/observer' import { isFunction, EMPTY_OBJ } from '@vue/shared' import { RenderProxyHandlers } from './componentProxy' import { ComponentPropsOptions, PropValidator } from './componentProps' -interface Value { - value: T -} - export type Data = { [key: string]: any } -type UnwrapBindings = { - [key in keyof T]: T[key] extends Value ? V : T[key] -} - type ExtractPropTypes = { readonly [key in keyof PropOptions]: PropOptions[key] extends PropValidator< infer V