wip: test + typing for value

This commit is contained in:
Evan You 2019-05-29 22:11:33 +08:00
parent ef4fde3522
commit dde6c151e4
4 changed files with 117 additions and 13 deletions

View File

@ -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)
})
})

View File

@ -24,11 +24,13 @@ import {
DebuggerEvent DebuggerEvent
} from './effect' } from './effect'
import { UnwrapBindings } from './value'
export { ReactiveEffect, ReactiveEffectOptions, DebuggerEvent } export { ReactiveEffect, ReactiveEffectOptions, DebuggerEvent }
export { OperationTypes } from './operations' export { OperationTypes } from './operations'
export { computed, ComputedValue } from './computed' export { computed, ComputedValue } from './computed'
export { lock, unlock } from './lock' export { lock, unlock } from './lock'
export { value, isValue, Value } from './value' export { value, isValue, Value, UnwrapBindings } from './value'
const collectionTypes: Set<any> = new Set([Set, Map, WeakMap, WeakSet]) const collectionTypes: Set<any> = new Set([Set, Map, WeakMap, WeakSet])
const observableValueRE = /^\[object (?:Object|Array|Map|Set|WeakMap|WeakSet)\]$/ const observableValueRE = /^\[object (?:Object|Array|Map|Set|WeakMap|WeakSet)\]$/
@ -42,7 +44,7 @@ const canObserve = (value: any): boolean => {
) )
} }
type identity = <T>(target?: T) => T type ObservableFactory = <T>(target?: T) => UnwrapBindings<T>
export const observable = ((target: any = {}): any => { export const observable = ((target: any = {}): any => {
// if trying to observe an immutable proxy, return the immutable version. // if trying to observe an immutable proxy, return the immutable version.
@ -60,7 +62,7 @@ export const observable = ((target: any = {}): any => {
mutableHandlers, mutableHandlers,
mutableCollectionHandlers mutableCollectionHandlers
) )
}) as identity }) as ObservableFactory
export const immutable = ((target: any = {}): any => { export const immutable = ((target: any = {}): any => {
// value is a mutable observable, retrive its original and return // value is a mutable observable, retrive its original and return
@ -75,7 +77,7 @@ export const immutable = ((target: any = {}): any => {
immutableHandlers, immutableHandlers,
immutableCollectionHandlers immutableCollectionHandlers
) )
}) as identity }) as ObservableFactory
function createObservable( function createObservable(
target: any, target: any,

View File

@ -9,6 +9,62 @@ export interface Value<T> {
value: T value: T
} }
type UnwrapValue<T, U = T> = T extends Value<infer V> ? 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<T> = {
[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) const convert = (val: any): any => (isObject(val) ? observable(val) : val)
export function value<T>(raw: T): Value<T> { export function value<T>(raw: T): Value<T> {

View File

@ -1,19 +1,11 @@
import { VNode, normalizeVNode, VNodeChild } from './vnode' 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 { isFunction, EMPTY_OBJ } from '@vue/shared'
import { RenderProxyHandlers } from './componentProxy' import { RenderProxyHandlers } from './componentProxy'
import { ComponentPropsOptions, PropValidator } from './componentProps' import { ComponentPropsOptions, PropValidator } from './componentProps'
interface Value<T> {
value: T
}
export type Data = { [key: string]: any } export type Data = { [key: string]: any }
type UnwrapBindings<T> = {
[key in keyof T]: T[key] extends Value<infer V> ? V : T[key]
}
type ExtractPropTypes<PropOptions> = { type ExtractPropTypes<PropOptions> = {
readonly [key in keyof PropOptions]: PropOptions[key] extends PropValidator< readonly [key in keyof PropOptions]: PropOptions[key] extends PropValidator<
infer V infer V