Skip to content

Commit

Permalink
fix(reactivity): unwrap nested refs on the template (#361)
Browse files Browse the repository at this point in the history
* fix: unwrap nested refs on the template

* chore: add more nested to test

* Update src/reactivity/unwrap.ts

Co-authored-by: Anthony Fu <anthonyfu117@hotmail.com>

* chore: if the object is not extensible stop unwrapping

Co-authored-by: Anthony Fu <anthonyfu117@hotmail.com>
  • Loading branch information
pikax and antfu committed Jun 10, 2020
1 parent 9613dde commit 1fd48f5
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 1 deletion.
51 changes: 51 additions & 0 deletions src/reactivity/unwrap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { isRef } from './ref'
import { proxy, isFunction, isObject, isArray } from '../utils'
import { isReactive } from './reactive'

export function unwrapRefProxy(value: any) {
if (isFunction(value)) {
return value
}

if (isRef(value)) {
return value
}

if (isArray(value)) {
return value
}

if (isReactive(value)) {
return value
}

if (!isObject(value)) {
return value
}

if (!Object.isExtensible(value)) {
return value
}

const obj: any = {}

// copy symbols over
Object.getOwnPropertySymbols(value).forEach(
(s) => (obj[s] = (value as any)[s])
)

for (const k of Object.keys(value)) {
const r = value[k]
// if is a ref, create a proxy to retrieve the value,
if (isRef(r)) {
const set = (v: any) => (r.value = v)
const get = () => r.value

proxy(obj, k, { get, set })
} else {
obj[k] = unwrapRefProxy(r)
}
}

return obj
}
3 changes: 2 additions & 1 deletion src/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { resolveSlots, createSlotProxy } from './helper'
import { hasOwn, isPlainObject, assert, proxy, warn, isFunction } from './utils'
import { ref } from './apis/state'
import vmStateManager from './vmStateManager'
import { unwrapRefProxy } from './reactivity/unwrap'
import { markReactive } from './reactivity/reactive'

function asVmProperty(
Expand Down Expand Up @@ -220,7 +221,7 @@ export function mixin(Vue: VueConstructor) {
bindingValue = bindingValue.bind(vm)
}
// a non-reactive should not don't get reactivity
bindingValue = ref(markRaw(bindingValue))
bindingValue = ref(markRaw(unwrapRefProxy(bindingValue)))
}
}
asVmProperty(vm, name, bindingValue)
Expand Down
102 changes: 102 additions & 0 deletions test/setup.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const {
createElement: h,
provide,
inject,
reactive,
toRefs,
} = require('../src')

Expand Down Expand Up @@ -466,6 +467,107 @@ 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',
},
},

aaa: reactive({
b: ref('aaa'),
bb: {
c: ref('aaa'),
cc: 'aaa',
},
}),

aaaa: {
b: [1],
bb: ref([1]),
bbb: reactive({
c: [1],
cc: ref([1]),
}),
bbbb: [ref(1)],
},
}

const refList = ref([ref('1'), ref('2'), ref('3')])
const list = [ref('a'), ref('b')]

return {
r,
nested,
refList,
list,
}
},
template: `<div>
<p id="r">{{r}}</p>
<p id="nested">{{nested.a}}</p>
<p id="list">{{list}}</p>
<p id="refList">{{refList}}</p>
<p id="nested_aa_b">{{ nested.aa.b }}</p>
<p id="nested_aa_bb_c">{{ nested.aa.bb.c }}</p>
<p id="nested_aa_bb_cc">{{ nested.aa.bb.cc }}</p>
<p id="nested_aaa_b">{{ nested.aaa.b }}</p>
<p id="nested_aaa_bb_c">{{ nested.aaa.bb.c }}</p>
<p id="nested_aaa_bb_cc">{{ nested.aaa.bb.cc }}</p>
<p id="nested_aaaa_b">{{ nested.aaaa.b }}</p>
<p id="nested_aaaa_bb_c">{{ nested.aaaa.bb }}</p>
<p id="nested_aaaa_bbb_cc">{{ nested.aaaa.bbb.c }}</p>
<p id="nested_aaaa_bbb_cc">{{ nested.aaaa.bbb.cc }}</p>
<p id="nested_aaaa_bbbb">{{ nested.aaaa.bbbb }}</p>
</div>`,
}).$mount()

expect(vm.$el.querySelector('#r').textContent).toBe('r')
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_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(
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 }])
})

describe('Methods', () => {
it('binds methods when calling with parenthesis', async () => {
let context = null
Expand Down

0 comments on commit 1fd48f5

Please sign in to comment.