乐闻世界logo
搜索文章和话题

MobX 中的中间件和拦截器如何使用?

2月21日 15:49

MobX 的中间件和拦截器提供了强大的功能,可以在状态变化前后执行自定义逻辑。以下是 MobX 中间件和拦截器的详细使用方法:

1. 拦截器(Intercept)

基本用法

拦截器允许在状态变化之前拦截和修改操作。

javascript
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); // 5 store.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(); // 清理拦截器

拦截数组操作

javascript
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 操作

javascript
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 key

2. 观察器(Observe)

基本用法

观察器允许在状态变化后执行自定义逻辑。

javascript
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(); // 清理观察器

观察数组变化

javascript
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 }

观察对象的所有变化

javascript
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 1 store.name = 'Jane'; // name changed from John to Jane dispose();

3. 中间件(Middleware)

创建自定义中间件

javascript
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 钩子

javascript
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); };

错误处理中间件

javascript
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. 使用拦截器和观察器实现撤销/重做

javascript
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. 性能监控中间件

javascript
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. 权限控制中间件

javascript
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. 日志记录中间件

javascript
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. 防抖和节流中间件

javascript
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 的中间件和拦截器提供了强大的功能:

  1. 拦截器:在状态变化前拦截和修改操作
  2. 观察器:在状态变化后执行自定义逻辑
  3. 中间件:包装 action 以添加额外功能
  4. 常见应用:撤销/重做、性能监控、权限控制、日志记录、防抖节流

正确使用这些功能,可以构建更强大、更灵活的 MobX 应用。

标签:Mobx