Redux中间件: ChanMiddleware
在开发客户端渲染(CSR)与服务端渲染(SSR)共用的前端代码时,需要使用Redux初始化状态。但是发现Redux-saga的take等函数只能在createStore()之后才能使用,不是很方便。于是,造一个跨组件通信的Redux side-effect中间件: ChanMiddleware 。
ChanMiddleware支持以下3个函数:
interface ChanHandler {
(state?: any, action?: AnyAction, dispatch?: Dispatch, next?: () => void): void
}
const use = (type: string, handler: ChanHandler) => void;
const useOnce = (type: string, handler: ChanHandler) => void;
const unUse = (type: string, handler: ChanHandler) => void;
type等于'‘时,handler处理所有的Action。
1. ChanMiddleware源码
// chanMiddleware.ts
import {
AnyAction, Store, Dispatch,
} from 'redux';
export interface ChanHandler {
(state?: any, action?: AnyAction, dispatch?: Dispatch, next?: () => void): void
}
const chanNextArgsRequired = 4;
function createChanMiddleware(opts = {}) {
const channels: {
[type: string]: ChanHandler[];
} = {};
const use = (type: string, handler: ChanHandler) => {
if (typeof handler !== 'function') {
throw new Error('Channel handler must be a function');
}
const handlers = channels[type];
if (handlers) {
const index = handlers.indexOf(handler);
if (index === -1) {
handlers.push(handler);
}
return;
}
channels[type] = [handler];
};
const unUse = (type: string, handler: ChanHandler) => {
const handlers = channels[type];
if (handlers) {
const index = handlers.indexOf(handler);
if (index !== -1) {
handlers.splice(index, 1);
}
}
};
const useOnce = (type: string, handler: ChanHandler) => {
const wrapHandler: ChanHandler = (state, action, dispatch, next) => {
unUse(type, wrapHandler);
const argLen = handler.length;
if (argLen < chanNextArgsRequired) {
handler(state, action, dispatch);
next();
} else {
handler(state, action, dispatch, next);
}
};
use(type, wrapHandler);
};
const dispatchChan = (store: Store, action: AnyAction) => {
if (!action || !action.type) {
return;
}
const state = store.getState();
const { dispatch } = store;
const handlers = [...(channels[''] || []), ...(channels[action.type] || [])];
if (handlers.length === 0) {
return;
}
let i = 0;
const next = () => {
if (i >= handlers.length) {
return;
}
const handler = handlers[i];
i += 1;
const argLen = handler.length;
if (argLen < chanNextArgsRequired) {
handler(state, action, dispatch);
next();
} else {
handler(state, action, dispatch, next);
}
};
next();
};
const chanMiddleware = (store: Store) => (next) => (action: AnyAction) => {
const result = next(action);
dispatchChan(store, action);
return result;
};
chanMiddleware.use = use;
chanMiddleware.useOnce = useOnce;
chanMiddleware.unUse = unUse;
return chanMiddleware;
}
export default createChanMiddleware;
2. ChanMiddleware的使用
创建Chan中间件:
// store.ts
const chanMiddleware = createChanMiddleware();
const enhancer = composeEnhancers(
applyMiddleware(thunk, chanMiddleware),
);
使用use():
// booksChan.ts
import { ChanHandler } from '../redux/chanMiddleware';
import { setBooks } from '../redux/actionCreators';
import { RELOAD_BOOKS } from '../redux/actionTypes';
import { chanMiddleware } from '../redux/store';
import fetchBooks from '../service/fetchBooks';
const reloadBooksHandler: ChanHandler = async (state, action, dispatch) => {
const books = await fetchBooks();
dispatch(setBooks(books));
};
chanMiddleware.use(RELOAD_BOOKS, reloadBooksHandler);
3. 局限性
在编写客户端渲染与服务端渲染共用的代码时,需要了解chanMiddleware全局实例的局限性。
3.1. 服务端渲染
服务端渲染时,如果使用全局chanMiddleware实例,没有做到每个请求之间隔离,ChanHandler最好是纯函数,避免状态跨越多个请求。这种用法最简单,但是有这一点局限性。
可以在每个请求的处理函数中使用createChanMiddleware()动态创建中间件,然后调用chanMiddleware.use()动态注册ChanHandler,做到请求之间完全隔离。但是,这种用法比较复杂。
3.2. 客户端渲染
客户端渲染时,没有服务端渲染的问题,可以使用全局chanMiddleware实例。