diff --git a/README.md b/README.md
index 3c00f5f7c..554f1a4d4 100644
--- a/README.md
+++ b/README.md
@@ -109,6 +109,24 @@ decorate(Todo, {
})
```
+For applying multiple decorators on a single property, you can pass an array of decorators. The decorators application order is from right to left.
+```javascript
+import { decorate, observable } from "mobx"
+import { serializable, primitive } from "serializr"
+import persist from "mobx-persist";
+
+class Todo {
+ id = Math.random();
+ title = "";
+ finished = false;
+}
+decorate(Todo, {
+ title: [serializable(primitive), persist("object"), observable],
+ finished: [serializable(primitive), observable]
+})
+```
+Note: Not all decorators can be composed together, and this functionality is just best-effort. Some decorators affect the instance directly and can 'hide' the effect of other decorators that only change the prototype.
+
### Computed values
Egghead.io lesson 3: computed values
diff --git a/package.json b/package.json
index d20c7d66e..80ae08262 100644
--- a/package.json
+++ b/package.json
@@ -67,6 +67,7 @@
"rollup": "^0.41.6",
"rollup-plugin-filesize": "^1.3.2",
"rollup-plugin-node-resolve": "^3.0.0",
+ "serializr": "^1.3.0",
"size-limit": "^0.2.0",
"tape": "^4.2.2",
"ts-jest": "^22.0.0",
@@ -116,4 +117,4 @@
"/node_modules/"
]
}
-}
\ No newline at end of file
+}
diff --git a/src/api/decorate.ts b/src/api/decorate.ts
index dcf649497..fad6e4705 100644
--- a/src/api/decorate.ts
+++ b/src/api/decorate.ts
@@ -2,25 +2,43 @@ import { invariant, isPlainObject } from "../internal"
export function decorate(
clazz: new (...args: any[]) => T,
- decorators: { [P in keyof T]?: MethodDecorator | PropertyDecorator }
+ decorators: {
+ [P in keyof T]?:
+ | MethodDecorator
+ | PropertyDecorator
+ | Array
+ | Array
+ }
): void
export function decorate(
object: T,
- decorators: { [P in keyof T]?: MethodDecorator | PropertyDecorator }
+ decorators: {
+ [P in keyof T]?:
+ | MethodDecorator
+ | PropertyDecorator
+ | Array
+ | Array
+ }
): T
export function decorate(thing: any, decorators: any) {
process.env.NODE_ENV !== "production" &&
invariant(isPlainObject(decorators), "Decorators should be a key value map")
const target = typeof thing === "function" ? thing.prototype : thing
for (let prop in decorators) {
- const decorator = decorators[prop]
+ let propertyDecorators = decorators[prop]
+ if (!Array.isArray(propertyDecorators)) {
+ propertyDecorators = [propertyDecorators]
+ }
process.env.NODE_ENV !== "production" &&
invariant(
- typeof decorator === "function",
- `Decorate: expected a decorator function for '${prop}'`
+ propertyDecorators.every(decorator => typeof decorator === "function"),
+ `Decorate: expected a decorator function or array of decorator functions for '${prop}'`
)
const descriptor = Object.getOwnPropertyDescriptor(target, prop)
- const newDescriptor = decorator(target, prop, descriptor)
+ const newDescriptor = propertyDecorators.reduce(
+ (accDescriptor, decorator) => decorator(target, prop, accDescriptor),
+ descriptor
+ )
if (newDescriptor) Object.defineProperty(target, prop, newDescriptor)
}
return thing
diff --git a/test/base/decorate.js b/test/base/decorate.js
index 7b7f3f199..8fec69602 100644
--- a/test/base/decorate.js
+++ b/test/base/decorate.js
@@ -14,9 +14,12 @@ import {
isComputedProp,
spy,
isAction,
- decorate
+ decorate,
+ reaction
} from "../../src/mobx"
+import { serializable, primitive, serialize, deserialize } from "serializr"
+
test("decorate should work", function() {
class Box {
// @ts-ignore
@@ -368,3 +371,79 @@ test("decorate should not allow @observable on getter", function() {
expect(() => obj.x).toThrow(/"y"/)
expect(() => obj.y).toThrow()
})
+
+test("decorate a function property with two decorators", function() {
+ let callsCount = 0
+ let spyCount = 0
+ const spyDisposer = spy(ev => {
+ if (ev.type === "action" && ev.name === "fn") spyCount++
+ })
+
+ const countFunctionCallsDecorator = (target, key, descriptor) => {
+ const func = descriptor.value
+ descriptor.value = function wrapper(...args) {
+ const result = func.call(this, ...args)
+ callsCount++
+ return result
+ }
+ for (const key in func) {
+ descriptor.value[key] = func[key]
+ }
+ return descriptor
+ }
+
+ class Obj {
+ fn() {}
+ }
+
+ decorate(Obj, {
+ fn: [action("fn"), countFunctionCallsDecorator]
+ })
+
+ const obj = new Obj()
+
+ expect(isAction(obj.fn)).toBe(true)
+
+ obj.fn()
+
+ expect(callsCount).toEqual(1)
+ expect(spyCount).toEqual(1)
+
+ obj.fn()
+
+ expect(callsCount).toEqual(2)
+ expect(spyCount).toEqual(2)
+
+ spyDisposer()
+})
+
+test("decorate a property with two decorators", function() {
+ let updatedByAutorun
+
+ class Obj {
+ x = null
+ }
+
+ decorate(Obj, {
+ x: [serializable(primitive()), observable]
+ })
+
+ const obj = deserialize(Obj, {
+ x: 0
+ })
+
+ const d = autorun(() => {
+ updatedByAutorun = obj.x
+ })
+
+ expect(isObservableProp(obj, "x")).toBe(true)
+ expect(updatedByAutorun).toEqual(0)
+
+ obj.x++
+
+ expect(obj.x).toEqual(1)
+ expect(updatedByAutorun).toEqual(1)
+ expect(serialize(obj).x).toEqual(1)
+
+ d()
+})
diff --git a/yarn.lock b/yarn.lock
index 695344fc2..848809d4c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5225,6 +5225,10 @@ send@0.15.3:
range-parser "~1.2.0"
statuses "~1.3.1"
+serializr@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/serializr/-/serializr-1.3.0.tgz#6c7f977461d54a24bb1f17a03ed0ce61d239b010"
+
serve-static@1.12.3:
version "1.12.3"
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.12.3.tgz#9f4ba19e2f3030c547f8af99107838ec38d5b1e2"