diff --git a/src/reactivity/unwrap.ts b/src/reactivity/unwrap.ts index a59fb29d..e7f591fe 100644 --- a/src/reactivity/unwrap.ts +++ b/src/reactivity/unwrap.ts @@ -2,19 +2,24 @@ import { isRef } from './ref' import { proxy, isFunction, isPlainObject, isArray } from '../utils' import { isReactive } from './reactive' -export function unwrapRefProxy(value: any) { +export function unwrapRefProxy(value: any, map = new WeakMap()) { + if (map.has(value)) { + return map.get(value) + } + if ( isFunction(value) || - isRef(value) || isArray(value) || isReactive(value) || !isPlainObject(value) || - !Object.isExtensible(value) + !Object.isExtensible(value) || + isRef(value) ) { return value } const obj: any = {} + map.set(value, obj) // copy symbols over Object.getOwnPropertySymbols(value).forEach( @@ -30,7 +35,7 @@ export function unwrapRefProxy(value: any) { proxy(obj, k, { get, set }) } else { - obj[k] = unwrapRefProxy(r) + obj[k] = unwrapRefProxy(r, map) } } diff --git a/test/setup.spec.js b/test/setup.spec.js index 98e50fcd..99e30eff 100644 --- a/test/setup.spec.js +++ b/test/setup.spec.js @@ -467,105 +467,161 @@ describe('setup', () => { .then(done) }) - it('should unwrap on the template', () => { - const vm = new Vue({ - setup() { - const r = ref('r') - const nested = { - a: ref('a'), - aa: { - b: ref('aa'), - bb: { - cc: ref('aa'), - c: 'aa', - }, - }, + describe('setup unwrap', () => { + test('ref', () => { + const vm = new Vue({ + setup() { + const r = ref('r') + + const refList = ref([ref('1'), ref('2'), ref('3')]) + const list = [ref('a'), ref('b')] + + return { + r, + refList, + list, + } + }, + template: `
+

{{r}}

+

{{list}}

+

{{refList}}

+
`, + }).$mount() + + expect(vm.$el.querySelector('#r').textContent).toBe('r') + + // shouldn't unwrap arrays + expect( + JSON.parse(vm.$el.querySelector('#list').textContent) + ).toMatchObject([{ value: 'a' }, { value: 'b' }]) + expect( + JSON.parse(vm.$el.querySelector('#refList').textContent) + ).toMatchObject([{ value: '1' }, { value: '2' }, { value: '3' }]) + }) - aaa: reactive({ - b: ref('aaa'), - bb: { - c: ref('aaa'), - cc: 'aaa', + test('nested', () => { + const vm = new Vue({ + setup() { + const nested = { + a: ref('a'), + aa: { + b: ref('aa'), + bb: { + cc: ref('aa'), + c: 'aa', + }, }, - }), - - aaaa: { - b: [1], - bb: ref([1]), - bbb: reactive({ - c: [1], - cc: ref([1]), + + aaa: reactive({ + b: ref('aaa'), + bb: { + c: ref('aaa'), + cc: 'aaa', + }, }), - bbbb: [ref(1)], - }, - } - const refList = ref([ref('1'), ref('2'), ref('3')]) - const list = [ref('a'), ref('b')] + aaaa: { + b: [1], + bb: ref([1]), + bbb: reactive({ + c: [1], + cc: ref([1]), + }), + bbbb: [ref(1)], + }, + } - return { - r, - nested, - refList, - list, - } - }, - template: `
-

{{r}}

-

{{nested.a}}

-

{{list}}

-

{{refList}}

- -

{{ nested.aa.b }}

-

{{ nested.aa.bb.c }}

-

{{ nested.aa.bb.cc }}

- -

{{ nested.aaa.b }}

-

{{ nested.aaa.bb.c }}

-

{{ nested.aaa.bb.cc }}

- -

{{ nested.aaaa.b }}

-

{{ nested.aaaa.bb }}

-

{{ nested.aaaa.bbb.c }}

-

{{ nested.aaaa.bbb.cc }}

-

{{ nested.aaaa.bbbb }}

-
`, - }).$mount() + return { + nested, + } + }, + template: `
+

{{nested.a}}

+ +

{{ nested.aa.b }}

+

{{ nested.aa.bb.c }}

+

{{ nested.aa.bb.cc }}

+ +

{{ nested.aaa.b }}

+

{{ nested.aaa.bb.c }}

+

{{ nested.aaa.bb.cc }}

+ +

{{ nested.aaaa.b }}

+

{{ nested.aaaa.bb }}

+

{{ nested.aaaa.bbb.c }}

+

{{ nested.aaaa.bbb.cc }}

+

{{ nested.aaaa.bbbb }}

+
`, + }).$mount() - expect(vm.$el.querySelector('#r').textContent).toBe('r') - expect(vm.$el.querySelector('#nested').textContent).toBe('a') + expect(vm.$el.querySelector('#nested').textContent).toBe('a') - // shouldn't unwrap arrays - expect( - JSON.parse(vm.$el.querySelector('#list').textContent) - ).toMatchObject([{ value: 'a' }, { value: 'b' }]) - expect( - JSON.parse(vm.$el.querySelector('#refList').textContent) - ).toMatchObject([{ value: '1' }, { value: '2' }, { value: '3' }]) + expect(vm.$el.querySelector('#nested_aa_b').textContent).toBe('aa') + expect(vm.$el.querySelector('#nested_aa_bb_c').textContent).toBe('aa') + expect(vm.$el.querySelector('#nested_aa_bb_cc').textContent).toBe('aa') - expect(vm.$el.querySelector('#nested_aa_b').textContent).toBe('aa') - expect(vm.$el.querySelector('#nested_aa_bb_c').textContent).toBe('aa') - expect(vm.$el.querySelector('#nested_aa_bb_cc').textContent).toBe('aa') + expect(vm.$el.querySelector('#nested_aaa_b').textContent).toBe('aaa') + expect(vm.$el.querySelector('#nested_aaa_bb_c').textContent).toBe('aaa') + expect(vm.$el.querySelector('#nested_aaa_bb_cc').textContent).toBe('aaa') + }) - expect(vm.$el.querySelector('#nested_aaa_b').textContent).toBe('aaa') - expect(vm.$el.querySelector('#nested_aaa_bb_c').textContent).toBe('aaa') - expect(vm.$el.querySelector('#nested_aaa_bb_cc').textContent).toBe('aaa') + it('recursive', () => { + const vm = new Vue({ + setup() { + const b = { + c: 'c', + } - expect( - JSON.parse(vm.$el.querySelector('#nested_aaaa_b').textContent) - ).toMatchObject([1]) - expect( - JSON.parse(vm.$el.querySelector('#nested_aaaa_bb_c').textContent) - ).toMatchObject([1]) - expect( - JSON.parse(vm.$el.querySelector('#nested_aaaa_bbb_cc').textContent) - ).toMatchObject([1]) - expect( - JSON.parse(vm.$el.querySelector('#nested_aaaa_bbb_cc').textContent) - ).toMatchObject([1]) - expect( - JSON.parse(vm.$el.querySelector('#nested_aaaa_bbbb').textContent) - ).toMatchObject([{ value: 1 }]) + const recursive = { + a: { + a: 'a', + b, + }, + } + + b.recursive = recursive + b.r = ref('r') + + return { + recursive, + } + }, + template: `
+

{{recursive.a.a}}

+

{{recursive.a.b.c}}

+

{{recursive.a.b.r}}

+ +

{{recursive.a.b.recursive.a.a}}

+

{{recursive.a.b.recursive.a.b.c}}

+

{{recursive.a.b.recursive.a.b.r}}

+ +

{{recursive.a.b.recursive.a.b.recursive.a.b.c}}

+

{{recursive.a.b.recursive.a.b.recursive.a.b.r}}

+
`, + }).$mount() + expect(vm.$el.querySelector('#recursive_a').textContent).toBe('a') + expect(vm.$el.querySelector('#recursive_b_c').textContent).toBe('c') + expect(vm.$el.querySelector('#recursive_b_r').textContent).toBe('r') + + expect(vm.$el.querySelector('#recursive_b_recursive_a').textContent).toBe( + 'a' + ) + expect(vm.$el.querySelector('#recursive_b_recursive_c').textContent).toBe( + 'c' + ) + expect(vm.$el.querySelector('#recursive_b_recursive_r').textContent).toBe( + 'r' + ) + + expect( + vm.$el.querySelector('#recursive_b_recursive_recursive_c').textContent + ).toBe('c') + + expect( + vm.$el.querySelector('#recursive_b_recursive_recursive_r').textContent + ).toBe('r') + }) }) it('should not unwrap built-in objects on the template', () => {