Skip to content

Commit

Permalink
Merge pull request #589 from mobxjs/fix-566-toJS
Browse files Browse the repository at this point in the history
Implemented `toJS` in such a way that it only recurses into observables
  • Loading branch information
mweststrate authored Oct 3, 2016
2 parents bd369a2 + 6672288 commit 1c5dde5
Show file tree
Hide file tree
Showing 6 changed files with 697 additions and 311 deletions.
61 changes: 56 additions & 5 deletions src/api/tojs.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,63 @@
import {isObservableArray} from "../types/observablearray";
import {isObservableObject} from "../types/observableobject";
import {isObservableMap} from "../types/observablemap";
import {isObservableValue} from "../types/observablevalue";
import {isObservable} from "../api/isobservable";
import {deprecated} from "../utils/utils";

/**
* Basically, a deep clone, so that no reactive property will exist anymore.
*/
export function toJS(source, detectCycles: boolean = true, __alreadySeen: [any, any][] = null) {
// TODO: in 3.0, detectCycles should default to false?
// optimization: using ES6 map would be more efficient!
// optimization: lift this function outside toJS, this makes recursion expensive
function cache(value) {
if (detectCycles)
__alreadySeen.push([source, value]);
return value;
}
if (isObservable(source)) {
if (detectCycles && __alreadySeen === null)
__alreadySeen = [];
if (detectCycles && source !== null && typeof source === "object") {
for (let i = 0, l = __alreadySeen.length; i < l; i++)
if (__alreadySeen[i][0] === source)
return __alreadySeen[i][1];
}

if (isObservableArray(source)) {
const res = cache([]);
const toAdd = source.map(value => toJS(value, detectCycles, __alreadySeen));
res.length = toAdd.length;
for (let i = 0, l = toAdd.length; i < l; i++)
res[i] = toAdd[i];
return res;
}
if (isObservableObject(source)) {
const res = cache({});
for (let key in source)
res[key] = toJS(source[key], detectCycles, __alreadySeen);
return res;
}
if (isObservableMap(source)) {
const res = cache({});
source.forEach(
(value, key) => res[key] = toJS(value, detectCycles, __alreadySeen)
);
return res;
}
if (isObservableValue(source))
return toJS(source.get(), detectCycles, __alreadySeen);
}
return source;
}

/**
* Basically, a deep clone, so that no reactive property will exist anymore.
*/
export function toJSlegacy(source, detectCycles: boolean = true, __alreadySeen: [any, any][] = null) {
deprecated("toJSlegacy is deprecated and will be removed in the next major. Use `toJS` instead. See #566");
// optimization: using ES6 map would be more efficient!
function cache(value) {
if (detectCycles)
Expand All @@ -28,7 +79,7 @@ export function toJS(source, detectCycles: boolean = true, __alreadySeen: [any,
return source;
if (Array.isArray(source) || isObservableArray(source)) {
const res = cache([]);
const toAdd = source.map(value => toJS(value, detectCycles, __alreadySeen));
const toAdd = source.map(value => toJSlegacy(value, detectCycles, __alreadySeen));
res.length = toAdd.length;
for (let i = 0, l = toAdd.length; i < l; i++)
res[i] = toAdd[i];
Expand All @@ -37,22 +88,22 @@ export function toJS(source, detectCycles: boolean = true, __alreadySeen: [any,
if (isObservableMap(source)) {
const res = cache({});
source.forEach(
(value, key) => res[key] = toJS(value, detectCycles, __alreadySeen)
(value, key) => res[key] = toJSlegacy(value, detectCycles, __alreadySeen)
);
return res;
}
if (isObservableValue(source))
return toJS(source.get(), detectCycles, __alreadySeen);
return toJSlegacy(source.get(), detectCycles, __alreadySeen);
if (typeof source === "object") {
const res = cache({});
for (let key in source)
res[key] = toJS(source[key], detectCycles, __alreadySeen);
res[key] = toJSlegacy(source[key], detectCycles, __alreadySeen);
return res;
}
return source;
}

export function toJSON(source, detectCycles: boolean = true, __alreadySeen: [any, any][] = null) {
deprecated("toJSON is deprecated. Use toJS instead");
return toJS.apply(null, arguments);
return toJSlegacy.apply(null, arguments);
}
2 changes: 1 addition & 1 deletion src/mobx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export { autorun, autorunAsync, autorunUntil, when, reaction } from "./api/auto
export { action, isAction, runInAction } from "./api/action";

export { expr } from "./api/expr";
export { toJSON, toJS } from "./api/tojs";
export { toJSON, toJS, toJSlegacy } from "./api/tojs";
export { ITransformer, createTransformer } from "./api/createtransformer";
export { whyRun } from "./api/whyrun";

Expand Down
3 changes: 2 additions & 1 deletion test/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,14 @@ test('correct api should be exposed', function(t) {
'runInAction',
'spy',
'toJS',
'toJSlegacy',
'toJSON',
'transaction',
'untracked',
'useStrict',
'when',
'whyRun'
]);
].sort());
t.equals(Object.keys(mobx).filter(function(key) {
return mobx[key] == undefined;
}).length, 0);
Expand Down
Loading

0 comments on commit 1c5dde5

Please sign in to comment.