Skip to content

Commit 6fbdfa1

Browse files
authored
fix: allow access to nested computed values (fix #2196) (#2356)
* fix: allow access to nested computed values * fix: remove useless access to vm
1 parent d767850 commit 6fbdfa1

File tree

5 files changed

+110
-6
lines changed

5 files changed

+110
-6
lines changed

src/createInstance.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import {
44
defineComponent,
55
reactive,
66
shallowReactive,
7-
isRef,
87
ref,
98
AppConfig,
109
ComponentOptions,
@@ -17,6 +16,7 @@ import { MountingOptions, Slot } from './types'
1716
import {
1817
getComponentsFromStubs,
1918
getDirectivesFromStubs,
19+
isDeepRef,
2020
isFunctionalComponent,
2121
isObject,
2222
isObjectComponent,
@@ -174,7 +174,7 @@ export function createInstance(
174174
...options?.props,
175175
ref: MOUNT_COMPONENT_REF
176176
}).forEach(([k, v]) => {
177-
if (isRef(v)) {
177+
if (isDeepRef(v)) {
178178
refs[k] = v
179179
} else {
180180
props[k] = v

src/types.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import {
77
VNode,
88
VNodeProps,
99
FunctionalComponent,
10-
ComponentInternalInstance
10+
ComponentInternalInstance,
11+
Ref
1112
} from 'vue'
1213

1314
export interface RefSelector {
@@ -167,3 +168,12 @@ export type VueNode<T extends Node = Node> = T & {
167168
export type VueElement = VueNode<Element>
168169

169170
export type DefinedComponent = new (...args: any[]) => any
171+
172+
/**
173+
* T is a DeepRef if:
174+
* - It's a Ref itself
175+
* - It's an array containing a ref at any level
176+
* - It's an object containing a ref at any level
177+
*/
178+
export type DeepRef<T> =
179+
T extends Ref<T> ? T : T extends object ? DeepRef<T> : Ref<T>

src/utils.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import { GlobalMountOptions, RefSelector, Stub, Stubs } from './types'
1+
import { DeepRef, GlobalMountOptions, RefSelector, Stub, Stubs } from './types'
22
import {
33
Component,
44
ComponentOptions,
55
ComponentPublicInstance,
66
ConcreteComponent,
77
Directive,
8-
FunctionalComponent
8+
FunctionalComponent,
9+
isRef
910
} from 'vue'
1011
import { config } from './config'
1112

@@ -252,3 +253,15 @@ export const getGlobalThis = (): any => {
252253
: {})
253254
)
254255
}
256+
257+
/**
258+
* Checks if the given value is a DeepRef.
259+
*
260+
* For both arrays and objects, it will recursively check
261+
* if any of their values is a Ref.
262+
*
263+
* @param {DeepRef<T> | unknown} r - The value to check.
264+
* @returns {boolean} Returns true if the value is a DeepRef, false otherwise.
265+
*/
266+
export const isDeepRef = <T>(r: DeepRef<T> | unknown): r is DeepRef<T> =>
267+
isRef(r) || (isObject(r) && Object.values(r).some(isDeepRef))

tests/components/WithDeepRef.vue

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<template>
2+
<div id="countValue">{{ countValue }}</div>
3+
<div id="oneLayerCountObjectValue">{{ oneLayerCountObjectValue }}</div>
4+
<div id="twoLayersCountObjectValue">{{ twoLayersCountObjectValue }}</div>
5+
<div id="countArrayValue">{{ countArrayValue }}</div>
6+
<div id="countMatrixValue">{{ countMatrixValue }}</div>
7+
<div id="oneLayerCountObjectArrayValue">
8+
{{ oneLayerCountObjectArrayValue }}
9+
</div>
10+
<div id="oneLayerCountArrayObjectValue">
11+
{{ oneLayerCountArrayObjectValue }}
12+
</div>
13+
<div id="oneLayerCountObjectMatrixValue">
14+
{{ oneLayerCountObjectMatrixValue }}
15+
</div>
16+
</template>
17+
18+
<script setup lang="ts">
19+
import { computed, Ref } from 'vue'
20+
21+
type Props = {
22+
count: Ref<number>
23+
oneLayerCountObject: { count: Ref<number> }
24+
twoLayersCountObject: { oneLayerCountObject: { count: Ref<number> } }
25+
26+
countArray: Ref<number>[]
27+
countMatrix: Ref<number>[][]
28+
29+
oneLayerCountObjectArray: { count: Ref<number> }[]
30+
oneLayerCountArrayObject: { count: Ref<number>[] }
31+
oneLayerCountObjectMatrix: { count: Ref<number> }[][]
32+
}
33+
34+
const props = defineProps<Props>()
35+
const countValue = computed(() => props.count.value)
36+
const oneLayerCountObjectValue = computed(
37+
() => props.oneLayerCountObject.count.value
38+
)
39+
const twoLayersCountObjectValue = computed(
40+
() => props.twoLayersCountObject.oneLayerCountObject.count.value
41+
)
42+
43+
const countArrayValue = computed(() => props.countArray[0].value)
44+
const countMatrixValue = computed(() => props.countMatrix[0][0].value)
45+
46+
const oneLayerCountObjectArrayValue = computed(
47+
() => props.oneLayerCountObjectArray[0].count.value
48+
)
49+
const oneLayerCountArrayObjectValue = computed(
50+
() => props.oneLayerCountArrayObject.count[0].value
51+
)
52+
const oneLayerCountObjectMatrixValue = computed(
53+
() => props.oneLayerCountObjectMatrix[0][0].count.value
54+
)
55+
</script>

tests/mount.spec.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { describe, expect, it, vi } from 'vitest'
2-
import { defineComponent } from 'vue'
2+
import { defineComponent, ref } from 'vue'
33
import { mount } from '../src'
44
import DefinePropsAndDefineEmits from './components/DefinePropsAndDefineEmits.vue'
5+
import WithDeepRef from './components/WithDeepRef.vue'
56
import HelloFromVitestPlayground from './components/HelloFromVitestPlayground.vue'
67

78
describe('mount: general tests', () => {
@@ -44,4 +45,29 @@ describe('mount: general tests', () => {
4445
expect(wrapper.get('div').text()).toContain('Hello')
4546
expect(wrapper.get('div').classes()).toContain('end')
4647
})
48+
it('allows access to nested computed values', async () => {
49+
const wrapper = mount(WithDeepRef, {
50+
props: {
51+
count: ref(1),
52+
oneLayerCountObject: { count: ref(2) },
53+
twoLayersCountObject: { oneLayerCountObject: { count: ref(3) } },
54+
55+
countArray: [ref(4)],
56+
countMatrix: [[ref(5)]],
57+
58+
oneLayerCountObjectArray: [{ count: ref(6) }],
59+
oneLayerCountArrayObject: { count: [ref(7)] },
60+
oneLayerCountObjectMatrix: [[{ count: ref(8) }]]
61+
}
62+
})
63+
64+
expect(wrapper.get('#countValue').text()).toBe('1')
65+
expect(wrapper.get('#oneLayerCountObjectValue').text()).toBe('2')
66+
expect(wrapper.get('#twoLayersCountObjectValue').text()).toBe('3')
67+
expect(wrapper.get('#countArrayValue').text()).toBe('4')
68+
expect(wrapper.get('#countMatrixValue').text()).toBe('5')
69+
expect(wrapper.get('#oneLayerCountObjectArrayValue').text()).toBe('6')
70+
expect(wrapper.get('#oneLayerCountArrayObjectValue').text()).toBe('7')
71+
expect(wrapper.get('#oneLayerCountObjectMatrixValue').text()).toBe('8')
72+
})
4773
})

0 commit comments

Comments
 (0)