0
# Development Tools
1
2
Development and debugging features including logging plugin, DevTools integration, and state subscription mechanisms.
3
4
## Capabilities
5
6
### Logger Plugin
7
8
Development plugin for logging store mutations and actions to the console.
9
10
```typescript { .api }
11
/**
12
* Create a logger plugin for development debugging
13
* @param options - Logger configuration options
14
* @returns Plugin function for store
15
*/
16
function createLogger<S>(options?: LoggerOption<S>): Plugin<S>;
17
18
interface LoggerOption<S> {
19
/** Whether to collapse log groups (default: true) */
20
collapsed?: boolean;
21
/** Filter function to determine which mutations to log */
22
filter?: <P extends Payload>(mutation: P, stateBefore: S, stateAfter: S) => boolean;
23
/** Transform state before logging */
24
transformer?: (state: S) => any;
25
/** Transform mutations before logging */
26
mutationTransformer?: <P extends Payload>(mutation: P) => any;
27
/** Filter function to determine which actions to log */
28
actionFilter?: <P extends Payload>(action: P, state: S) => boolean;
29
/** Transform actions before logging */
30
actionTransformer?: <P extends Payload>(action: P) => any;
31
/** Whether to log mutations (default: true) */
32
logMutations?: boolean;
33
/** Whether to log actions (default: true) */
34
logActions?: boolean;
35
/** Custom logger object (default: console) */
36
logger?: Logger;
37
}
38
39
interface Logger extends Partial<Pick<Console, 'groupCollapsed' | 'group' | 'groupEnd'>> {
40
log(message: string, color: string, payload: any): void;
41
log(message: string): void;
42
}
43
44
interface Payload {
45
type: string;
46
}
47
48
type Plugin<S> = (store: Store<S>) => any;
49
```
50
51
**Usage Examples:**
52
53
```typescript
54
import { createStore, createLogger } from 'vuex';
55
56
// Basic logger setup
57
const store = createStore({
58
state: { count: 0 },
59
mutations: {
60
increment(state) { state.count++; }
61
},
62
plugins: process.env.NODE_ENV !== 'production'
63
? [createLogger()]
64
: []
65
});
66
67
// Advanced logger configuration
68
const loggerPlugin = createLogger({
69
collapsed: false, // Don't collapse log groups
70
71
// Only log mutations that change important state
72
filter: (mutation, stateBefore, stateAfter) => {
73
return mutation.type !== 'UPDATE_SCROLL_POSITION';
74
},
75
76
// Transform state to hide sensitive data
77
transformer: (state) => ({
78
...state,
79
user: state.user ? { ...state.user, password: '[HIDDEN]' } : null
80
}),
81
82
// Format mutation display
83
mutationTransformer: (mutation) => ({
84
type: mutation.type,
85
payload: mutation.type.includes('PASSWORD')
86
? '[HIDDEN]'
87
: mutation.payload
88
}),
89
90
// Only log specific actions
91
actionFilter: (action, state) => {
92
return !action.type.startsWith('analytics/');
93
},
94
95
// Custom action formatting
96
actionTransformer: (action) => ({
97
...action,
98
timestamp: Date.now()
99
}),
100
101
// Disable action logging in production
102
logActions: process.env.NODE_ENV !== 'production',
103
104
// Custom logger
105
logger: {
106
log: console.log,
107
groupCollapsed: console.groupCollapsed,
108
groupEnd: console.groupEnd
109
}
110
});
111
112
const store = createStore({
113
// ... store config
114
plugins: [loggerPlugin]
115
});
116
```
117
118
### State Subscription
119
120
Subscribe to mutations and actions for debugging and monitoring.
121
122
```typescript { .api }
123
/**
124
* Subscribe to store mutations
125
* @param fn - Callback function called on each mutation
126
* @param options - Subscription options
127
* @returns Unsubscribe function
128
*/
129
subscribe<P extends MutationPayload>(
130
fn: (mutation: P, state: S) => any,
131
options?: SubscribeOptions
132
): () => void;
133
134
/**
135
* Subscribe to store actions
136
* @param fn - Callback function or options object
137
* @param options - Subscription options
138
* @returns Unsubscribe function
139
*/
140
subscribeAction<P extends ActionPayload>(
141
fn: SubscribeActionOptions<P, S>,
142
options?: SubscribeOptions
143
): () => void;
144
145
interface MutationPayload extends Payload {
146
payload: any;
147
}
148
149
interface ActionPayload extends Payload {
150
payload: any;
151
}
152
153
interface SubscribeOptions {
154
/** Add subscriber to beginning of list */
155
prepend?: boolean;
156
}
157
158
type ActionSubscriber<P, S> = (action: P, state: S) => any;
159
type ActionErrorSubscriber<P, S> = (action: P, state: S, error: Error) => any;
160
161
interface ActionSubscribersObject<P, S> {
162
before?: ActionSubscriber<P, S>;
163
after?: ActionSubscriber<P, S>;
164
error?: ActionErrorSubscriber<P, S>;
165
}
166
167
type SubscribeActionOptions<P, S> = ActionSubscriber<P, S> | ActionSubscribersObject<P, S>;
168
```
169
170
**Usage Examples:**
171
172
```typescript
173
import { createStore } from 'vuex';
174
175
const store = createStore({
176
state: { count: 0, logs: [] },
177
mutations: {
178
increment(state) { state.count++; },
179
addLog(state, message) { state.logs.push(message); }
180
},
181
actions: {
182
async fetchData({ commit }) {
183
const data = await api.getData();
184
commit('setData', data);
185
}
186
}
187
});
188
189
// Subscribe to mutations
190
const unsubscribeMutations = store.subscribe((mutation, state) => {
191
console.log('Mutation:', mutation.type);
192
console.log('Payload:', mutation.payload);
193
console.log('New state:', state);
194
195
// Log mutations to server
196
analytics.track('store_mutation', {
197
type: mutation.type,
198
payload: mutation.payload
199
});
200
});
201
202
// Subscribe to actions with lifecycle hooks
203
const unsubscribeActions = store.subscribeAction({
204
before: (action, state) => {
205
console.log(`Before action ${action.type}`, action.payload);
206
// Start performance timing
207
performance.mark(`action-${action.type}-start`);
208
},
209
210
after: (action, state) => {
211
console.log(`After action ${action.type}`, state);
212
// End performance timing
213
performance.mark(`action-${action.type}-end`);
214
performance.measure(
215
`action-${action.type}`,
216
`action-${action.type}-start`,
217
`action-${action.type}-end`
218
);
219
},
220
221
error: (action, state, error) => {
222
console.error(`Action ${action.type} failed:`, error);
223
// Log errors to monitoring service
224
errorReporting.captureException(error, {
225
action: action.type,
226
payload: action.payload,
227
state
228
});
229
}
230
});
231
232
// Subscribe with prepend option
233
const debugSubscriber = store.subscribe((mutation, state) => {
234
// This will be called first due to prepend: true
235
console.log('Debug:', mutation);
236
}, { prepend: true });
237
238
// Cleanup subscriptions
239
const cleanup = () => {
240
unsubscribeMutations();
241
unsubscribeActions();
242
debugSubscriber();
243
};
244
245
// Auto-cleanup on app unmount
246
window.addEventListener('beforeunload', cleanup);
247
```
248
249
### State Watching
250
251
Watch specific parts of the store state for changes.
252
253
```typescript { .api }
254
/**
255
* Watch a computed value derived from store state
256
* @param getter - Function that returns the value to watch
257
* @param cb - Callback function called when value changes
258
* @param options - Watch options (Vue WatchOptions)
259
* @returns Unwatch function
260
*/
261
watch<T>(
262
getter: (state: S, getters: any) => T,
263
cb: (value: T, oldValue: T) => void,
264
options?: WatchOptions
265
): () => void;
266
267
// Note: WatchOptions is imported from Vue and includes:
268
// { deep?: boolean; immediate?: boolean; flush?: 'pre' | 'post' | 'sync' }
269
```
270
271
**Usage Examples:**
272
273
```typescript
274
import { createStore } from 'vuex';
275
276
const store = createStore({
277
state: {
278
user: null,
279
cart: { items: [] },
280
ui: { theme: 'light' }
281
},
282
getters: {
283
cartTotal: state => state.cart.items.reduce((sum, item) =>
284
sum + item.price * item.quantity, 0),
285
itemCount: state => state.cart.items.length
286
}
287
});
288
289
// Watch specific state property
290
const unwatchUser = store.watch(
291
(state) => state.user,
292
(newUser, oldUser) => {
293
console.log('User changed:', { newUser, oldUser });
294
295
if (newUser && !oldUser) {
296
// User logged in
297
analytics.track('user_login', { userId: newUser.id });
298
} else if (!newUser && oldUser) {
299
// User logged out
300
analytics.track('user_logout', { userId: oldUser.id });
301
}
302
},
303
{ deep: true } // Watch object properties
304
);
305
306
// Watch computed getter
307
const unwatchCartTotal = store.watch(
308
(state, getters) => getters.cartTotal,
309
(newTotal, oldTotal) => {
310
console.log(`Cart total changed: ${oldTotal} -> ${newTotal}`);
311
312
// Update localStorage
313
localStorage.setItem('cartTotal', newTotal.toString());
314
315
// Show notification for significant changes
316
if (newTotal - oldTotal > 100) {
317
showNotification('Large price change detected!');
318
}
319
}
320
);
321
322
// Watch multiple values
323
const unwatchMultiple = store.watch(
324
(state, getters) => ({
325
user: state.user,
326
itemCount: getters.itemCount,
327
theme: state.ui.theme
328
}),
329
(newValues, oldValues) => {
330
// React to changes in any watched value
331
console.log('Multiple values changed:', { newValues, oldValues });
332
},
333
{ deep: true }
334
);
335
336
// Watch with immediate option
337
const unwatchImmediate = store.watch(
338
(state) => state.ui.theme,
339
(theme) => {
340
document.body.className = `theme-${theme}`;
341
},
342
{ immediate: true } // Call immediately with current value
343
);
344
345
// Conditional watching
346
let themeWatcher = null;
347
348
const startThemeWatching = () => {
349
if (!themeWatcher) {
350
themeWatcher = store.watch(
351
(state) => state.ui.theme,
352
(theme) => applyTheme(theme)
353
);
354
}
355
};
356
357
const stopThemeWatching = () => {
358
if (themeWatcher) {
359
themeWatcher();
360
themeWatcher = null;
361
}
362
};
363
364
// Cleanup all watchers
365
const cleanup = () => {
366
unwatchUser();
367
unwatchCartTotal();
368
unwatchMultiple();
369
unwatchImmediate();
370
stopThemeWatching();
371
};
372
```
373
374
### DevTools Integration
375
376
Integration with Vue DevTools browser extension for debugging.
377
378
**Usage Examples:**
379
380
```typescript
381
import { createApp } from 'vue';
382
import { createStore } from 'vuex';
383
384
// DevTools is automatically enabled in development
385
const store = createStore({
386
state: { count: 0 },
387
mutations: {
388
increment(state) { state.count++; }
389
},
390
// DevTools enabled by default in development
391
devtools: process.env.NODE_ENV !== 'production'
392
});
393
394
// Explicit DevTools control
395
const store = createStore({
396
state: { count: 0 },
397
mutations: {
398
increment(state) { state.count++; }
399
},
400
devtools: true // Force enable even in production
401
});
402
403
// Custom DevTools configuration
404
const app = createApp(App);
405
const store = createStore({
406
// ... store config
407
devtools: true
408
});
409
410
app.use(store);
411
412
// DevTools will show:
413
// - State tree with current values
414
// - Mutation history with time-travel debugging
415
// - Action dispatch logging
416
// - Module hierarchy
417
// - Getter values and dependencies
418
419
// Time-travel debugging features:
420
// 1. Click on any mutation in the timeline to see state at that point
421
// 2. Use "Commit All" to apply all mutations up to a point
422
// 3. Use "Revert" to undo mutations
423
// 4. Export/import state snapshots
424
// 5. Filter mutations and actions by type or module
425
```
426
427
### Custom Development Plugins
428
429
Create custom plugins for development workflow enhancement.
430
431
**Usage Examples:**
432
433
```typescript
434
// Performance monitoring plugin
435
const performancePlugin = (store) => {
436
const actionTimes = new Map();
437
438
store.subscribeAction({
439
before: (action) => {
440
actionTimes.set(action.type, performance.now());
441
},
442
after: (action) => {
443
const startTime = actionTimes.get(action.type);
444
const duration = performance.now() - startTime;
445
446
if (duration > 100) {
447
console.warn(`Slow action detected: ${action.type} took ${duration}ms`);
448
}
449
450
actionTimes.delete(action.type);
451
}
452
});
453
};
454
455
// State persistence plugin
456
const persistPlugin = (store) => {
457
// Load state from localStorage on store creation
458
const saved = localStorage.getItem('vuex-state');
459
if (saved) {
460
store.replaceState(JSON.parse(saved));
461
}
462
463
// Save state on every mutation
464
store.subscribe((mutation, state) => {
465
localStorage.setItem('vuex-state', JSON.stringify(state));
466
});
467
};
468
469
// Error tracking plugin
470
const errorTrackingPlugin = (store) => {
471
store.subscribeAction({
472
error: (action, state, error) => {
473
// Send to error tracking service
474
errorTracker.captureException(error, {
475
extra: {
476
action: action.type,
477
payload: action.payload,
478
state: JSON.stringify(state)
479
}
480
});
481
}
482
});
483
};
484
485
// Usage
486
const store = createStore({
487
// ... store config
488
plugins: process.env.NODE_ENV !== 'production'
489
? [createLogger(), performancePlugin, errorTrackingPlugin]
490
: [persistPlugin]
491
});
492
```