Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(types): support inferring attrs #13089

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion types/index.d.ts
Original file line number Diff line number Diff line change
@@ -49,7 +49,8 @@ export {
ComputedOptions as ComponentComputedOptions,
MethodOptions as ComponentMethodOptions,
ComponentPropsOptions,
ComponentCustomOptions
ComponentCustomOptions,
AttrsType
} from './v3-component-options'
export {
ComponentInstance,
8 changes: 6 additions & 2 deletions types/options.d.ts
Original file line number Diff line number Diff line change
@@ -3,8 +3,9 @@ import { VNode, VNodeData, VNodeDirective, NormalizedScopedSlot } from './vnode'
import { SetupContext } from './v3-setup-context'
import { DebuggerEvent } from './v3-generated'
import { DefineComponent } from './v3-define-component'
import { ComponentOptionsMixin } from './v3-component-options'
import { AttrsType, ComponentOptionsMixin, noAttrsDefine, UnwrapAttrsType } from './v3-component-options'
import { ObjectDirective, FunctionDirective } from './v3-directive'
import { Data } from './common'

type Constructor = {
new (...args: any[]): any
@@ -263,7 +264,7 @@ export interface FunctionalComponentOptions<
): VNode | VNode[]
}

export interface RenderContext<Props = DefaultProps> {
export interface RenderContext<Props = DefaultProps, Attrs extends AttrsType = Record<string, unknown>> {
props: Props
children: VNode[]
slots(): any
@@ -272,6 +273,9 @@ export interface RenderContext<Props = DefaultProps> {
listeners: { [key: string]: Function | Function[] }
scopedSlots: { [key: string]: NormalizedScopedSlot }
injections: any
attrs: noAttrsDefine<Attrs> extends true
? Data
: UnwrapAttrsType<NonNullable<Attrs>>
}

export type Prop<T> =
200 changes: 196 additions & 4 deletions types/test/v3/define-component-test.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import Vue, { VueConstructor } from '../../index'
import Vue, { AttrsType, ExtractPropTypes, VueConstructor } from '../../index'
import {
Component,
defineComponent,
PropType,
ref,
reactive,
ComponentPublicInstance
} from '../../index'
import { reactive } from '../../../src/v3/reactivity/reactive'
import { ref } from '../../../src/v3/reactivity/ref'
import { describe, test, expectType, expectError, IsUnion } from '../utils'
import { ImgHTMLAttributes, StyleValue } from '../../jsx'

describe('compat with v2 APIs', () => {
const comp = defineComponent({})
@@ -1079,7 +1080,9 @@ export default {
}
})
}

const Foo = defineComponent((props: { msg: string }) => {
return () => <div>{props.msg}</div>
})
describe('functional w/ array props', () => {
const Foo = defineComponent({
functional: true,
@@ -1225,3 +1228,192 @@ describe('should report non-existent properties in instance', () => {
// @ts-expect-error
instance2.foo
})
const MyComp = defineComponent({
props: {
foo: String
},

created() {
console.log(this)
}
})
describe('define attrs', () => {
test('define attrs w/ object props', () => {
const MyComp = defineComponent({
props: {
foo: String
},
attrs: Object as AttrsType<{
bar?: number
}>,
created() {
expectType<number | undefined>(this.$attrs.bar)
}
})
expectType<JSX.Element>(<MyComp foo="1" bar={1} />)
})

test('define attrs w/ array props', () => {
const MyComp = defineComponent({
props: ['foo'],
attrs: Object as AttrsType<{
bar?: number
}>,
created() {
expectType<number | undefined>(this.$attrs.bar)
}
})
expectType<JSX.Element>(<MyComp foo="1" bar={1} />)
})

test('define attrs w/ no props', () => {
const MyComp = defineComponent({
attrs: Object as AttrsType<{
bar?: number
}>,
created() {
expectType<number | undefined>(this.$attrs.bar)
}
})
expectType<JSX.Element>(<MyComp bar={1} />)
})

test('define attrs w/ composition api', () => {
const MyComp = defineComponent({
props: {
foo: {
type: String,
required: true
}
},
attrs: Object as AttrsType<{
bar?: number
}>,
setup(props, { attrs }) {
expectType<string>(props.foo)
expectType<number | undefined>(attrs.bar)
}
})
expectType<JSX.Element>(<MyComp foo="1" bar={1} />)
})

test('functional w/ array props', () => {
const Comp = defineComponent({
functional: true,
props: ['foo'],
attrs: Object as AttrsType<{
bar?: number
}>,
render(h, ctx) {
expectType<any>(ctx.props.foo)
expectType<number | undefined>(ctx.attrs.bar)
}
});

<Comp foo="hi" bar={1}/>
})

test('functional w/ object props', () => {
const Comp = defineComponent({
functional: true,
props: {
foo: String
},
attrs: Object as AttrsType<{
bar?: number
}>,
render(h, ctx) {
expectType<any>(ctx.props.foo)
expectType<number | undefined>(ctx.attrs.bar)
}
});
<Comp foo="hi" bar={1}/>
})

test('define attrs as low priority', () => {
const MyComp = defineComponent({
props: {
foo: String
},
attrs: Object as AttrsType<{
foo?: number
}>,
created() {
// @ts-expect-error
this.$attrs.foo

expectType<string | undefined>(this.foo)
}
})
expectType<JSX.Element>(<MyComp foo="1" />)
})

test('define required attrs', () => {
const MyComp = defineComponent({
attrs: Object as AttrsType<{
bar: number
}>,
created() {
expectType<number | undefined>(this.$attrs.bar)
}
})
expectType<JSX.Element>(<MyComp bar={1} />)
// @ts-expect-error
expectType<JSX.Element>(<MyComp />)
})

test('define no attrs w/ object props', () => {
const MyComp = defineComponent({
props: {
foo: String
},
created() {
expectType<unknown>(this.$attrs.bar)
}
})
// @ts-expect-error
expectType<JSX.Element>(<MyComp foo="1" bar={1} />)
})

test('wrap elements, such as img element', () => {
const MyImg = defineComponent({
props: {
foo: String
},
attrs: Object as AttrsType<ImgHTMLAttributes>,
created() {
expectType<any>(this.$attrs.class)
expectType<StyleValue | undefined>(this.$attrs.style)
},
render() {
return <img {...this.$attrs} />
}
})
expectType<JSX.Element>(<MyImg class={'str'} style={'str'} src={'str'} />)
})

test('secondary packaging of components', () => {
const childProps = {
foo: String
}
type ChildProps = ExtractPropTypes<typeof childProps>
const Child = defineComponent({
props: childProps,
render() {
return <div>{this.foo}</div>
}
})
const Comp = defineComponent({
props: {
bar: Number
},
attrs: Object as AttrsType<ChildProps>,
render() {
return <Child {...this.$attrs} />
}
})
expectType<JSX.Element>(
<Comp class={'str'} style={'str'} bar={1} foo={'str'} />
)
})
})
3 changes: 2 additions & 1 deletion types/umd.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as V from './index'
import { AttrsType } from './index'
import {
DefaultData,
DefaultProps,
@@ -38,7 +39,7 @@ declare namespace Vue {
Props = DefaultProps,
PropDefs = PropsDefinition<Props>
> = V.FunctionalComponentOptions<Props, PropDefs>
export type RenderContext<Props = DefaultProps> = V.RenderContext<Props>
export type RenderContext<Props = DefaultProps, Attrs extends AttrsType = Record<string, unknown>> = V.RenderContext<Props, Attrs>
export type PropType<T> = V.PropType<T>
export type PropOptions<T = any> = V.PropOptions<T>
export type ComputedOptions<T> = V.ComputedOptions<T>
Loading
Oops, something went wrong.