Prerequisites
- Basic understanding of JavaScript ๐
- TypeScript installation โก
- VS Code or preferred IDE ๐ป
What you'll learn
- Understand Vuex fundamentals with TypeScript ๐ฏ
- Apply type-safe state management in real projects ๐๏ธ
- Debug common Vuex TypeScript issues ๐
- Write type-safe state management code โจ
๐ฏ Introduction
Welcome to this exciting tutorial on Vuex with TypeScript! ๐ In this guide, weโll explore how to build type-safe, scalable state management for your Vue applications.
Youโll discover how Vuex combined with TypeScript can transform your Vue development experience. Whether youโre building single-page applications ๐, complex dashboards ๐, or real-time interfaces โก, understanding type-safe state management is essential for writing robust, maintainable code.
By the end of this tutorial, youโll feel confident building complex state management systems with full type safety! Letโs dive in! ๐โโ๏ธ
๐ Understanding Vuex with TypeScript
๐ค What is Vuex?
Vuex is like a centralized store for your Vue application ๐ช. Think of it as a global state container that helps you manage data that needs to be shared across multiple components, with TypeScript adding compile-time safety to prevent bugs.
In TypeScript terms, Vuex provides a predictable state container with strong typing support โก. This means you can:
- โจ Catch state-related errors at compile time
- ๐ Get amazing IntelliSense and autocomplete
- ๐ก๏ธ Ensure action and mutation type safety
- ๐ Self-document your state structure
๐ก Why Use Vuex with TypeScript?
Hereโs why developers love this combination:
- Type Safety ๐: Catch state management errors at compile-time
- Better IDE Support ๐ป: Autocomplete for actions, mutations, and getters
- Code Documentation ๐: Types serve as inline documentation
- Refactoring Confidence ๐ง: Change state structure without fear
- Team Collaboration ๐ค: Clear contracts between components and store
Real-world example: Imagine building an e-commerce app ๐. With Vuex + TypeScript, you can ensure your cart state, user authentication, and product data are all type-safe and consistent across your entire application.
๐ง Basic Syntax and Usage
๐ Simple Store Setup
Letโs start with a basic typed Vuex store:
// ๐ Hello, Vuex with TypeScript!
import { createStore, Store } from 'vuex';
// ๐จ Define our state type
interface RootState {
count: number;
message: string;
isLoading: boolean;
}
// ๐ช Create our typed store
const store: Store<RootState> = createStore({
state: {
count: 0,
message: "Welcome to Vuex! ๐",
isLoading: false
},
// ๐ Getters with type safety
getters: {
doubledCount: (state): number => state.count * 2,
uppercaseMessage: (state): string => state.message.toUpperCase()
},
// ๐ Mutations for synchronous state changes
mutations: {
INCREMENT(state): void {
state.count++; // โจ Type-safe mutation
},
SET_MESSAGE(state, payload: string): void {
state.message = payload;
},
SET_LOADING(state, loading: boolean): void {
state.isLoading = loading;
}
},
// โก Actions for asynchronous operations
actions: {
async fetchData({ commit }): Promise<void> {
commit('SET_LOADING', true);
// ๐ Simulate API call
await new Promise(resolve => setTimeout(resolve, 1000));
commit('SET_MESSAGE', 'Data loaded! ๐');
commit('SET_LOADING', false);
}
}
});
export default store;
๐ก Explanation: Notice how we define types for everything! The state interface ensures type safety, and TypeScript will warn us if we try to access properties that donโt exist.
๐ฏ Using the Store in Components
Hereโs how to use the typed store in Vue components:
// ๐๏ธ Component with typed store access
import { computed, ComputedRef } from 'vue';
import { useStore } from 'vuex';
import { RootState } from './store';
export default {
setup() {
// ๐จ Get typed store instance
const store = useStore<RootState>();
// ๐ Computed properties with full type safety
const count: ComputedRef<number> = computed(() => store.state.count);
const message: ComputedRef<string> = computed(() => store.state.message);
const doubledCount: ComputedRef<number> = computed(() => store.getters.doubledCount);
// ๐ Typed action dispatching
const increment = (): void => {
store.commit('INCREMENT');
};
const updateMessage = (newMessage: string): void => {
store.commit('SET_MESSAGE', newMessage);
};
const loadData = async (): Promise<void> => {
await store.dispatch('fetchData');
};
return {
count,
message,
doubledCount,
increment,
updateMessage,
loadData
};
}
};
๐ก Practical Examples
๐ Example 1: E-commerce Shopping Cart
Letโs build a real shopping cart with full type safety:
// ๐๏ธ Define our product and cart types
interface Product {
id: string;
name: string;
price: number;
emoji: string;
stock: number;
}
interface CartItem extends Product {
quantity: number;
}
interface CartState {
items: CartItem[];
total: number;
isLoading: boolean;
discountCode?: string;
}
// ๐ช Shopping cart store module
const cartModule = {
namespaced: true,
state: (): CartState => ({
items: [],
total: 0,
isLoading: false,
discountCode: undefined
}),
getters: {
// ๐ Calculate total with type safety
cartTotal: (state: CartState): number => {
return state.items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
},
// ๐ข Get item count
itemCount: (state: CartState): number => {
return state.items.reduce((count, item) => count + item.quantity, 0);
},
// ๐ฏ Check if product is in cart
isInCart: (state: CartState) => (productId: string): boolean => {
return state.items.some(item => item.id === productId);
}
},
mutations: {
// โ Add item to cart
ADD_ITEM(state: CartState, product: Product): void {
const existingItem = state.items.find(item => item.id === product.id);
if (existingItem) {
existingItem.quantity++;
console.log(`๐ Updated ${product.emoji} ${product.name} quantity`);
} else {
state.items.push({ ...product, quantity: 1 });
console.log(`โ Added ${product.emoji} ${product.name} to cart!`);
}
},
// โ Remove item from cart
REMOVE_ITEM(state: CartState, productId: string): void {
const index = state.items.findIndex(item => item.id === productId);
if (index > -1) {
const item = state.items[index];
console.log(`๐๏ธ Removed ${item.emoji} ${item.name} from cart`);
state.items.splice(index, 1);
}
},
// ๐ Update quantity
UPDATE_QUANTITY(state: CartState, { productId, quantity }: { productId: string; quantity: number }): void {
const item = state.items.find(item => item.id === productId);
if (item) {
item.quantity = Math.max(0, quantity);
if (item.quantity === 0) {
// Auto-remove items with 0 quantity
const index = state.items.findIndex(i => i.id === productId);
state.items.splice(index, 1);
}
}
},
// ๐งน Clear entire cart
CLEAR_CART(state: CartState): void {
state.items = [];
console.log('๐งน Cart cleared!');
},
SET_LOADING(state: CartState, loading: boolean): void {
state.isLoading = loading;
}
},
actions: {
// ๐ Add product with stock validation
async addProduct({ commit, state }: any, product: Product): Promise<void> {
const existingItem = state.items.find((item: CartItem) => item.id === product.id);
const currentQuantity = existingItem ? existingItem.quantity : 0;
if (currentQuantity < product.stock) {
commit('ADD_ITEM', product);
} else {
console.warn(`โ ๏ธ Cannot add more ${product.name} - out of stock!`);
}
},
// ๐ณ Simulate checkout process
async checkout({ commit, getters, state }: any): Promise<boolean> {
if (state.items.length === 0) {
console.warn('โ ๏ธ Cannot checkout empty cart!');
return false;
}
commit('SET_LOADING', true);
try {
// ๐ Simulate API call
await new Promise(resolve => setTimeout(resolve, 2000));
console.log(`๐ Order successful! Total: $${getters.cartTotal.toFixed(2)}`);
commit('CLEAR_CART');
commit('SET_LOADING', false);
return true;
} catch (error) {
console.error('๐ฅ Checkout failed:', error);
commit('SET_LOADING', false);
return false;
}
}
}
};
// ๐ฎ Usage example
const store = createStore({
modules: {
cart: cartModule
}
});
// ๐๏ธ Sample products
const products: Product[] = [
{ id: '1', name: 'TypeScript Book', price: 29.99, emoji: '๐', stock: 5 },
{ id: '2', name: 'Coffee Mug', price: 12.99, emoji: 'โ', stock: 10 },
{ id: '3', name: 'Keyboard', price: 89.99, emoji: 'โจ๏ธ', stock: 3 }
];
๐ฏ Try it yourself: Add a favorites system and implement discount code functionality!
๐ฎ Example 2: User Authentication State
Letโs create a complete auth system:
// ๐ค User and auth types
interface User {
id: string;
email: string;
name: string;
avatar?: string;
role: 'admin' | 'user' | 'moderator';
}
interface AuthState {
user: User | null;
token: string | null;
isAuthenticated: boolean;
isLoading: boolean;
loginAttempts: number;
lastLoginAt?: Date;
}
// ๐ Authentication store module
const authModule = {
namespaced: true,
state: (): AuthState => ({
user: null,
token: localStorage.getItem('auth_token'),
isAuthenticated: false,
isLoading: false,
loginAttempts: 0,
lastLoginAt: undefined
}),
getters: {
// ๐ฏ Check if user has specific role
hasRole: (state: AuthState) => (role: User['role']): boolean => {
return state.user?.role === role || false;
},
// ๐ค Get user display name
displayName: (state: AuthState): string => {
return state.user?.name || 'Guest User ๐';
},
// ๐ Check if user can perform action
canPerformAction: (state: AuthState) => (requiredRole: User['role']): boolean => {
if (!state.user) return false;
const roleHierarchy = { 'user': 1, 'moderator': 2, 'admin': 3 };
const userLevel = roleHierarchy[state.user.role];
const requiredLevel = roleHierarchy[requiredRole];
return userLevel >= requiredLevel;
}
},
mutations: {
// ๐ Set authenticated user
SET_USER(state: AuthState, user: User): void {
state.user = user;
state.isAuthenticated = true;
state.lastLoginAt = new Date();
console.log(`๐ Welcome back, ${user.name}!`);
},
// ๐ Set auth token
SET_TOKEN(state: AuthState, token: string): void {
state.token = token;
localStorage.setItem('auth_token', token);
},
// ๐ Logout user
LOGOUT(state: AuthState): void {
state.user = null;
state.token = null;
state.isAuthenticated = false;
state.loginAttempts = 0;
localStorage.removeItem('auth_token');
console.log('๐ See you later!');
},
SET_LOADING(state: AuthState, loading: boolean): void {
state.isLoading = loading;
},
// ๐ Track login attempts
INCREMENT_LOGIN_ATTEMPTS(state: AuthState): void {
state.loginAttempts++;
},
RESET_LOGIN_ATTEMPTS(state: AuthState): void {
state.loginAttempts = 0;
}
},
actions: {
// ๐ Login action
async login({ commit, state }: any, credentials: { email: string; password: string }): Promise<boolean> {
if (state.loginAttempts >= 3) {
console.warn('โ ๏ธ Too many login attempts. Please try again later.');
return false;
}
commit('SET_LOADING', true);
try {
// ๐ Simulate API login
await new Promise(resolve => setTimeout(resolve, 1500));
// Mock successful login
const mockUser: User = {
id: '123',
email: credentials.email,
name: 'Sarah Developer',
avatar: '๐ฉโ๐ป',
role: 'user'
};
const mockToken = 'jwt_token_here_' + Date.now();
commit('SET_USER', mockUser);
commit('SET_TOKEN', mockToken);
commit('RESET_LOGIN_ATTEMPTS');
commit('SET_LOADING', false);
return true;
} catch (error) {
commit('INCREMENT_LOGIN_ATTEMPTS');
commit('SET_LOADING', false);
console.error('๐ฅ Login failed:', error);
return false;
}
},
// ๐ Refresh user session
async refreshSession({ commit, state }: any): Promise<void> {
if (!state.token) return;
try {
// ๐ Simulate token refresh
await new Promise(resolve => setTimeout(resolve, 1000));
console.log('๐ Session refreshed successfully!');
} catch (error) {
console.error('๐ฅ Session refresh failed:', error);
commit('LOGOUT');
}
},
// ๐ Logout action
async logout({ commit }: any): Promise<void> {
commit('SET_LOADING', true);
// ๐ Simulate logout API call
await new Promise(resolve => setTimeout(resolve, 500));
commit('LOGOUT');
commit('SET_LOADING', false);
}
}
};
๐ Advanced Concepts
๐งโโ๏ธ Advanced Topic 1: Typed Store Modules
When youโre ready to level up, try creating fully typed store modules:
// ๐ฏ Advanced typed module system
import { Module, ActionContext } from 'vuex';
// ๐๏ธ Define module state types
interface NotificationState {
notifications: Notification[];
maxNotifications: number;
}
interface Notification {
id: string;
type: 'success' | 'error' | 'warning' | 'info';
title: string;
message: string;
emoji: string;
timestamp: Date;
autoClose: boolean;
duration: number;
}
// ๐จ Root state interface
interface RootState {
notifications: NotificationState;
auth: AuthState;
// ... other modules
}
// ๐ง Typed action context
type NotificationActionContext = ActionContext<NotificationState, RootState>;
// ๐ช Fully typed notification module
const notificationModule: Module<NotificationState, RootState> = {
namespaced: true,
state: (): NotificationState => ({
notifications: [],
maxNotifications: 5
}),
getters: {
activeNotifications: (state): Notification[] => {
return state.notifications.filter(n => !n.autoClose ||
Date.now() - n.timestamp.getTime() < n.duration);
},
hasErrors: (state): boolean => {
return state.notifications.some(n => n.type === 'error');
}
},
mutations: {
ADD_NOTIFICATION(state: NotificationState, notification: Omit<Notification, 'id' | 'timestamp'>): void {
const newNotification: Notification = {
...notification,
id: Date.now().toString(),
timestamp: new Date()
};
state.notifications.unshift(newNotification);
// Keep only max notifications
if (state.notifications.length > state.maxNotifications) {
state.notifications = state.notifications.slice(0, state.maxNotifications);
}
console.log(`${notification.emoji} ${notification.title}`);
},
REMOVE_NOTIFICATION(state: NotificationState, notificationId: string): void {
const index = state.notifications.findIndex(n => n.id === notificationId);
if (index > -1) {
state.notifications.splice(index, 1);
}
}
},
actions: {
showSuccess({ commit }: NotificationActionContext, { title, message }: { title: string; message: string }): void {
commit('ADD_NOTIFICATION', {
type: 'success',
title,
message,
emoji: 'โ
',
autoClose: true,
duration: 3000
});
},
showError({ commit }: NotificationActionContext, { title, message }: { title: string; message: string }): void {
commit('ADD_NOTIFICATION', {
type: 'error',
title,
message,
emoji: 'โ',
autoClose: false,
duration: 0
});
}
}
};
๐๏ธ Advanced Topic 2: Store Composition with TypeScript
For complex applications, compose multiple typed modules:
// ๐ Advanced store composition
import { createStore, Store } from 'vuex';
import { InjectionKey } from 'vue';
// ๐ฏ Create injection key for typed store
export const key: InjectionKey<Store<RootState>> = Symbol();
// ๐ช Composed store with all modules
export const store = createStore<RootState>({
modules: {
auth: authModule,
cart: cartModule,
notifications: notificationModule
},
// ๐ง Global plugins
plugins: [
// ๐พ Persistence plugin
(store) => {
store.subscribe((mutation, state) => {
if (mutation.type.startsWith('cart/')) {
localStorage.setItem('cart_state', JSON.stringify(state.cart));
}
});
}
]
});
// ๐จ Helper for typed store access in components
export function useTypedStore(): Store<RootState> {
return useStore(key);
}
โ ๏ธ Common Pitfalls and Solutions
๐ฑ Pitfall 1: Losing Type Safety in Actions
// โ Wrong way - losing type information!
const badAction = async ({ commit }: any, payload: any): Promise<void> => {
commit('SOME_MUTATION', payload); // ๐ฅ No type checking!
};
// โ
Correct way - maintain full type safety!
const goodAction = async (
{ commit }: ActionContext<CartState, RootState>,
product: Product
): Promise<void> => {
commit('ADD_ITEM', product); // โ
Fully typed!
};
๐คฏ Pitfall 2: Forgetting Namespaced Module Types
// โ Dangerous - accessing non-namespaced!
store.commit('ADD_ITEM', product); // ๐ฅ Won't work with namespaced modules!
// โ
Safe - use correct namespace!
store.commit('cart/ADD_ITEM', product); // โ
Correctly namespaced!
๐ฐ Pitfall 3: Mutation vs Action Confusion
// โ Wrong - async operation in mutation
mutations: {
async FETCH_DATA(state): Promise<void> { // ๐ฅ Mutations must be synchronous!
const data = await api.getData();
state.data = data;
}
}
// โ
Correct - async in actions, sync in mutations
actions: {
async fetchData({ commit }: ActionContext<State, RootState>): Promise<void> {
const data = await api.getData(); // โ
Async operations belong here
commit('SET_DATA', data); // โ
Then commit synchronously
}
},
mutations: {
SET_DATA(state: State, data: any[]): void { // โ
Synchronous only
state.data = data;
}
}
๐ ๏ธ Best Practices
- ๐ฏ Always Type Your State: Define interfaces for all state structures
- ๐ Use Namespaced Modules: Keep your store organized and prevent conflicts
- ๐ก๏ธ Strict Typing: Use proper ActionContext types for actions
- โจ Keep Mutations Simple: Only synchronous state changes in mutations
- ๐ Actions for Async: All async operations belong in actions
- ๐พ Consider Persistence: Save important state to localStorage
- ๐งช Test Your Store: Write unit tests for your state management logic
๐งช Hands-On Exercise
๐ฏ Challenge: Build a Task Management Store
Create a complete task management system with full TypeScript support:
๐ Requirements:
- โ Tasks with title, description, status, priority, and due dates
- ๐ท๏ธ Categories for task organization (work, personal, urgent)
- ๐ค User assignment and collaboration features
- ๐ Analytics and progress tracking
- ๐ Advanced filtering and sorting
- ๐จ Each task needs an emoji and color coding!
๐ Bonus Points:
- Add drag-and-drop reordering
- Implement task dependencies
- Create time tracking functionality
- Add team collaboration features
- Build a dashboard with charts
๐ก Solution
๐ Click to see solution
// ๐ฏ Complete task management system with TypeScript!
// ๐ Task-related types
interface Task {
id: string;
title: string;
description: string;
status: 'todo' | 'in_progress' | 'completed' | 'blocked';
priority: 'low' | 'medium' | 'high' | 'urgent';
category: 'work' | 'personal' | 'urgent' | 'health' | 'learning';
emoji: string;
color: string;
dueDate?: Date;
createdAt: Date;
updatedAt: Date;
assignee?: string;
timeSpent: number; // in minutes
estimatedTime?: number; // in minutes
tags: string[];
dependencies: string[]; // task IDs this task depends on
}
interface TaskState {
tasks: Task[];
categories: string[];
selectedCategory: string | null;
sortBy: 'dueDate' | 'priority' | 'createdAt' | 'title';
sortOrder: 'asc' | 'desc';
isLoading: boolean;
searchQuery: string;
}
// ๐ช Task management store module
const taskModule: Module<TaskState, RootState> = {
namespaced: true,
state: (): TaskState => ({
tasks: [],
categories: ['work', 'personal', 'urgent', 'health', 'learning'],
selectedCategory: null,
sortBy: 'dueDate',
sortOrder: 'asc',
isLoading: false,
searchQuery: ''
}),
getters: {
// ๐ Get tasks by status
tasksByStatus: (state: TaskState) => (status: Task['status']): Task[] => {
return state.tasks.filter(task => task.status === status);
},
// ๐ฏ Get filtered and sorted tasks
filteredTasks: (state: TaskState): Task[] => {
let filtered = state.tasks;
// Filter by category
if (state.selectedCategory) {
filtered = filtered.filter(task => task.category === state.selectedCategory);
}
// Filter by search query
if (state.searchQuery) {
const query = state.searchQuery.toLowerCase();
filtered = filtered.filter(task =>
task.title.toLowerCase().includes(query) ||
task.description.toLowerCase().includes(query) ||
task.tags.some(tag => tag.toLowerCase().includes(query))
);
}
// Sort tasks
filtered.sort((a, b) => {
let compareValue = 0;
switch (state.sortBy) {
case 'priority':
const priorityOrder = { 'low': 1, 'medium': 2, 'high': 3, 'urgent': 4 };
compareValue = priorityOrder[a.priority] - priorityOrder[b.priority];
break;
case 'dueDate':
if (a.dueDate && b.dueDate) {
compareValue = a.dueDate.getTime() - b.dueDate.getTime();
} else if (a.dueDate) {
compareValue = -1;
} else if (b.dueDate) {
compareValue = 1;
}
break;
case 'createdAt':
compareValue = a.createdAt.getTime() - b.createdAt.getTime();
break;
case 'title':
compareValue = a.title.localeCompare(b.title);
break;
}
return state.sortOrder === 'asc' ? compareValue : -compareValue;
});
return filtered;
},
// ๐ Get completion statistics
completionStats: (state: TaskState) => {
const total = state.tasks.length;
const completed = state.tasks.filter(t => t.status === 'completed').length;
const inProgress = state.tasks.filter(t => t.status === 'in_progress').length;
const blocked = state.tasks.filter(t => t.status === 'blocked').length;
return {
total,
completed,
inProgress,
blocked,
completionRate: total > 0 ? Math.round((completed / total) * 100) : 0
};
},
// โฐ Get overdue tasks
overdueTasks: (state: TaskState): Task[] => {
const now = new Date();
return state.tasks.filter(task =>
task.dueDate &&
task.dueDate < now &&
task.status !== 'completed'
);
},
// ๐ฏ Get task dependencies
getTaskDependencies: (state: TaskState) => (taskId: string): Task[] => {
const task = state.tasks.find(t => t.id === taskId);
if (!task) return [];
return task.dependencies
.map(depId => state.tasks.find(t => t.id === depId))
.filter(Boolean) as Task[];
}
},
mutations: {
// โ Add new task
ADD_TASK(state: TaskState, taskData: Omit<Task, 'id' | 'createdAt' | 'updatedAt'>): void {
const newTask: Task = {
...taskData,
id: Date.now().toString() + Math.random().toString(36).substr(2, 9),
createdAt: new Date(),
updatedAt: new Date()
};
state.tasks.unshift(newTask);
console.log(`โ
Created task: ${newTask.emoji} ${newTask.title}`);
},
// ๐ Update existing task
UPDATE_TASK(state: TaskState, { taskId, updates }: { taskId: string; updates: Partial<Task> }): void {
const taskIndex = state.tasks.findIndex(t => t.id === taskId);
if (taskIndex > -1) {
state.tasks[taskIndex] = {
...state.tasks[taskIndex],
...updates,
updatedAt: new Date()
};
console.log(`๐ Updated task: ${state.tasks[taskIndex].title}`);
}
},
// ๐๏ธ Delete task
DELETE_TASK(state: TaskState, taskId: string): void {
const taskIndex = state.tasks.findIndex(t => t.id === taskId);
if (taskIndex > -1) {
const task = state.tasks[taskIndex];
state.tasks.splice(taskIndex, 1);
console.log(`๐๏ธ Deleted task: ${task.title}`);
}
},
// ๐ฏ Set category filter
SET_CATEGORY_FILTER(state: TaskState, category: string | null): void {
state.selectedCategory = category;
},
// ๐ Set search query
SET_SEARCH_QUERY(state: TaskState, query: string): void {
state.searchQuery = query;
},
// ๐ Set sorting
SET_SORTING(state: TaskState, { sortBy, sortOrder }: { sortBy: TaskState['sortBy']; sortOrder: TaskState['sortOrder'] }): void {
state.sortBy = sortBy;
state.sortOrder = sortOrder;
},
SET_LOADING(state: TaskState, loading: boolean): void {
state.isLoading = loading;
}
},
actions: {
// ๐ Create task with validation
async createTask({ commit }: ActionContext<TaskState, RootState>, taskData: Omit<Task, 'id' | 'createdAt' | 'updatedAt'>): Promise<boolean> {
try {
// ๐ Validate task data
if (!taskData.title.trim()) {
console.warn('โ ๏ธ Task title is required');
return false;
}
if (taskData.dueDate && taskData.dueDate < new Date()) {
console.warn('โ ๏ธ Due date cannot be in the past');
return false;
}
commit('ADD_TASK', taskData);
return true;
} catch (error) {
console.error('๐ฅ Failed to create task:', error);
return false;
}
},
// โ
Complete task
async completeTask({ commit, getters }: ActionContext<TaskState, RootState>, taskId: string): Promise<void> {
const task = getters.filteredTasks.find((t: Task) => t.id === taskId);
if (task) {
commit('UPDATE_TASK', {
taskId,
updates: { status: 'completed' as const }
});
// ๐ Show success notification
await this.dispatch('notifications/showSuccess', {
title: 'Task Completed! ๐',
message: `Great job finishing "${task.title}"!`
}, { root: true });
}
},
// ๐ Generate productivity report
async generateReport({ state, getters }: ActionContext<TaskState, RootState>): Promise<any> {
const stats = getters.completionStats;
const overdueTasks = getters.overdueTasks;
const report = {
summary: stats,
overdueTasks: overdueTasks.length,
categoryBreakdown: state.categories.map(category => ({
category,
total: state.tasks.filter(t => t.category === category).length,
completed: state.tasks.filter(t => t.category === category && t.status === 'completed').length
})),
timeTracking: {
totalTimeSpent: state.tasks.reduce((sum, task) => sum + task.timeSpent, 0),
averageTimePerTask: state.tasks.length > 0
? Math.round(state.tasks.reduce((sum, task) => sum + task.timeSpent, 0) / state.tasks.length)
: 0
}
};
console.log('๐ Productivity Report Generated:', report);
return report;
}
}
};
// ๐ฎ Usage example in a component
export default {
setup() {
const store = useTypedStore();
// ๐ Reactive computed properties
const tasks = computed(() => store.getters['tasks/filteredTasks']);
const stats = computed(() => store.getters['tasks/completionStats']);
const overdueTasks = computed(() => store.getters['tasks/overdueTasks']);
// ๐ฏ Task management methods
const createTask = async (taskData: Omit<Task, 'id' | 'createdAt' | 'updatedAt'>): Promise<void> => {
await store.dispatch('tasks/createTask', taskData);
};
const completeTask = async (taskId: string): Promise<void> => {
await store.dispatch('tasks/completeTask', taskId);
};
const generateReport = async (): Promise<void> => {
await store.dispatch('tasks/generateReport');
};
return {
tasks,
stats,
overdueTasks,
createTask,
completeTask,
generateReport
};
}
};
๐ Key Takeaways
Youโve learned so much! Hereโs what you can now do:
- โ Create type-safe Vuex stores with confidence ๐ช
- โ Avoid common state management mistakes that trip up beginners ๐ก๏ธ
- โ Apply TypeScript best practices in Vue applications ๐ฏ
- โ Debug complex state issues like a pro ๐
- โ Build scalable applications with predictable state! ๐
Remember: Vuex with TypeScript is your superpower for building reliable, maintainable Vue applications. The initial setup takes time, but it pays dividends in the long run! ๐ค
๐ค Next Steps
Congratulations! ๐ Youโve mastered Vuex with TypeScript state management!
Hereโs what to do next:
- ๐ป Practice with the task management exercise above
- ๐๏ธ Build a real project using typed Vuex stores
- ๐ Move on to our next tutorial: Pinia with TypeScript (Modern State Management)
- ๐ Share your awesome projects with the community!
Remember: Every Vue expert was once a beginner. Keep building, keep learning, and most importantly, have fun with your type-safe state management! ๐
Happy coding! ๐๐โจ