MobX 中的中间件和拦截器如何使用?
MobX 的中间件和拦截器提供了强大的功能,可以在状态变化前后执行自定义逻辑。以下是 MobX 中间件和拦截器的详细使用方法:1. 拦截器(Intercept)基本用法拦截器允许在状态变化之前拦截和修改操作。import { observable, intercept } from 'mobx';const store = observable({ count: 0, items: []});// 拦截 count 的变化const dispose = intercept(store, 'count', (change) => { console.log('Before change:', change); // 可以修改变化 if (change.newValue < 0) { change.newValue = 0; // 不允许负数 } // 可以取消变化 if (change.newValue > 100) { return null; // 取消变化 } return change; // 允许变化});store.count = 5; // Before change: { type: 'update', object: store, name: 'count', newValue: 5 }console.log(store.count); // 5store.count = -1; // Before change: { type: 'update', object: store, name: 'count', newValue: -1 }console.log(store.count); // 0 (被拦截器修改)store.count = 101; // Before change: { type: 'update', object: store, name: 'count', newValue: 101 }console.log(store.count); // 0 (被拦截器取消)dispose(); // 清理拦截器拦截数组操作const items = observable([1, 2, 3]);intercept(items, (change) => { console.log('Array change:', change); // 拦截 push 操作 if (change.type === 'add') { if (typeof change.newValue !== 'number') { throw new Error('Only numbers allowed'); } } return change;});items.push(4); // Array change: { type: 'add', object: items, name: '3', newValue: 4 }items.push('invalid'); // Error: Only numbers allowed拦截 Map 操作const map = observable(new Map());intercept(map, (change) => { console.log('Map change:', change); // 拦截 set 操作 if (change.type === 'update' || change.type === 'add') { if (change.name === 'secret') { throw new Error('Cannot set secret key'); } } return change;});map.set('key1', 'value1'); // Map change: { type: 'add', object: map, name: 'key1', newValue: 'value1' }map.set('secret', 'value'); // Error: Cannot set secret key2. 观察器(Observe)基本用法观察器允许在状态变化后执行自定义逻辑。import { observable, observe } from 'mobx';const store = observable({ count: 0, items: []});// 观察 count 的变化const dispose = observe(store, 'count', (change) => { console.log('After change:', change); console.log('Old value:', change.oldValue); console.log('New value:', change.newValue);});store.count = 5;// After change: { type: 'update', object: store, name: 'count', oldValue: 0, newValue: 5 }dispose(); // 清理观察器观察数组变化const items = observable([1, 2, 3]);observe(items, (change) => { console.log('Array changed:', change); if (change.type === 'splice') { console.log('Added:', change.added); console.log('Removed:', change.removed); console.log('Index:', change.index); }});items.push(4);// Array changed: { type: 'splice', object: items, added: [4], removed: [], index: 3 }items.splice(1, 1);// Array changed: { type: 'splice', object: items, added: [], removed: [2], index: 1 }观察对象的所有变化const store = observable({ count: 0, name: 'John', items: []});// 观察所有属性的变化const dispose = observe(store, (change) => { console.log(`${change.name} changed from ${change.oldValue} to ${change.newValue}`);});store.count = 1; // count changed from 0 to 1store.name = 'Jane'; // name changed from John to Janedispose();3. 中间件(Middleware)创建自定义中间件import { observable, action, runInAction } from 'mobx';function loggingMiddleware(store, methodName, actionFn) { return function(...args) { console.log(`[Action] ${methodName} called with:`, args); const startTime = performance.now(); const result = actionFn.apply(this, args); const endTime = performance.now(); console.log(`[Action] ${methodName} completed in ${endTime - startTime}ms`); return result; };}class Store { @observable count = 0; constructor() { makeAutoObservable(this); } @action increment = () => { this.count++; }; @action decrement = () => { this.count--; };}// 应用中间件const store = new Store();const originalIncrement = store.increment.bind(store);store.increment = loggingMiddleware(store, 'increment', originalIncrement);使用 action 钩子import { action, configure } from 'mobx';configure({ // 启用 action 钩子 enforceActions: 'always'});// 全局 action 钩子const originalAction = action.bound;action.bound = function(target, propertyKey, descriptor) { console.log(`[Action] ${propertyKey} is being defined`); return originalAction(target, propertyKey, descriptor);};错误处理中间件function errorHandlingMiddleware(store, methodName, actionFn) { return async function(...args) { try { return await actionFn.apply(this, args); } catch (error) { console.error(`[Error] ${methodName} failed:`, error); // 可以将错误存储到 store 中 if (store.errorStore) { store.errorStore.addError(error); } throw error; } };}class Store { @observable data = null; @observable error = null; constructor() { makeAutoObservable(this); } @action fetchData = async () => { this.data = await fetch('/api/data').then(r => r.json()); };}// 应用错误处理中间件const store = new Store();store.fetchData = errorHandlingMiddleware(store, 'fetchData', store.fetchData);4. 使用拦截器和观察器实现撤销/重做class HistoryStore { @observable past = []; @observable future = []; constructor(targetStore) { this.targetStore = targetStore; makeAutoObservable(this); this.setupInterceptors(); } setupInterceptors() { // 拦截所有状态变化 Object.keys(this.targetStore).forEach(key => { if (this.targetStore[key] && typeof this.targetStore[key] === 'object') { intercept(this.targetStore, key, (change) => { // 保存当前状态到 past this.past.push({ key, oldValue: change.oldValue, timestamp: Date.now() }); // 清空 future this.future = []; return change; }); } }); } @action undo = () => { if (this.past.length === 0) return; const lastChange = this.past.pop(); this.future.push(lastChange); // 恢复旧值 this.targetStore[lastChange.key] = lastChange.oldValue; }; @action redo = () => { if (this.future.length === 0) return; const nextChange = this.future.pop(); this.past.push(nextChange); // 恢复新值 this.targetStore[nextChange.key] = nextChange.newValue; }; @computed get canUndo() { return this.past.length > 0; } @computed get canRedo() { return this.future.length > 0; }}5. 性能监控中间件function performanceMonitoringMiddleware(store, methodName, actionFn) { return function(...args) { const startTime = performance.now(); const result = actionFn.apply(this, args); const endTime = performance.now(); const duration = endTime - startTime; // 记录性能数据 if (!store.performanceMetrics) { store.performanceMetrics = {}; } if (!store.performanceMetrics[methodName]) { store.performanceMetrics[methodName] = { count: 0, totalTime: 0, maxTime: 0, minTime: Infinity }; } const metrics = store.performanceMetrics[methodName]; metrics.count++; metrics.totalTime += duration; metrics.maxTime = Math.max(metrics.maxTime, duration); metrics.minTime = Math.min(metrics.minTime, duration); // 警告慢操作 if (duration > 100) { console.warn(`[Performance] ${methodName} took ${duration.toFixed(2)}ms`); } return result; };}6. 权限控制中间件function permissionMiddleware(store, methodName, actionFn, permissions) { return function(...args) { const user = store.userStore?.user; if (!user) { throw new Error('User not authenticated'); } if (permissions && !user.permissions.includes(permissions)) { throw new Error(`User does not have permission: ${permissions}`); } return actionFn.apply(this, args); };}class Store { @observable data = []; constructor() { makeAutoObservable(this); } @action addItem = (item) => { this.data.push(item); }; @action deleteItem = (id) => { this.data = this.data.filter(item => item.id !== id); };}// 应用权限中间件const store = new Store();store.addItem = permissionMiddleware(store, 'addItem', store.addItem, 'write');store.deleteItem = permissionMiddleware(store, 'deleteItem', store.deleteItem, 'delete');7. 日志记录中间件function loggingMiddleware(store, methodName, actionFn) { return function(...args) { const logEntry = { timestamp: new Date().toISOString(), action: methodName, args: JSON.parse(JSON.stringify(args)), result: null, error: null }; try { const result = actionFn.apply(this, args); logEntry.result = JSON.parse(JSON.stringify(result)); return result; } catch (error) { logEntry.error = { message: error.message, stack: error.stack }; throw error; } finally { // 将日志发送到服务器或存储到本地 if (store.logStore) { store.logStore.addLog(logEntry); } } };}8. 防抖和节流中间件function debounceMiddleware(store, methodName, actionFn, delay = 300) { let timeoutId = null; return function(...args) { if (timeoutId) { clearTimeout(timeoutId); } timeoutId = setTimeout(() => { actionFn.apply(this, args); timeoutId = null; }, delay); };}function throttleMiddleware(store, methodName, actionFn, delay = 300) { let lastCallTime = 0; return function(...args) { const now = Date.now(); const timeSinceLastCall = now - lastCallTime; if (timeSinceLastCall >= delay) { actionFn.apply(this, args); lastCallTime = now; } };}class SearchStore { @observable query = ''; @observable results = []; constructor() { makeAutoObservable(this); } @action performSearch = async (query) => { this.results = await api.search(query); };}// 应用防抖中间件const searchStore = new SearchStore();searchStore.performSearch = debounceMiddleware( searchStore, 'performSearch', searchStore.performSearch, 300);总结MobX 的中间件和拦截器提供了强大的功能:拦截器:在状态变化前拦截和修改操作观察器:在状态变化后执行自定义逻辑中间件:包装 action 以添加额外功能常见应用:撤销/重做、性能监控、权限控制、日志记录、防抖节流正确使用这些功能,可以构建更强大、更灵活的 MobX 应用。