Skip to content

Commit bcc0e2b

Browse files
committed
add details on type inference
1 parent 3247c70 commit bcc0e2b

File tree

1 file changed

+99
-1
lines changed

1 file changed

+99
-1
lines changed

active-rfcs/0000-function-api.md

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ See also:
107107

108108
One of the major goals of 3.0 is to provide better built-in TypeScript type inference support. Originally we tried to address this problem with the now-abandoned [Class API RFC](https://github.com/vuejs/rfcs/pull/17), but after discussion and prototyping we discovered that using Classes [doesn't fully address the typing issue](#type-issues-with-class-api).
109109

110-
The function-based APIs, on the other hand, are naturally type-friendly. In the prototype we have already achieved full typing support for the proposed APIs. The best part is - code written in TypeScript will look almost identical to code written in plain JavaScript. There are no manual type hints needed except for dependency injection.
110+
The function-based APIs, on the other hand, are naturally type-friendly. In the prototype we have already achieved full typing support for the proposed APIs. The best part is - code written in TypeScript will look almost identical to code written in plain JavaScript. More details will be discussed later in this RFC.
111111

112112
See also:
113113

@@ -507,6 +507,104 @@ const Descendent = {
507507

508508
If provided key contains a value wrapper, `inject` will also return a value wrapper and the binding will be reactive (i.e. the child will update if ancestor mutates the provided value).
509509

510+
## Type Inference
511+
512+
To get proper type inference in TypeScript, we do need to wrap a component definition in a function call:
513+
514+
``` ts
515+
import { createComponent } from 'vue'
516+
517+
const MyComponent = createComponent({
518+
props: {
519+
msg: String
520+
},
521+
setup(props) {
522+
watch(() => props.msg, msg => { /* ... */ })
523+
return {
524+
count: value(0)
525+
}
526+
},
527+
render({ state, props }) {
528+
// state typing inferred from value returned by setup()
529+
console.log(state.count)
530+
// props typing inferred from `props` declaration
531+
console.log(props.msg)
532+
533+
// `this` exposes both state and props
534+
console.log(this.count)
535+
console.log(this.msg)
536+
}
537+
})
538+
```
539+
540+
`createComponent` is conceptually similar to 2.x's `Vue.extend`, but internally it's just a no-op for typing purposes. The returned component is the object itself, but typed in a way that would provide props inference when used in TSX expressions.
541+
542+
If you are using Single-File Components, Vetur can implicitly add the wrapper function for you.
543+
544+
### Required Props
545+
546+
By default, props are inferred as optional properties. `required: true` will be respected if present:
547+
548+
``` ts
549+
import { createComponent } from 'vue'
550+
551+
createComponent({
552+
props: {
553+
foo: {
554+
type: String,
555+
required: true
556+
},
557+
'bar: {
558+
type: String
559+
}
560+
} as const,
561+
setup(props) {
562+
props.foo // string
563+
props.bar // string | undefined
564+
}
565+
})
566+
```
567+
568+
Note that we need to add `as const` after the `props` declaration. This is because without `as const` the type will be `required: boolean` and won't qualify for `extends true` in conditional type operations.
569+
570+
> Side note: should we consider making props required by default (And can be made optional with `optional: true`)?
571+
572+
### Complex Prop Types
573+
574+
The exposed `PropType` type can be used to declare complex prop types - but it requires a force-cast via `as any`:
575+
576+
``` ts
577+
import { createComponent, PropType } from 'vue'
578+
579+
createComponent({
580+
props: {
581+
options: {
582+
type: (null as any) as PropType<{ msg: string }>,
583+
}
584+
},
585+
setup(props) {
586+
props.options // { msg: string } | undefined
587+
}
588+
})
589+
```
590+
591+
### Dependency Injection Typing
592+
593+
The `inject` method is the only API that requires manual typing:
594+
595+
``` ts
596+
import { createComponent, inject, Value } from 'vue'
597+
598+
createComponent({
599+
setup() {
600+
const count: Value<number> = inject(CountSymbol)
601+
return {
602+
count
603+
}
604+
}
605+
})
606+
```
607+
510608
# Drawbacks
511609

512610
- Makes it more difficult to reflect and manipulate component definitions.

0 commit comments

Comments
 (0)