Skip to content

Commit

Permalink
fix(reactive): fix tojs recursive dependence stack overflow (#1245)
Browse files Browse the repository at this point in the history
* test(reactive): move externals tests to new file

* fix(reactive): fix tojs recursive dependence stack overflow
  • Loading branch information
gwsbhqt authored Apr 18, 2021
1 parent c43516c commit 675df0c
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 97 deletions.
68 changes: 1 addition & 67 deletions packages/reactive/src/__tests__/annotations.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { observable, action, model } from '../'
import { autorun, reaction } from '../autorun'
import { observe } from '../observe'
import { isObservable, isSupportObservable, markObservable, markRaw } from '../externals'
import { isObservable } from '../externals'

test('observable annotation', () => {
const obs = observable<any>({
Expand Down Expand Up @@ -243,69 +243,3 @@ test('computed with computed array length', () => {
obs.arr = []
expect(handler).toBeCalledWith(false)
})

test('is support observable', () => {
const obs = observable<any>({ aa: 111 })
expect(isSupportObservable(obs)).toBeFalsy()
expect(isSupportObservable(null)).toBeFalsy()
expect(isSupportObservable([])).toBeTruthy()
expect(isSupportObservable({})).toBeTruthy()
expect(isSupportObservable({ $$typeof: {}, _owner: {} })).toBeFalsy()
expect(isSupportObservable({ _isAMomentObject: {} })).toBeFalsy()
expect(isSupportObservable({ _isJSONSchemaObject: {} })).toBeFalsy()
expect(isSupportObservable({ toJS: () => {} })).toBeFalsy()
expect(isSupportObservable({ toJSON: () => {} })).toBeFalsy()
expect(isSupportObservable(new Map())).toBeTruthy()
expect(isSupportObservable(new WeakMap())).toBeTruthy()
expect(isSupportObservable(new Set())).toBeTruthy()
expect(isSupportObservable(new WeakSet())).toBeTruthy()
})

describe('mark operation', () => {
test('plain object should be observable', () => {
const obs = observable<any>({ aa: 111 })
expect(isObservable(obs)).toBeTruthy()
})

test('class instance should be observable', () => {
class Class {}
const obs = observable<any>(new Class())
const obs2 = observable<any>(new Class())
expect(isObservable(obs)).toBeTruthy()
expect(isObservable(obs2)).toBeTruthy()
})

test('object with toJS function should NOT be observable', () => {
const obs = observable<any>({ aa: 111, toJS: () => {} })
expect(isObservable(obs)).toBeFalsy()
})

test('plain object marked as raw should NOT be observable', () => {
const obs = observable<any>(markRaw({ aa: 111 }))
expect(isObservable(obs)).toBeFalsy()
})

test('class marked as raw instance should NOT be observable', () => {
class Class {}
markRaw(Class)
const obs = observable<any>(new Class())
const obs2 = observable<any>(new Class())
expect(isObservable(obs)).toBeFalsy()
expect(isObservable(obs2)).toBeFalsy()
})

test('object with toJS function marked as observable should be observable', () => {
const obs = observable<any>(markObservable({ aa: 111, toJS: () => {} }))
expect(isObservable(obs)).toBeTruthy()
})

test('plain object marked as raw and observable should NOT be observable', () => {
const obs = observable<any>(markRaw(markObservable({ aa: 111 })))
expect(isObservable(obs)).toBeFalsy()
})

test('plain object marked as observable and raw should NOT be observable', () => {
const obs = observable<any>(markObservable(markRaw({ aa: 111 })))
expect(isObservable(obs)).toBeFalsy()
})
})
75 changes: 75 additions & 0 deletions packages/reactive/src/__tests__/externals.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { isObservable, isSupportObservable, markObservable, markRaw, observable, toJS } from ".."

test('is support observable', () => {
const obs = observable<any>({ aa: 111 })
expect(isSupportObservable(obs)).toBeFalsy()
expect(isSupportObservable(null)).toBeFalsy()
expect(isSupportObservable([])).toBeTruthy()
expect(isSupportObservable({})).toBeTruthy()
expect(isSupportObservable({ $$typeof: {}, _owner: {} })).toBeFalsy()
expect(isSupportObservable({ _isAMomentObject: {} })).toBeFalsy()
expect(isSupportObservable({ _isJSONSchemaObject: {} })).toBeFalsy()
expect(isSupportObservable({ toJS: () => {} })).toBeFalsy()
expect(isSupportObservable({ toJSON: () => {} })).toBeFalsy()
expect(isSupportObservable(new Map())).toBeTruthy()
expect(isSupportObservable(new WeakMap())).toBeTruthy()
expect(isSupportObservable(new Set())).toBeTruthy()
expect(isSupportObservable(new WeakSet())).toBeTruthy()
})

describe('mark operation', () => {
test('plain object should be observable', () => {
const obs = observable<any>({ aa: 111 })
expect(isObservable(obs)).toBeTruthy()
})

test('class instance should be observable', () => {
class Class {}
const obs = observable<any>(new Class())
const obs2 = observable<any>(new Class())
expect(isObservable(obs)).toBeTruthy()
expect(isObservable(obs2)).toBeTruthy()
})

test('object with toJS function should NOT be observable', () => {
const obs = observable<any>({ aa: 111, toJS: () => {} })
expect(isObservable(obs)).toBeFalsy()
})

test('plain object marked as raw should NOT be observable', () => {
const obs = observable<any>(markRaw({ aa: 111 }))
expect(isObservable(obs)).toBeFalsy()
})

test('class marked as raw instance should NOT be observable', () => {
class Class {}
markRaw(Class)
const obs = observable<any>(new Class())
const obs2 = observable<any>(new Class())
expect(isObservable(obs)).toBeFalsy()
expect(isObservable(obs2)).toBeFalsy()
})

test('object with toJS function marked as observable should be observable', () => {
const obs = observable<any>(markObservable({ aa: 111, toJS: () => {} }))
expect(isObservable(obs)).toBeTruthy()
})

test('plain object marked as raw and observable should NOT be observable', () => {
const obs = observable<any>(markRaw(markObservable({ aa: 111 })))
expect(isObservable(obs)).toBeFalsy()
})

test('plain object marked as observable and raw should NOT be observable', () => {
const obs = observable<any>(markObservable(markRaw({ aa: 111 })))
expect(isObservable(obs)).toBeFalsy()
})
})

test('recursive references tojs', () => {
const obj: any = { aa: 111 }
obj.obj = obj
const obs = observable<any>(obj)
obs.obs = obs
expect(toJS(obs)).toBeTruthy()
})
66 changes: 36 additions & 30 deletions packages/reactive/src/externals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,39 +78,45 @@ export const markObservable = <T>(target: T): T => {
export const raw = <T>(target: T): T => ProxyRaw.get(target as any)

export const toJS = <T>(values: T): T => {
if (!isObservable(values)) {
return values
}
if (Array.isArray(values)) {
const res: any = []
values.forEach((item) => {
res.push(toJS(item))
})
return res
} else if (isPlainObj(values)) {
if ('$$typeof' in values && '_owner' in values) {
return values
}
if (values['_isAMomentObject']) {
const visited = new WeakSet<any>()

const tojs: typeof toJS = (values) => {
if (visited.has(values)) {
return values
}
if (values['_isJSONSchemaObject']) {
} else if (!isObservable(values)) {
return values
}
if (isFn(values['toJS'])) {
return values['toJS']()
}
if (isFn(values['toJSON'])) {
return values['toJSON']()
}
const res: any = {}
for (const key in values) {
if (Object.hasOwnProperty.call(values, key)) {
res[key] = toJS(values[key])
} else if (isArr(values)) {
visited.add(values)
const res: any = []
values.forEach((item) => {
res.push(tojs(item))
})
return res
} else if (isPlainObj(values)) {
if ('$$typeof' in values && '_owner' in values) {
return values
} else if (values['_isAMomentObject']) {
return values
} else if (values['_isJSONSchemaObject']) {
return values
} else if (isFn(values['toJS'])) {
return values['toJS']()
} else if (isFn(values['toJSON'])) {
return values['toJSON']()
} else {
visited.add(values)
const res: any = {}
for (const key in values) {
if (Object.hasOwnProperty.call(values, key)) {
res[key] = tojs(values[key])
}
}
return res
}
} else {
return values
}
return res
} else {
return values
}

return tojs(values)
}

0 comments on commit 675df0c

Please sign in to comment.