Skip to content

Commit a643c41

Browse files
committed
feat(types): infer attrs in defineComponent
1 parent 2ffe3d5 commit a643c41

File tree

7 files changed

+214
-45
lines changed

7 files changed

+214
-45
lines changed

packages/dts-test/defineComponent.test-d.tsx

Lines changed: 118 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
SetupContext,
1111
h,
1212
SlotsType,
13+
AttrsType,
1314
Slots,
1415
VNode
1516
} from 'vue'
@@ -1043,7 +1044,7 @@ describe('inject', () => {
10431044
expectType<unknown>(this.foo)
10441045
expectType<unknown>(this.bar)
10451046
// @ts-expect-error
1046-
this.foobar = 1
1047+
expectError((this.foobar = 1))
10471048
}
10481049
})
10491050

@@ -1055,7 +1056,7 @@ describe('inject', () => {
10551056
expectType<unknown>(this.foo)
10561057
expectType<unknown>(this.bar)
10571058
// @ts-expect-error
1058-
this.foobar = 1
1059+
expectError((this.foobar = 1))
10591060
}
10601061
})
10611062

@@ -1075,7 +1076,7 @@ describe('inject', () => {
10751076
expectType<unknown>(this.foo)
10761077
expectType<unknown>(this.bar)
10771078
// @ts-expect-error
1078-
this.foobar = 1
1079+
expectError((this.foobar = 1))
10791080
}
10801081
})
10811082

@@ -1084,9 +1085,9 @@ describe('inject', () => {
10841085
props: ['a', 'b'],
10851086
created() {
10861087
// @ts-expect-error
1087-
this.foo = 1
1088+
expectError((this.foo = 1))
10881089
// @ts-expect-error
1089-
this.bar = 1
1090+
expectError((this.bar = 1))
10901091
}
10911092
})
10921093
})
@@ -1188,6 +1189,118 @@ describe('async setup', () => {
11881189
vm.a = 2
11891190
})
11901191

