Skip to content

Commit ae5c772

Browse files
TypeScript section (vuejs#339)
* feat: scaffolded typescript page * feat: added general instructions on TS * feat: added basic Options API usage * feat: added raw Composition API TS * feat: scaffolded typescript page * feat: added general instructions on TS * feat: added basic Options API usage * feat: added raw Composition API TS * fix: added a heading * Update src/guide/typescript-support.md Co-authored-by: Carlos Rodrigues <[email protected]> * fix: changed data typing and type annotation * fix: add computed setter example * Update src/guide/typescript-support.md Co-authored-by: Carlos Rodrigues <[email protected]> * fix: added PropType explanation * fix: removed complex typings * fix: fix computed example * Update src/guide/typescript-support.md Co-authored-by: Carlos Rodrigues <[email protected]> * fix: added fixs to Composition API * fix: fixed TS section intro * fix: removed an example for unwrapped ref * fix: fixed computed example * Update src/guide/typescript-support.md Co-authored-by: Carlos Rodrigues <[email protected]> Co-authored-by: Carlos Rodrigues <[email protected]>
1 parent b14a634 commit ae5c772

File tree

2 files changed

+272
-2
lines changed

2 files changed

+272
-2
lines changed

src/.vuepress/config.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,11 @@ const sidebar = {
8282
{
8383
title: 'Tooling',
8484
collapsable: false,
85-
children: ['/guide/single-file-component', '/guide/testing']
85+
children: [
86+
'/guide/single-file-component',
87+
'/guide/testing',
88+
'/guide/typescript-support'
89+
]
8690
},
8791
{
8892
title: 'Scaling Up',
@@ -221,7 +225,7 @@ module.exports = {
221225
ariaLabel: 'Community Menu',
222226
items: [
223227
{ text: 'Team', link: '/community/team/' },
224-
{ text: 'Partners', link: '/community/partners/' },
228+
{ text: 'Partners', link: '/community/partners' },
225229
{ text: 'Join', link: '/community/join/' },
226230
{ text: 'Themes', link: '/community/themes/' }
227231
]

src/guide/typescript-support.md

Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
# TypeScript Support
2+
3+
> [Vue CLI](https://cli.vuejs.org) provides built-in TypeScript tooling support.
4+
5+
## Official Declaration in NPM Packages
6+
7+
A static type system can help prevent many potential runtime errors as applications grow, which is why Vue 3 is written in TypeScript. This means you don't need any additional tooling to use TypeScript with Vue - it has a first-class citizen support.
8+
9+
## Recommended Configuration
10+
11+
```js
12+
// tsconfig.json
13+
{
14+
"compilerOptions": {
15+
"target": "esnext",
16+
"module": "esnext",
17+
// this enables stricter inference for data properties on `this`
18+
"strict": true,
19+
"moduleResolution": "node"
20+
}
21+
}
22+
```
23+
24+
Note that you have to include `strict: true` (or at least `noImplicitThis: true` which is a part of `strict` flag) to leverage type checking of `this` in component methods otherwise it is always treated as `any` type.
25+
26+
See [TypeScript compiler options docs](https://www.typescriptlang.org/docs/handbook/compiler-options.html) for more details.
27+
28+
## Development Tooling
29+
30+
### Project Creation
31+
32+
[Vue CLI](https://github.com/vuejs/vue-cli) can generate new projects that use TypeScript. To get started:
33+
34+
```bash
35+
# 1. Install Vue CLI, if it's not already installed
36+
npm install --global @vue/cli@next
37+
38+
# 2. Create a new project, then choose the "Manually select features" option
39+
vue create my-project-name
40+
41+
# If you already have a Vue CLI project without TypeScript, please add a proper Vue CLI plugin:
42+
vue add typescript
43+
```
44+
45+
Make sure that `script` part of the component has TypeScript set as a language:
46+
47+
```html
48+
<script lang="ts">
49+
...
50+
</script>
51+
```
52+
53+
### Editor Support
54+
55+
For developing Vue applications with TypeScript, we strongly recommend using [Visual Studio Code](https://code.visualstudio.com/), which provides great out-of-the-box support for TypeScript. If you are using [single-file components](./single-file-components.html) (SFCs), get the awesome [Vetur extension](https://github.com/vuejs/vetur), which provides TypeScript inference inside SFCs and many other great features.
56+
57+
[WebStorm](https://www.jetbrains.com/webstorm/) also provides out-of-the-box support for both TypeScript and Vue.
58+
59+
## Defining Vue components
60+
61+
To let TypeScript properly infer types inside Vue component options, you need to define components with `defineComponent` global method:
62+
63+
```ts
64+
import { defineComponent } from 'vue'
65+
66+
const Component = defineComponent({
67+
// type inference enabled
68+
})
69+
```
70+
71+
## Using with Options API
72+
73+
TypeScript should be able to infer most of the types without defining types explicitly. For example, if you have a component with a number `count` property, you will have an error if you try to call a string-specific method on it:
74+
75+
```ts
76+
const Component = defineComponent({
77+
data() {
78+
return {
79+
count: 0
80+
}
81+
},
82+
mounted() {
83+
const result = this.count.split('') // => Property 'split' does not exist on type 'number'
84+
}
85+
})
86+
```
87+
88+
If you have a complex type or interface, you can cast it using [type assertion](https://www.typescriptlang.org/docs/handbook/basic-types.html#type-assertions):
89+
90+
```ts
91+
interface Book {
92+
title: string
93+
author: string
94+
year: number
95+
}
96+
97+
const Component = defineComponent({
98+
data() {
99+
return {
100+
book: {
101+
title: 'Vue 3 Guide',
102+
author: 'Vue Team',
103+
year: 2020
104+
} as Book
105+
}
106+
}
107+
})
108+
```
109+
110+
### Annotating Return Types
111+
112+
Because of the circular nature of Vue’s declaration files, TypeScript may have difficulties inferring the types of computed. For this reason, you may need to annotate the return type computed properties.
113+
114+
```ts
115+
import { defineComponent } from 'vue'
116+
117+
const Component = defineComponent({
118+
data() {
119+
return {
120+
message: 'Hello!'
121+
}
122+
},
123+
computed: {
124+
// needs an annotation
125+
greeting(): string {
126+
return this.message + '!'
127+
}
128+
129+
// in a computed with a setter, getter needs to be annotated
130+
greetingUppercased: {
131+
get(): string {
132+
return this.greeting.toUpperCase();
133+
},
134+
set(newValue: string) {
135+
this.message = newValue.toUpperCase();
136+
},
137+
},
138+
}
139+
})
140+
```
141+
142+
### Annotating Props
143+
144+
Vue does a runtime validation on props with a `type` defined. To provide these types to TypeScript, we need to cast the constructor with `PropType`:
145+
146+
```ts
147+
import { defineComponent, PropType } from 'vue'
148+
149+
interface ComplexMessage {
150+
title: string
151+
okMessage: string
152+
cancelMessage: string
153+
}
154+
const Component = defineComponent({
155+
props: {
156+
name: String,
157+
success: { type: String },
158+
callback: {
159+
type: Function as PropType<() => void>
160+
},
161+
message: {
162+
type: Object as PropType<ComplexMessage>,
163+
required: true,
164+
validator(message: ComplexMessage) {
165+
return !!message.title
166+
}
167+
}
168+
}
169+
})
170+
```
171+
172+
If you find validator not getting type inference or member completion isn’t working, annotating the argument with the expected type may help address these problems.
173+
174+
## Using with Composition API
175+
176+
On `setup()` function, you don't need to pass a typing to `props` parameter as it will infer types from `props` component option.
177+
178+
```ts
179+
import { defineComponent } from 'vue'
180+
181+
const Component = defineComponent({
182+
props: {
183+
message: {
184+
type: String,
185+
required: true
186+
}
187+
},
188+
189+
setup(props) {
190+
const result = props.message.split('') // correct, 'message' is typed as a string
191+
const filtered = props.message.filter(p => p.value) // an error will be thrown: Property 'filter' does not exist on type 'string'
192+
}
193+
})
194+
```
195+
196+
### Typing `ref`s
197+
198+
Refs infer the type from the initial value:
199+
200+
```ts
201+
import { defineComponent, ref } from 'vue'
202+
203+
const Component = defineComponent({
204+
setup() {
205+
const year = ref(2020)
206+
207+
const result = year.value.split('') // => Property 'filter' does not exist on type 'number'
208+
}
209+
})
210+
```
211+
212+
Sometimes we may need to specify complex types for a ref's inner value. We can do that simply passing a generic argument when calling ref to override the default inference:
213+
214+
```ts
215+
const year = ref<string | number>('2020') // year's type: Ref<string | number>
216+
217+
year.value = 2020 // ok!
218+
```
219+
220+
::: tip Note
221+
If the type of the generic is unknown, it's recommended to cast `ref` to `Ref<T>`.
222+
:::
223+
224+
### Typing `reactive`
225+
226+
When typing a `reactive` property, we can use use interfaces:
227+
228+
```ts
229+
import { defineComponent, reactive } from 'vue'
230+
231+
interface Book {
232+
title: string
233+
year?: number
234+
}
235+
236+
export default defineComponent({
237+
name: 'HelloWorld',
238+
setup() {
239+
const book = reactive<Book>({ title: 'Vue 3 Guide' })
240+
// or
241+
const book: Book = reactive({ title: 'Vue 3 Guide' })
242+
// or
243+
const book = reactive({ title: 'Vue 3 Guide' }) as Book
244+
}
245+
})
246+
```
247+
248+
### Typing `computed`
249+
250+
Computed values will automatically infer the type from returned value
251+
252+
```ts
253+
import { defineComponent, ref, computed } from 'vue'
254+
255+
export default defineComponent({
256+
name: 'HelloWorld',
257+
setup() {
258+
let count = ref(0)
259+
260+
// read-only
261+
const double = computed(() => count.value)
262+
263+
const result = double.value.split('') // => Property 'split' does not exist on type 'number'
264+
}
265+
})
266+
```

0 commit comments

Comments
 (0)