Skip to content

Commit

Permalink
types(runtime-core): support typed safe vnode ref on the template
Browse files Browse the repository at this point in the history
  • Loading branch information
pikax committed Feb 9, 2022
1 parent 15adf25 commit 119557a
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 13 deletions.
2 changes: 1 addition & 1 deletion packages/runtime-core/__tests__/apiWatch.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -906,7 +906,7 @@ describe('api: watch', () => {

const Comp = defineComponent({
setup() {
const comp = ref<ComponentPublicInstance | undefined>()
const comp = ref<ComponentPublicInstance | null>()
const show = ref(true)
_show = show
return { comp, show }
Expand Down
3 changes: 2 additions & 1 deletion packages/runtime-core/src/apiDefineComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ import {
ComponentPublicInstanceConstructor
} from './componentPublicInstance'

export type PublicProps = VNodeProps &
// NOTE omit `ref` since that will be overridden by JSX.d.ts
export type PublicProps = Omit<VNodeProps, 'ref'> &
AllowedComponentProps &
ComponentCustomProps

Expand Down
2 changes: 1 addition & 1 deletion packages/runtime-core/src/h.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ h(Component, {}, {}) // named slots
h(Component, null, {})
**/

type RawProps = VNodeProps & {
type RawProps<ComponentInstance = object> = VNodeProps<ComponentInstance> & {
// used to differ from a single VNode object as children
__v_isVNode?: never
// used to differ from Array children
Expand Down
10 changes: 5 additions & 5 deletions packages/runtime-core/src/vnode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,10 @@ export type VNodeTypes =
| typeof TeleportImpl
| typeof SuspenseImpl

export type VNodeRef =
export type VNodeRef<T = object> =
| string
| Ref
| ((ref: object | null, refs: Record<string, any>) => void)
| Ref<T | null>
| ((ref: T | null, refs: Record<string, any>) => void)

export type VNodeNormalizedRefAtom = {
i: ComponentInternalInstance
Expand All @@ -90,9 +90,9 @@ export type VNodeHook =
| VNodeUpdateHook[]

// https://github.com/microsoft/TypeScript/issues/33099
export type VNodeProps = {
export type VNodeProps<ComponentInstance = object> = {
key?: string | number | symbol
ref?: VNodeRef
ref?: VNodeRef<ComponentInstance>
ref_for?: boolean
ref_key?: string

Expand Down
38 changes: 33 additions & 5 deletions packages/runtime-dom/types/jsx.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1308,20 +1308,24 @@ import * as RuntimeCore from '@vue/runtime-core'

type ReservedProps = {
key?: string | number | symbol
ref?:
| string
| RuntimeCore.Ref
| ((ref: Element | RuntimeCore.ComponentPublicInstance | null) => void)
ref_for?: boolean
ref_key?: string
}

type RefProp<T> = string | RuntimeCore.Ref<T | null> | ((ref: T | null) => void)

type ElementAttrs<T> = T & ReservedProps

type NativeElements = {
[K in keyof IntrinsicElementAttributes]: ElementAttrs<
IntrinsicElementAttributes[K]
>
> & {
ref?: RefProp<
K extends keyof HTMLElementTagNameMap
? HTMLElementTagNameMap[K]
: HTMLElement
>
}
}

declare global {
Expand All @@ -1339,6 +1343,30 @@ declare global {
[name: string]: any
}
interface IntrinsicAttributes extends ReservedProps {}

interface IntrinsicClassAttributes<T> extends ReservedProps {}

// managed based props, this will be used to override the props defined
// used mainly to set `ref` property correct
type LibraryManagedAttributes<C, P> = {
ref?: P extends { ref?: infer R } // it already exists, most likely NativeElement
? R
: RefProp<
C extends { new (): infer I }
? I
: C extends RuntimeCore.FunctionalComponent
? C
: C extends RuntimeCore.FunctionalComponent<infer Props, infer E>
? E extends RuntimeCore.EmitsOptions
? RuntimeCore.ComponentPublicInstance<Props, {}, {}, {}, {}, E>
: { x: 1 } & RuntimeCore.ComponentPublicInstance<Props>
: C extends (props: infer Props) => any
? RuntimeCore.ComponentPublicInstance<Props>
: C extends () => any
? RuntimeCore.ComponentPublicInstance<{}>
: Element | RuntimeCore.ComponentPublicInstance
>
} & P
}
}

Expand Down
69 changes: 69 additions & 0 deletions test-dts/componentRef.test-d.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import {
expectError,
Ref,
defineComponent,
ComponentPublicInstance,
FunctionalComponent,
expectType
} from './index'

// html component ref
declare let videoEl: HTMLVideoElement | null
declare let videoElRef: Ref<HTMLVideoElement | null>
;<video ref={e => (videoEl = e)} />
;<video ref={videoElRef} />

// @ts-expect-error
expectError(<a ref={e => (videoEl = e)} />)
// @ts-expect-error
expectError(<a ref={videoEl} />)

const Comp = defineComponent({
props: {
test: String
}
})

// Component based ref
declare let myComp: InstanceType<typeof Comp> | null
declare let myCompRef: Ref<InstanceType<typeof Comp> | null>
declare let anyComp: ComponentPublicInstance | null
expectType<JSX.Element>(<Comp ref={e => (myComp = e)} />)
expectType<JSX.Element>(<Comp ref={myCompRef} />)
expectType<JSX.Element>(<Comp ref={e => (anyComp = e)} />)

declare let wrongComponent: ComponentPublicInstance<{ a: string }>

// @ts-expect-error wrong Component type
expectType<JSX.Element>(<Comp ref={e => (wrongComponent = e)} />)

// Function
declare function FuncComp(props: { foo: string }): any

expectType<JSX.Element>(<FuncComp foo="test" ref={e => e?.$props.foo} />)

// @ts-expect-error not valid prop
expectType<JSX.Element>(<FuncComp foo="test" ref={e => e?.$props.bar} />)

declare const FuncEmitComp: FunctionalComponent<{ foo: string }, ['test']>

expectType<JSX.Element>(
<FuncEmitComp foo="test" ref={e => e?.$props.foo && e?.$emit('test')} />
)

// @ts-expect-error not valid prop
expectType<JSX.Element>(<FuncEmitComp foo="test" ref={e => e?.$props.bar} />)

// @ts-expect-error not valid emit
expectType<JSX.Element>(<FuncEmitComp foo="test" ref={e => e?.$emit('bar')} />)

// Class
declare const CustomComp: {
new (): { $props: { foo: string } }
}

declare let customComp: InstanceType<typeof CustomComp> | null
expectType<JSX.Element>(<CustomComp foo="test" ref={e => (customComp = e)} />)

// @ts-expect-error not valid prop
expectType<JSX.Element>(<CustomComp foo="test" ref={e => e?.$props.bar} />)

0 comments on commit 119557a

Please sign in to comment.