Skip to content

Commit 1fd48f5

Browse files
pikaxantfu
andauthored
fix(reactivity): unwrap nested refs on the template (vuejs#361)
* fix: unwrap nested refs on the template * chore: add more nested to test * Update src/reactivity/unwrap.ts Co-authored-by: Anthony Fu <[email protected]> * chore: if the object is not extensible stop unwrapping Co-authored-by: Anthony Fu <[email protected]>
1 parent 9613dde commit 1fd48f5

File tree

3 files changed

+155
-1
lines changed

3 files changed

+155
-1
lines changed

src/reactivity/unwrap.ts

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { isRef } from './ref'
2+
import { proxy, isFunction, isObject, isArray } from '../utils'
3+
import { isReactive } from './reactive'
4+
5+
export function unwrapRefProxy(value: any) {
6+
if (isFunction(value)) {
7+
return value
8+
}
9+
10+
if (isRef(value)) {
11+
return value
12+
}
13+
14+
if (isArray(value)) {
15+
return value
16+
}
17+
18+
if (isReactive(value)) {
19+
return value
20+
}
21+
22+
if (!isObject(value)) {
23+
return value
24+
}
25+
26+
if (!Object.isExtensible(value)) {
27+
return value
28+
}
29+
30+
const obj: any = {}
31+
32+
// copy symbols over
33+
Object.getOwnPropertySymbols(value).forEach(
34+
(s) => (obj[s] = (value as any)[s])
35+
)
36+
37+
for (const k of Object.keys(value)) {
38+
const r = value[k]
39+
// if is a ref, create a proxy to retrieve the value,
40+
if (isRef(r)) {
41+
const set = (v: any) => (r.value = v)
42+
const get = () => r.value
43+
44+
proxy(obj, k, { get, set })
45+
} else {
46+
obj[k] = unwrapRefProxy(r)
47+
}
48+
}
49+
50+
return obj
51+
}

src/setup.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { resolveSlots, createSlotProxy } from './helper'
1111
import { hasOwn, isPlainObject, assert, proxy, warn, isFunction } from './utils'
1212
import { ref } from './apis/state'
1313
import vmStateManager from './vmStateManager'
14+
import { unwrapRefProxy } from './reactivity/unwrap'
1415
import { markReactive } from './reactivity/reactive'
1516

1617
function asVmProperty(
@@ -220,7 +221,7 @@ export function mixin(Vue: VueConstructor) {
220221
bindingValue = bindingValue.bind(vm)
221222
}
222223
// a non-reactive should not don't get reactivity
223-
bindingValue = ref(markRaw(bindingValue))
224+
bindingValue = ref(markRaw(unwrapRefProxy(bindingValue)))
224225
}
225226
}
226227
asVmProperty(vm, name, bindingValue)

test/setup.spec.js

+102
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const {
55
createElement: h,
66
provide,
77
inject,
8+
reactive,
89
toRefs,
910
} = require('../src')
1011

@@ -466,6 +467,107 @@ describe('setup', () => {
466467
.then(done)
467468
})
468469

470+
it('should unwrap on the template', () => {
471+
const vm = new Vue({
472+
setup() {
473+
const r = ref('r')
474+
const nested = {
475+
a: ref('a'),
476+
aa: {
477+
b: ref('aa'),
478+
bb: {
479+
cc: ref('aa'),
480+
c: 'aa',
481+
},
482+
},
483+
484+
aaa: reactive({
485+
b: ref('aaa'),
486+
bb: {
487+
c: ref('aaa'),
488+
cc: 'aaa',
489+
},
490+
}),
491+
492+
aaaa: {
493+
b: [1],
494+
bb: ref([1]),
495+
bbb: reactive({
496+
c: [1],
497+
cc: ref([1]),
498+
}),
499+
bbbb: [ref(1)],
500+
},
501+
}
502+
503+
const refList = ref([ref('1'), ref('2'), ref('3')])
504+
const list = [ref('a'), ref('b')]
505+
506+
return {
507+
r,
508+
nested,
509+
refList,
510+
list,
511+
}
512+
},
513+
template: `<div>
514+
<p id="r">{{r}}</p>
515+
<p id="nested">{{nested.a}}</p>
516+
<p id="list">{{list}}</p>
517+
<p id="refList">{{refList}}</p>
518+
519+
<p id="nested_aa_b">{{ nested.aa.b }}</p>
520+
<p id="nested_aa_bb_c">{{ nested.aa.bb.c }}</p>
521+
<p id="nested_aa_bb_cc">{{ nested.aa.bb.cc }}</p>
522+
523+
<p id="nested_aaa_b">{{ nested.aaa.b }}</p>
524+
<p id="nested_aaa_bb_c">{{ nested.aaa.bb.c }}</p>
525+
<p id="nested_aaa_bb_cc">{{ nested.aaa.bb.cc }}</p>
526+
527+
<p id="nested_aaaa_b">{{ nested.aaaa.b }}</p>
528+
<p id="nested_aaaa_bb_c">{{ nested.aaaa.bb }}</p>
529+
<p id="nested_aaaa_bbb_cc">{{ nested.aaaa.bbb.c }}</p>
530+
<p id="nested_aaaa_bbb_cc">{{ nested.aaaa.bbb.cc }}</p>
531+
<p id="nested_aaaa_bbbb">{{ nested.aaaa.bbbb }}</p>
532+
</div>`,
533+
}).$mount()
534+
535+
expect(vm.$el.querySelector('#r').textContent).toBe('r')
536+
expect(vm.$el.querySelector('#nested').textContent).toBe('a')
537+
538+
// shouldn't unwrap arrays
539+
expect(
540+
JSON.parse(vm.$el.querySelector('#list').textContent)
541+
).toMatchObject([{ value: 'a' }, { value: 'b' }])
542+
expect(
543+
JSON.parse(vm.$el.querySelector('#refList').textContent)
544+
).toMatchObject([{ value: '1' }, { value: '2' }, { value: '3' }])
545+
546+
expect(vm.$el.querySelector('#nested_aa_b').textContent).toBe('aa')
547+
expect(vm.$el.querySelector('#nested_aa_bb_c').textContent).toBe('aa')
548+
expect(vm.$el.querySelector('#nested_aa_bb_cc').textContent).toBe('aa')
549+
550+
expect(vm.$el.querySelector('#nested_aaa_b').textContent).toBe('aaa')
551+
expect(vm.$el.querySelector('#nested_aaa_bb_c').textContent).toBe('aaa')
552+
expect(vm.$el.querySelector('#nested_aaa_bb_cc').textContent).toBe('aaa')
553+
554+
expect(
555+
JSON.parse(vm.$el.querySelector('#nested_aaaa_b').textContent)
556+
).toMatchObject([1])
557+
expect(
558+
JSON.parse(vm.$el.querySelector('#nested_aaaa_bb_c').textContent)
559+
).toMatchObject([1])
560+
expect(
561+
JSON.parse(vm.$el.querySelector('#nested_aaaa_bbb_cc').textContent)
562+
).toMatchObject([1])
563+
expect(
564+
JSON.parse(vm.$el.querySelector('#nested_aaaa_bbb_cc').textContent)
565+
).toMatchObject([1])
566+
expect(
567+
JSON.parse(vm.$el.querySelector('#nested_aaaa_bbbb').textContent)
568+
).toMatchObject([{ value: 1 }])
569+
})
570+
469571
describe('Methods', () => {
470572
it('binds methods when calling with parenthesis', async () => {
471573
let context = null

0 commit comments

Comments
 (0)