From 68dbee835af152d50081d272078b1efb1bd55cd3 Mon Sep 17 00:00:00 2001 From: Michel Weststrate Date: Fri, 10 Jun 2016 10:46:08 +0200 Subject: [PATCH] cleanup --- src/api/computeddecorator.ts | 85 +++++++++------------------------- src/api/observabledecorator.ts | 43 +---------------- src/core/action.ts | 61 ++---------------------- src/utils/decorators.ts | 84 ++------------------------------- 4 files changed, 29 insertions(+), 244 deletions(-) diff --git a/src/api/computeddecorator.ts b/src/api/computeddecorator.ts index 5bc56cf51..609aa16b9 100644 --- a/src/api/computeddecorator.ts +++ b/src/api/computeddecorator.ts @@ -1,36 +1,15 @@ import {ValueMode, getValueModeFromValue, asStructure} from "../types/modifiers"; import {IObservableValue} from "./observable"; -import {asObservableObject, setObservableObjectInstanceProperty, defineObservableProperty} from "../types/observableobject"; -import {invariant, assertPropertyConfigurable} from "../utils/utils"; +import {asObservableObject, defineObservableProperty} from "../types/observableobject"; +import {invariant} from "../utils/utils"; import {createClassPropertyDecorator} from "../utils/decorators"; import {ComputedValue} from "../core/computedvalue"; -import {getDebugName} from "../types/type-utils"; export interface IComputedValueOptions { asStructure: boolean; } -/** - * Decorator for class properties: @computed get value() { return expr; }. - * For legacy purposes also invokable as ES5 observable created: `computed(() => expr)`; - */ -export function computed(func: () => T, scope?: any): IObservableValue; -export function computed(opts: IComputedValueOptions): (target: Object, key: string, baseDescriptor?: PropertyDescriptor) => void; -export function computed(target: Object, key: string | symbol, baseDescriptor?: PropertyDescriptor): void; -export function computed(targetOrExpr: any, keyOrScope?: any, baseDescriptor?: PropertyDescriptor, options?: IComputedValueOptions) { - if (arguments.length < 3 && typeof targetOrExpr === "function") - return computedExpr(targetOrExpr, keyOrScope); - invariant(!baseDescriptor || !baseDescriptor.set, `@observable properties cannot have a setter: ${keyOrScope}`); - return computedDecoratorImpl.apply(null, arguments); -// return computedDecorator.apply(null, arguments); -} - -function computedExpr(expr: () => T, scope?: any) { - const [mode, value] = getValueModeFromValue(expr, ValueMode.Recursive); - return new ComputedValue(value, scope, mode === ValueMode.Structure, value.name); -} - -const computedDecoratorImpl = createClassPropertyDecorator( +const computedDecorator = createClassPropertyDecorator( (target, name, _, decoratorArgs, originalDescriptor) => { const baseValue = originalDescriptor.get; invariant(typeof baseValue === "function", "@computed can only be used on getter functions, like: '@computed get myProps() { return ...; }'"); @@ -45,52 +24,30 @@ const computedDecoratorImpl = createClassPropertyDecorator( function (name) { return this.$mobx.values[name].get(); }, - function (name) { - invariant(false, `It is not allowed to assign new values to @computed properties: ${getDebugName(this)}.${name}`); - }, + throwingComputedValueSetter, false, - true, true ); +/** + * Decorator for class properties: @computed get value() { return expr; }. + * For legacy purposes also invokable as ES5 observable created: `computed(() => expr)`; + */ +export function computed(func: () => T, scope?: any): IObservableValue; +export function computed(opts: IComputedValueOptions): (target: Object, key: string, baseDescriptor?: PropertyDescriptor) => void; +export function computed(target: Object, key: string | symbol, baseDescriptor?: PropertyDescriptor): void; +export function computed(targetOrExpr: any, keyOrScope?: any, baseDescriptor?: PropertyDescriptor, options?: IComputedValueOptions) { + if (arguments.length < 3 && typeof targetOrExpr === "function") + return computedExpr(targetOrExpr, keyOrScope); + invariant(!baseDescriptor || !baseDescriptor.set, `@observable properties cannot have a setter: ${keyOrScope}`); + return computedDecorator.apply(null, arguments); +} -function computedDecorator(target: any, key?: any, baseDescriptor?: PropertyDescriptor, options?: IComputedValueOptions): any { - // invoked as decorator factory with options - if (arguments.length === 1) { - const options = target; - return (target, key, baseDescriptor) => computedDecorator.call(null, target, key, baseDescriptor, options); - } - invariant(baseDescriptor && baseDescriptor.hasOwnProperty("get"), "@computed can only be used on getter functions, like: '@computed get myProps() { return ...; }'"); - assertPropertyConfigurable(target, key); - - const descriptor: PropertyDescriptor = {}; - const getter = baseDescriptor.get; - - invariant(typeof target === "object", `The @observable decorator can only be used on objects`, key); - invariant(typeof getter === "function", `@observable expects a getter function if used on a property.`, key); - invariant(!baseDescriptor.set, `@observable properties cannot have a setter.`, key); - invariant(getter.length === 0, `@observable getter functions should not take arguments.`, key); - - descriptor.configurable = true; - descriptor.enumerable = false; - descriptor.get = function() { - setObservableObjectInstanceProperty( - asObservableObject(this, undefined, ValueMode.Recursive), - key, - options && options.asStructure === true ? asStructure(getter) : getter - ); - return this[key]; - }; - - // by default, assignments to properties without setter are ignored. Let's fail fast instead. - descriptor.set = throwingComputedValueSetter; - if (!baseDescriptor) { - Object.defineProperty(target, key, descriptor); // For typescript - } else { - return descriptor; - } +function computedExpr(expr: () => T, scope?: any) { + const [mode, value] = getValueModeFromValue(expr, ValueMode.Recursive); + return new ComputedValue(value, scope, mode === ValueMode.Structure, value.name); } export function throwingComputedValueSetter() { throw new Error(`[ComputedValue] It is not allowed to assign new values to computed properties.`); -} \ No newline at end of file +} diff --git a/src/api/observabledecorator.ts b/src/api/observabledecorator.ts index 619f5ae00..05a8b7156 100644 --- a/src/api/observabledecorator.ts +++ b/src/api/observabledecorator.ts @@ -1,11 +1,8 @@ import {ValueMode, asReference} from "../types/modifiers"; import {allowStateChanges} from "../core/action"; -import {computed} from "../api/computeddecorator"; import {asObservableObject, defineObservableProperty, setPropertyValue} from "../types/observableobject"; -import {invariant, assertPropertyConfigurable, deprecated} from "../utils/utils"; -import {checkIfStateModificationsAreAllowed} from "../core/derivation"; +import {invariant, assertPropertyConfigurable} from "../utils/utils"; import {createClassPropertyDecorator} from "../utils/decorators"; -import {ObservableValue} from "../types/observablevalue"; const decoratorImpl = createClassPropertyDecorator( (target, name, baseValue) => { @@ -42,42 +39,4 @@ export function observableDecorator(target: Object, key: string, baseDescriptor: assertPropertyConfigurable(target, key); invariant(!baseDescriptor || !baseDescriptor.get, "@observable can not be used on getters, use @computed instead"); return decoratorImpl.apply(null, arguments); - - // - In typescript, observable annotations are invoked on the prototype, not on actual instances, - // so upon invocation, determine the 'this' instance, and define a property on the - // instance as well (that hides the propotype property) - // - In babel, the initial value is passed as the closure baseDiscriptor.initializer' -/* - if (baseDescriptor && baseDescriptor.hasOwnProperty("get")) { - deprecated("Using @observable on computed values is deprecated. Use @computed instead."); - return computed.apply(null, arguments); - } - const descriptor: PropertyDescriptor = {}; - - invariant(typeof target === "object", `The @observable decorator can only be used on objects`, key); - descriptor.configurable = true; - descriptor.enumerable = true; - descriptor.get = function() { - let baseValue = undefined; - if (baseDescriptor && (baseDescriptor).initializer) { // For babel - baseValue = (baseDescriptor).initializer(); - if (typeof baseValue === "function") - baseValue = asReference(baseValue); - } - // the getter might create a reactive property lazily, so this might even happen during a view. - allowStateChanges(true, () => { - setObservableObjectProperty(asObservableObject(this, undefined, ValueMode.Recursive), key, baseValue); - }); - return this[key]; - }; - descriptor.set = function(value) { - setObservableObjectProperty(asObservableObject(this, undefined, ValueMode.Recursive), key, typeof value === "function" ? asReference(value) : value); - }; - if (!baseDescriptor) { - Object.defineProperty(target, key, descriptor); // For typescript - } else { - return descriptor; - } -*/ } - diff --git a/src/core/action.ts b/src/core/action.ts index 0d31de2de..a01777e23 100644 --- a/src/core/action.ts +++ b/src/core/action.ts @@ -18,7 +18,10 @@ const actionFieldDecorator = createClassPropertyDecorator( }); } else { // bound instance methods - addBoundAction(target, key, wrappedAction); + Object.defineProperty(target, key, { + configurable: true, enumerable: false, writable: false, + value: wrappedAction + }); } }, function (key) { @@ -44,62 +47,6 @@ export function action(arg1, arg2?, arg3?, arg4?): any { return actionFieldDecorator.apply(null, arguments); } -function actionDecorator(name: string, target: any, key: string, descriptor: PropertyDescriptor) { - if (descriptor === undefined) { - // typescript: @action f = () => { } - typescriptActionValueDecorator(name, target, key); - return; - } - if (descriptor.value === undefined && typeof (descriptor as any).initializer === "function") { - // babel: @action f = () => { } - return babelActionValueDecorator(name, target, key, descriptor); - } - const base = descriptor.value; - descriptor.value = actionImplementation(name, base); -} - -/** - * Decorators the following pattern @action method = () => {} by desugaring it to method = action(() => {}) - */ -function babelActionValueDecorator(name: string, target, prop, descriptor): PropertyDescriptor { - return { - configurable: true, - enumerable: false, - get: function() { - const v = descriptor.initializer.call(this); - invariant(typeof v === "function", `Babel @action decorator expects the field '${prop} to be initialized with a function`); - const implementation = action(name, v); - addBoundAction(this, prop, implementation); - return implementation; - }, - set: function() { - invariant(false, `Babel @action decorator: field '${prop}' not initialized`); - } - }; -} - -function typescriptActionValueDecorator(name: string, target, prop) { - Object.defineProperty(target, prop, { - configurable: true, - enumerable: false, - get: function() { - invariant(false, `TypeScript @action decorator: field '${prop}' not initialized`); - }, - set: function(v) { - invariant(typeof v === "function", `TypeScript @action decorator expects the field '${prop} to be initialized with a function`); - addBoundAction(this, prop, action(name, v)); - } - }); -} - -function addBoundAction(target, prop, implementation) { - Object.defineProperty(target, prop, { - enumerable: false, - writable: false, - value: implementation - }); -} - export function actionImplementation(actionName: string, fn: Function): Function { invariant(typeof fn === "function", "`action` can only be invoked on functions"); invariant(typeof actionName === "string" && actionName.length > 0, `actions should have valid names, got: '${actionName}'`); diff --git a/src/utils/decorators.ts b/src/utils/decorators.ts index ea6d48372..9a85b7b25 100644 --- a/src/utils/decorators.ts +++ b/src/utils/decorators.ts @@ -26,12 +26,7 @@ export function createClassPropertyDecorator( /** * Can this decorator invoked with arguments? e.g. @decorator(args) */ - allowCustomArguments: boolean, - /** - * Usually the initial value will be based on either the .value or .initializer or initial assignment to the property. - * If this flag is set, the initial value will be the original defined get value. This is used by @computed. - */ - useGetterAsInitialValue: boolean = false + allowCustomArguments: boolean ): any { function classPropertyDecorator(target: any, key: string, descriptor, customArgs?: IArguments) { invariant(allowCustomArguments || quacksLikeADecorator(arguments), "This function is a decorator, but it wasn't invoked like a decorator"); @@ -63,12 +58,11 @@ export function createClassPropertyDecorator( } const {value, initializer} = descriptor; - const getter = descriptor.get; target.__mobxLazyInitializers.push(instance => { onInitialize( instance, key, - useGetterAsInitialValue ? getter : (initializer ? initializer.call(instance) : value), + (initializer ? initializer.call(instance) : value), customArgs, descriptor ); @@ -115,16 +109,6 @@ function typescriptInitializeProperty(instance, key, v, onInitialize, customArgs onInitialize(instance, key, v, customArgs, baseDescriptor); } -function isPropInitialized(instance, prop) { - if (instance["__initialized" + prop] !== true) { - Object.defineProperty(instance, "__initialized" + prop, { - configurable: false, enumerable: false, writable: false, value: true - }); - return false; - } - return true; -} - export function runLazyInitializers(instance) { if (instance.__mobxDidRunLazyInitializers === true) return; @@ -139,68 +123,6 @@ export function runLazyInitializers(instance) { } } -// values on prototype: @action -// fields on object: @observable -// getters on object: @computed -// enumerable (on protot and self!) -// custom args -export function decoratorFactory( - allowCustomArguments: boolean, - allowValuesOnPrototype: boolean, - getDescriptor: (target, propName, initialValue: any, customArgs: IArguments) => PropertyDescriptor -): (target, prop: string, descriptor?) => PropertyDescriptor { - function theDecorator(target: Object, key: string, baseDescriptor, customArgs?: IArguments): any { - invariant(allowCustomArguments || quacksLikeADecorator(arguments), "This function is a decorator, but it wasn't invoked like a decorator"); - const intermediateDescriptor = { - configurable: true, - enumerable: false, - get: illegalState, - set: illegalState - }; - if (baseDescriptor === undefined) { - /* typescript */ - // TODO: or return? - Object.defineProperty(baseDescriptor, key, intermediateDescriptor); - } else { - /* babel */ - if (typeof baseDescriptor.initializer === "function") { - /* initializers are used for instance properties*/ - (intermediateDescriptor as any).initializer = function () { - Object.defineProperty(this, key, getDescriptor(this, key, baseDescriptor.initializer(), customArgs)); - }; - return intermediateDescriptor; - } else if (baseDescriptor.hasOwnProperty("value")) { - /* values are used for values that can be defined on the prototype, e.g. methos */ - invariant(allowValuesOnPrototype, "This decorator can only be used on field assignments"); - /* prototype props cannot be reconfigured, so just set a new value */ - const _temp = getDescriptor(target, key, baseDescriptor.value, customArgs); - baseDescriptor.value = _temp.value; - // TODO: mweh? just return _temp? - return baseDescriptor; - } else { - invariant(false, "Illegal state while applying decorator"); - } - } - } - - if (allowCustomArguments) { - /** If custom arguments are allowed, we should return a function that returns a decorator */ - return function() { - /** Direct invocation: @decorator bla */ - if (quacksLikeADecorator(arguments)) - return theDecorator.apply(null, arguments); - /** Indirect invocation: @decorator(args) bla */ - const outerArgs = arguments; - return (target, key, descriptor) => theDecorator(target, key, descriptor, outerArgs); - }; - } - return theDecorator; -} - -function illegalState() { - invariant(false, "Illegal state while trying to apply decorator"); -} - function quacksLikeADecorator(args: IArguments): boolean { return (args.length === 2 || args.length === 3) && typeof args[1] === "string"; -} \ No newline at end of file +}