1192+
describe('define attrs', () => {
1193+
test('define attrs w/ object props', () => {
1194+
type CompAttrs = {
1195+
bar: number
1196+
baz?: string
1197+
}
1198+
const MyComp = defineComponent(
1199+
{
1200+
props: {
1201+
foo: String
1202+
},
1203+
attrs: Object as AttrsType<CompAttrs>,
1204+
created() {
1205+
expectType<CompAttrs['bar']>(this.$attrs.bar)
1206+
expectType<CompAttrs['baz']>(this.$attrs.baz)
1207+
}
1208+
}
1209+
)
1210+
expectType<JSX.Element>(<MyComp foo="1" bar={1} />)
1211+
})
1212+
1213+
test('define attrs w/ array props', () => {
1214+
type CompAttrs = {
1215+
bar: number
1216+
baz?: string
1217+
}
1218+
const MyComp = defineComponent(
1219+
{
1220+
props: ['foo'],
1221+
attrs: Object as AttrsType<CompAttrs>,
1222+
created() {
1223+
expectType<CompAttrs['bar']>(this.$attrs.bar)
1224+
expectType<CompAttrs['baz']>(this.$attrs.baz)
1225+
}
1226+
}
1227+
)
1228+
expectType<JSX.Element>(<MyComp foo="1" bar={1} />)
1229+
})
1230+
1231+
test('define attrs w/ no props', () => {
1232+
type CompAttrs = {
1233+
bar: number
1234+
baz?: string
1235+
}
1236+
const MyComp = defineComponent(
1237+
{
1238+
attrs: Object as AttrsType<CompAttrs>,
1239+
created() {
1240+
expectType<CompAttrs['bar']>(this.$attrs.bar)
1241+
expectType<CompAttrs['baz']>(this.$attrs.baz)
1242+
}
1243+
}
1244+
)
1245+
expectType<JSX.Element>(<MyComp bar={1} />)
1246+
})
1247+
1248+
test('define attrs w/ function component', () => {
1249+
type CompAttrs = {
1250+
bar: number
1251+
baz?: string
1252+
}
1253+
const MyComp = defineComponent(
1254+
(props: { foo: string }, ctx) => {
1255+
expectType<number>(ctx.attrs.bar)
1256+
expectType<CompAttrs['bar']>(ctx.attrs.bar)
1257+
expectType<CompAttrs['baz']>(ctx.attrs.baz)
1258+
return () => (
1259+
// return a render function (both JSX and h() works)
1260+
<div>
1261+
{props.foo}
1262+
</div>
1263+
)
1264+
}, {
1265+
attrs: Object as AttrsType<CompAttrs>
1266+
}
1267+
)
1268+
expectType<JSX.Element>(<MyComp foo={'1'} bar={1} />)
1269+
})
1270+
1271+
test('define attrs as low priority', () => {
1272+
type CompAttrs = {
1273+
foo: number
1274+
}
1275+
const MyComp = defineComponent(
1276+
{
1277+
props: {
1278+
foo: String
1279+
},
1280+
attrs: Object as AttrsType<CompAttrs>,
1281+
created() {
1282+
// @ts-expect-error
1283+
console.log(this.$attrs.foo)
1284+
}
1285+
}
1286+
)
1287+
expectType<JSX.Element>(<MyComp foo="1" />)
1288+
})
1289+
1290+
test('define attrs w/ default attrs such as class, style', () => {
1291+
const MyComp = defineComponent({
1292+
props: {
1293+
foo: String
1294+
},
1295+
created() {
1296+
expectType<unknown>(this.$attrs.class)
1297+
expectType<unknown>(this.$attrs.style)
1298+
}
1299+
})
1300+
expectType<JSX.Element>(<MyComp class="1" style={1} />)
1301+
})
1302+
})
1303+
11911304
// #5948
11921305
describe('DefineComponent should infer correct types when assigning to Component', () => {
11931306
let component: Component

packages/runtime-core/src/apiDefineComponent.ts

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ import {
88
RenderFunction,
99
ComponentOptionsBase,
1010
ComponentInjectOptions,
11-
ComponentOptions
11+
ComponentOptions,
12+
AttrsType,
13+
UnwrapAttrsType
1214
} from './componentOptions'
1315
import {
1416
SetupContext,
@@ -54,7 +56,8 @@ export type DefineComponent<
5456
PP = PublicProps,
5557
Props = ResolveProps<PropsOrPropOptions, E>,
5658
Defaults = ExtractDefaultPropTypes<PropsOrPropOptions>,
57-
S extends SlotsType = {}
59+
S extends SlotsType = {},
60+
Attrs extends AttrsType = {},
5861
> = ComponentPublicInstanceConstructor<
5962
CreateComponentPublicInstance<
6063
Props,
@@ -69,7 +72,8 @@ export type DefineComponent<
6972
Defaults,
7073
true,
7174
{},
72-
S
75+
S,
76+
Attrs
7377
> &
7478
Props
7579
> &
@@ -86,7 +90,8 @@ export type DefineComponent<
8690
Defaults,
8791
{},
8892
string,
89-
S
93+
S,
94+
Attrs
9095
> &
9196
PP
9297

@@ -101,18 +106,20 @@ export function defineComponent<
101106
Props extends Record<string, any>,
102107
E extends EmitsOptions = {},
103108
EE extends string = string,
104-
S extends SlotsType = {}
109+
S extends SlotsType = {},
110+
Attrs extends AttrsType = {}
105111
>(
106112
setup: (
107113
props: Props,
108-
ctx: SetupContext<E, S>
114+
ctx: SetupContext<E, S, Attrs>
109115
) => RenderFunction | Promise<RenderFunction>,
110116
options?: Pick<ComponentOptions, 'name' | 'inheritAttrs'> & {
111117
props?: (keyof Props)[]
112118
emits?: E | EE[]
113-
slots?: S
119+
slots?: S,
120+
attrs?: Attrs
114121
}
115-
): (props: Props & EmitsToProps<E>) => any
122+
): (props: Props & EmitsToProps<E> & UnwrapAttrsType<Attrs>) => any
116123
export function defineComponent<
117124
Props extends Record<string, any>,
118125
E extends EmitsOptions = {},
@@ -144,10 +151,11 @@ export function defineComponent<
144151
E extends EmitsOptions = {},
145152
EE extends string = string,
146153
S extends SlotsType = {},
154+
Attrs extends AttrsType = {},
147155
I extends ComponentInjectOptions = {},
148-
II extends string = string
156+
II extends string = string,
149157
>(
150-
options: ComponentOptionsWithoutProps<
158+
comp: ComponentOptionsWithoutProps<
151159
Props,
152160
RawBindings,
153161
D,
@@ -159,7 +167,8 @@ export function defineComponent<
159167
EE,
160168
I,
161169
II,
162-
S
170+
S,
171+
Attrs
163172
>
164173
): DefineComponent<
165174
Props,
@@ -174,7 +183,8 @@ export function defineComponent<
174183
PublicProps,
175184
ResolveProps<Props, E>,
176185
ExtractDefaultPropTypes<Props>,
177-
S
186+
S,
187+
Attrs
178188
>
179189

180190
// overload 3: object format with array props declaration
@@ -191,11 +201,12 @@ export function defineComponent<
191201
E extends EmitsOptions = {},
192202
EE extends string = string,
193203
S extends SlotsType = {},
204+
Attrs extends AttrsType = {},
194205
I extends ComponentInjectOptions = {},
195206
II extends string = string,
196207
Props = Readonly<{ [key in PropNames]?: any }>
197208
>(
198-
options: ComponentOptionsWithArrayProps<
209+
comp: ComponentOptionsWithArrayProps<
199210
PropNames,
200211
RawBindings,
201212
D,
@@ -207,7 +218,8 @@ export function defineComponent<
207218
EE,
208219
I,
209220
II,
210-
S
221+
S,
222+
Attrs
211223
>
212224
): DefineComponent<
213225
Props,
@@ -222,7 +234,8 @@ export function defineComponent<
222234
PublicProps,
223235
ResolveProps<Props, E>,
224236
ExtractDefaultPropTypes<Props>,
225-
S
237+
S,
238+
Attrs
226239
>
227240

228241
// overload 4: object format with object props declaration
@@ -241,9 +254,10 @@ export function defineComponent<
241254
EE extends string = string,
242255
S extends SlotsType = {},
243256
I extends ComponentInjectOptions = {},
244-
II extends string = string
257+
II extends string = string,
258+
Attrs extends AttrsType = {}
245259
>(
246-
options: ComponentOptionsWithObjectProps<
260+
comp: ComponentOptionsWithObjectProps<
247261
PropsOptions,
248262
RawBindings,
249263
D,
@@ -255,7 +269,8 @@ export function defineComponent<
255269
EE,
256270
I,
257271
II,
258-
S
272+
S,
273+
Attrs
259274
>
260275
): DefineComponent<
261276
PropsOptions,
@@ -270,7 +285,8 @@ export function defineComponent<
270285
PublicProps,
271286
ResolveProps<PropsOptions, E>,
272287
ExtractDefaultPropTypes<PropsOptions>,
273-
S
288+
S,
289+
Attrs
274290
>
275291

276292
// implementation, close to no-op

packages/runtime-core/src/apiSetupHelpers.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ import {
1111
setCurrentInstance,
1212
SetupContext,
1313
createSetupContext,
14-
unsetCurrentInstance
14+
unsetCurrentInstance,
15+
Data
1516
} from './component'
1617
import { EmitFn, EmitsOptions, ObjectEmitsOptions } from './componentEmits'
1718
import {
@@ -349,8 +350,8 @@ export function useSlots(): SetupContext['slots'] {
349350
return getContext().slots
350351
}
351352

352-
export function useAttrs(): SetupContext['attrs'] {
353-
return getContext().attrs
353+
export function useAttrs<T extends Data = {}>(): SetupContext['attrs'] {
354+
return getContext().attrs as T
354355
}
355356

356357
export function useModel<T extends Record<string, any>, K extends keyof T>(

packages/runtime-core/src/component.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,12 @@ import { AppContext, createAppContext, AppConfig } from './apiCreateApp'
4040
import { Directive, validateDirectiveName } from './directives'
4141
import {
4242
applyOptions,
43+
AttrsType,
4344
ComponentOptions,
4445
ComputedOptions,
4546
MethodOptions,
46-
resolveMergedOptions
47+
resolveMergedOptions,
48+
UnwrapAttrsType
4749
} from './componentOptions'
4850
import {
4951
EmitsOptions,
@@ -184,10 +186,11 @@ type LifecycleHook<TFn = Function> = TFn[] | null
184186
// use `E extends any` to force evaluating type to fix #2362
185187
export type SetupContext<
186188
E = EmitsOptions,
187-
S extends SlotsType = {}
189+
S extends SlotsType = {},
190+
Attrs extends AttrsType = {}
188191
> = E extends any
189192
? {
190-
attrs: Data
193+
attrs: UnwrapAttrsType<Attrs>,
191194
slots: UnwrapSlotsType<S>
192195
emit: EmitFn<E>
193196
expose: (exposed?: Record<string, any>) => void

0 commit comments

Comments
 (0)