Skip to content

Commit b7b652e

Browse files
authored
feat(runtime-vapor): template ref on component (#185)
1 parent 7fe4712 commit b7b652e

File tree

4 files changed

+149
-13
lines changed

4 files changed

+149
-13
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { ref, shallowRef } from '@vue/reactivity'
2+
import { createComponent } from '../src/apiCreateComponent'
3+
import { setRef } from '../src/dom/templateRef'
4+
import { makeRender } from './_utils'
5+
import {
6+
type ComponentInternalInstance,
7+
getCurrentInstance,
8+
} from '../src/component'
9+
10+
const define = makeRender()
11+
describe('api: expose', () => {
12+
test('via setup context', () => {
13+
const { component: Child } = define({
14+
setup(_, { expose }) {
15+
expose({
16+
foo: 1,
17+
bar: ref(2),
18+
})
19+
return {
20+
bar: ref(3),
21+
baz: ref(4),
22+
}
23+
},
24+
})
25+
const childRef = ref()
26+
const { render } = define({
27+
render: () => {
28+
const n0 = createComponent(Child)
29+
setRef(n0, childRef)
30+
return n0
31+
},
32+
})
33+
34+
render()
35+
expect(childRef.value).toBeTruthy()
36+
expect(childRef.value.foo).toBe(1)
37+
expect(childRef.value.bar).toBe(2)
38+
expect(childRef.value.baz).toBeUndefined()
39+
})
40+
41+
test('via setup context (expose empty)', () => {
42+
let childInstance: ComponentInternalInstance | null = null
43+
const { component: Child } = define({
44+
setup(_) {
45+
childInstance = getCurrentInstance()
46+
},
47+
})
48+
const childRef = shallowRef()
49+
const { render } = define({
50+
render: () => {
51+
const n0 = createComponent(Child)
52+
setRef(n0, childRef)
53+
return n0
54+
},
55+
})
56+
57+
render()
58+
expect(childInstance!.exposed).toBeUndefined()
59+
expect(childRef.value).toBe(childInstance!)
60+
})
61+
62+
test('warning for ref', () => {
63+
const { render } = define({
64+
setup(_, { expose }) {
65+
expose(ref(1))
66+
},
67+
})
68+
render()
69+
expect(
70+
'expose() should be passed a plain object, received ref',
71+
).toHaveBeenWarned()
72+
})
73+
74+
test('warning for array', () => {
75+
const { render } = define({
76+
setup(_, { expose }) {
77+
expose(['focus'])
78+
},
79+
})
80+
render()
81+
expect(
82+
'expose() should be passed a plain object, received array',
83+
).toHaveBeenWarned()
84+
})
85+
86+
test('warning for function', () => {
87+
const { render } = define({
88+
setup(_, { expose }) {
89+
expose(() => null)
90+
},
91+
})
92+
render()
93+
expect(
94+
'expose() should be passed a plain object, received function',
95+
).toHaveBeenWarned()
96+
})
97+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { ref, setRef, template } from '../../src'
2+
import { makeRender } from '../_utils'
3+
4+
const define = makeRender()
5+
6+
describe('api: template ref', () => {
7+
test('string ref mount', () => {
8+
const t0 = template('<div ref="refKey"></div>')
9+
const el = ref(null)
10+
const { render } = define({
11+
setup() {
12+
return {
13+
refKey: el,
14+
}
15+
},
16+
render() {
17+
const n0 = t0()
18+
setRef(n0 as Element, 'refKey')
19+
return n0
20+
},
21+
})
22+
23+
const { host } = render()
24+
expect(el.value).toBe(host.children[0])
25+
})
26+
})

packages/runtime-vapor/src/component.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { EffectScope, isRef } from '@vue/reactivity'
2-
import { EMPTY_OBJ, isArray, isFunction } from '@vue/shared'
2+
import { EMPTY_OBJ, hasOwn, isArray, isFunction } from '@vue/shared'
33
import type { Block } from './apiRender'
44
import type { DirectiveBinding } from './directives'
55
import {
@@ -327,6 +327,12 @@ export function createComponentInstance(
327327
return instance
328328
}
329329

330+
export function isVaporComponent(
331+
val: unknown,
332+
): val is ComponentInternalInstance {
333+
return !!val && hasOwn(val, componentKey)
334+
}
335+
330336
function getAttrsProxy(instance: ComponentInternalInstance): Data {
331337
return (
332338
instance.attrsProxy ||

packages/runtime-vapor/src/dom/templateRef.ts

+19-12
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ import {
44
isRef,
55
onScopeDispose,
66
} from '@vue/reactivity'
7-
import { currentInstance } from '../component'
7+
import {
8+
type ComponentInternalInstance,
9+
currentInstance,
10+
isVaporComponent,
11+
} from '../component'
812
import { VaporErrorCodes, callWithErrorHandling } from '../errorHandling'
913
import {
1014
EMPTY_OBJ,
@@ -18,25 +22,28 @@ import { warn } from '../warning'
1822
import { queuePostFlushCb } from '../scheduler'
1923

2024
export type NodeRef = string | Ref | ((ref: Element) => void)
25+
export type RefEl = Element | ComponentInternalInstance
2126

2227
/**
2328
* Function for handling a template ref
2429
*/
25-
export function setRef(el: Element, ref: NodeRef, refFor = false) {
30+
export function setRef(el: RefEl, ref: NodeRef, refFor = false) {
2631
if (!currentInstance) return
2732
const { setupState, isUnmounted } = currentInstance
2833

2934
if (isUnmounted) {
3035
return
3136
}
3237

38+
const refValue = isVaporComponent(el) ? el.exposed || el : el
39+
3340
const refs =
3441
currentInstance.refs === EMPTY_OBJ
3542
? (currentInstance.refs = {})
3643
: currentInstance.refs
3744

3845
if (isFunction(ref)) {
39-
const invokeRefSetter = (value: Element | null) => {
46+
const invokeRefSetter = (value?: Element | Record<string, any>) => {
4047
callWithErrorHandling(
4148
ref,
4249
currentInstance,
@@ -45,8 +52,8 @@ export function setRef(el: Element, ref: NodeRef, refFor = false) {
4552
)
4653
}
4754

48-
invokeRefSetter(el)
49-
onScopeDispose(() => invokeRefSetter(null))
55+
invokeRefSetter(refValue)
56+
onScopeDispose(() => invokeRefSetter())
5057
} else {
5158
const _isString = isString(ref)
5259
const _isRef = isRef(ref)
@@ -62,7 +69,7 @@ export function setRef(el: Element, ref: NodeRef, refFor = false) {
6269
: ref.value
6370

6471
if (!isArray(existing)) {
65-
existing = [el]
72+
existing = [refValue]
6673
if (_isString) {
6774
refs[ref] = existing
6875
if (hasOwn(setupState, ref)) {
@@ -75,16 +82,16 @@ export function setRef(el: Element, ref: NodeRef, refFor = false) {
7582
} else {
7683
ref.value = existing
7784
}
78-
} else if (!existing.includes(el)) {
79-
existing.push(el)
85+
} else if (!existing.includes(refValue)) {
86+
existing.push(refValue)
8087
}
8188
} else if (_isString) {
82-
refs[ref] = el
89+
refs[ref] = refValue
8390
if (hasOwn(setupState, ref)) {
84-
setupState[ref] = el
91+
setupState[ref] = refValue
8592
}
8693
} else if (_isRef) {
87-
ref.value = el
94+
ref.value = refValue
8895
} else if (__DEV__) {
8996
warn('Invalid template ref type:', ref, `(${typeof ref})`)
9097
}
@@ -95,7 +102,7 @@ export function setRef(el: Element, ref: NodeRef, refFor = false) {
95102
onScopeDispose(() => {
96103
queuePostFlushCb(() => {
97104
if (isArray(existing)) {
98-
remove(existing, el)
105+
remove(existing, refValue)
99106
} else if (_isString) {
100107
refs[ref] = null
101108
if (hasOwn(setupState, ref)) {

0 commit comments

Comments
 (0)