Skip to content

Commit c211685

Browse files
committed
docs: port typescript tutorial to Vue docs
1 parent cafa1c6 commit c211685

File tree

1 file changed

+167
-0
lines changed

1 file changed

+167
-0
lines changed

docs/tutorials/typescript.md

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
---
2+
id: typescript-quick-start
3+
title: TypeScript Quick Start
4+
sidebar_label: TypeScript Quick Start
5+
hide_title: true
6+
---
7+
8+
 
9+
10+
# Vue Redux TypeScript Quick Start
11+
12+
:::tip What You'll Learn
13+
14+
- How to set up and use Redux Toolkit and Vue Redux with TypeScript
15+
16+
:::
17+
18+
:::info Prerequisites
19+
20+
- Knowledge of Vue's [Composition API](https://vuejs.org/guide/extras/composition-api-faq.html)
21+
- Understanding of [Redux terms and concepts](https://redux.js.org/tutorials/fundamentals/part-2-concepts-data-flow)
22+
- Understanding of TypeScript syntax and concepts
23+
24+
:::
25+
26+
## Introduction
27+
28+
Welcome to the Vue Redux TypeScript Quick Start tutorial! **This tutorial will briefly show how to use TypeScript with Redux Toolkit and Vue-Redux**.
29+
30+
This page focuses on just how to set up the TypeScript aspects. For explanations of what Redux is, how it works, and full examples of how to use Redux, see [the Redux core docs tutorials](https://redux.js.org/tutorials/index).
31+
32+
[Vue Redux](/) is also written in TypeScript, and also includes its own type definitions.
33+
34+
## Project Setup
35+
36+
### Define Root State and Dispatch Types
37+
38+
[Redux Toolkit's `configureStore` API](https://redux-toolkit.js.org/api/configureStore) should not need any additional typings. You will, however, want to extract the `RootState` type and the `Dispatch` type so that they can be referenced as needed. Inferring these types from the store itself means that they correctly update as you add more state slices or modify middleware settings.
39+
40+
Since those are types, it's safe to export them directly from your store setup file such as `app/store.ts` and import them directly into other files.
41+
42+
```ts title="app/store.ts"
43+
import { configureStore } from "@reduxjs/toolkit";
44+
// ...
45+
46+
const store = configureStore({
47+
reducer: {
48+
posts: postsReducer,
49+
comments: commentsReducer,
50+
users: usersReducer,
51+
},
52+
});
53+
54+
// highlight-start
55+
// Infer the `RootState` and `AppDispatch` types from the store itself
56+
export type RootState = ReturnType<typeof store.getState>;
57+
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
58+
export type AppDispatch = typeof store.dispatch;
59+
// highlight-end
60+
```
61+
62+
### Define Typed Compositions
63+
64+
While it's possible to import the `RootState` and `AppDispatch` types into each component, it's **better to create typed versions of the `useDispatch` and `useSelector` compositions for usage in your application**. This is important for a couple reasons:
65+
66+
- For `useSelector`, it saves you the need to type `(state: RootState)` every time
67+
- For `useDispatch`, the default `Dispatch` type does not know about thunks. In order to correctly dispatch thunks, you need to use the specific customized `AppDispatch` type from the store that includes the thunk middleware types, and use that with `useDispatch`. Adding a pre-typed `useDispatch` injectable keeps you from forgetting to import `AppDispatch` where it's needed.
68+
69+
Since these are actual variables, not types, it's important to define them in a separate file such as `app/injectables.ts`, not the store setup file. This allows you to import them into any component file that needs to use the injectables, and avoids potential circular import dependency issues.
70+
71+
```ts title="app/injectables.ts"
72+
import { useDispatch, useSelector } from "@reduxjs/vue-redux";
73+
import type { RootState, AppDispatch } from "./store";
74+
75+
// highlight-start
76+
// Use throughout your app instead of plain `useDispatch` and `useSelector`
77+
export const useAppDispatch = useDispatch.withTypes<AppDispatch>();
78+
export const useAppSelector = useSelector.withTypes<RootState>();
79+
// highlight-end
80+
```
81+
82+
## Application Usage
83+
84+
### Define Slice State and Action Types
85+
86+
Each slice file should define a type for its initial state value, so that `createSlice` can correctly infer the type of `state` in each case reducer.
87+
88+
All generated actions should be defined using the `PayloadAction<T>` type from Redux Toolkit, which takes the type of the `action.payload` field as its generic argument.
89+
90+
You can safely import the `RootState` type from the store file here. It's a circular import, but the TypeScript compiler can correctly handle that for types. This may be needed for use cases like writing selector functions.
91+
92+
```ts title="features/counter/counterSlice.ts"
93+
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
94+
import type { RootState } from "../../app/store";
95+
96+
// highlight-start
97+
// Define a type for the slice state
98+
interface CounterState {
99+
value: number;
100+
}
101+
102+
// Define the initial state using that type
103+
const initialState: CounterState = {
104+
value: 0,
105+
};
106+
// highlight-end
107+
108+
export const counterSlice = createSlice({
109+
name: "counter",
110+
// `createSlice` will infer the state type from the `initialState` argument
111+
initialState,
112+
reducers: {
113+
increment: (state) => {
114+
state.value += 1;
115+
},
116+
decrement: (state) => {
117+
state.value -= 1;
118+
},
119+
// highlight-start
120+
// Use the PayloadAction type to declare the contents of `action.payload`
121+
incrementByAmount: (state, action: PayloadAction<number>) => {
122+
// highlight-end
123+
state.value += action.payload;
124+
},
125+
},
126+
});
127+
128+
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
129+
130+
// Other code such as selectors can use the imported `RootState` type
131+
export const selectCount = (state: RootState) => state.counter.value;
132+
133+
export default counterSlice.reducer;
134+
```
135+
136+
The generated action creators will be correctly typed to accept a `payload` argument based on the `PayloadAction<T>` type you provided for the reducer. For example, `incrementByAmount` requires a `number` as its argument.
137+
138+
In some cases, [TypeScript may unnecessarily tighten the type of the initial state](https://github.com/reduxjs/redux-toolkit/pull/827). If that happens, you can work around it by casting the initial state using `as`, instead of declaring the type of the variable:
139+
140+
```ts
141+
// Workaround: cast state instead of declaring variable type
142+
const initialState = {
143+
value: 0,
144+
} as CounterState;
145+
```
146+
147+
### Use Typed Compositions in Components
148+
149+
In component files, import the pre-typed compositions instead of the standard injectables from Vue-Redux.
150+
151+
```vue title="features/counter/Counter.vue"
152+
<script setup>
153+
// highlight-next-line
154+
import { useAppSelector, useAppDispatch } from "app/injectables";
155+
import { decrement, increment } from "./store/counter-slice";
156+
157+
// highlight-start
158+
// The `state` arg is correctly typed as `RootState` already
159+
const count = useAppSelector((state) => state.counter.value);
160+
const dispatch = useAppDispatch();
161+
// highlight-end
162+
</script>
163+
164+
<template>
165+
<!-- omit rendering logic -->
166+
</template>
167+
```

0 commit comments

Comments
 (0)