最近一直都在思考,如何写React
能最舒服,从最初的繁琐的Redux
到Context
,同级组件间的数据交流逐渐的简单了起来,但有时候也会写多余的代码,让我又在想,如何才能让组件间的简单的交流更纯粹
最近的项目在做后台管理,简单的页面经常会需要这样的情况,简单页面就是一个查询表单,一个结果的表格
const Page = () => { return ( <Container> <SearchForm /> <ResultTable /> </Container> ); }; 复制代码
功能也很简单,查询表单输入条件,表格显示结果,这是组件的单向通信,即表单条件=>查询表格,但是因为是同级组件,不得不状态提升
状态提升后,变成了这样
const Page = () => { const [params,setParams] = useState(); return ( <Container> <SearchForm setParams={setParams}/> <ResultTable params={params}/> </Container> ); }; 复制代码
这样是可行的,但是却有额外的东西出现,如果使用Redux
、Context
这样的数据流方式,可以不用传额外的参数,但是却有了更繁琐的步骤
这个名称是我胡编的,我想表达的意思就是,<SearchForm>
组件说<ResultTable>
组件你得重新请求数据了,<ResultTable>
就会重新请求数据
最先有这个想法,是因为使用了swr
这个库,表格组件一定会请求一个接口,如果这个请求是使用swr
发起的,在任意组件,甚至不在组件里,都可以使用类似于trigger('/api/tableList')
这样的方式来让表格组件刷新
那原生怎么做,其实很简单,发布订阅模式就行了,改写组件
const SearchForm = () => { // 触发research事件 emitter.emit('research', { time: 'now' }); // ... }; const ResultTable = () => { // 当research事件被触发时 emitter.on('research', ({ time }) => getList(time)); // ... }; 复制代码
这样的方式,就像我想的触发式的组件刷新一样,任何地方触发research
,结果表格都会显示最新的结果
其实这样的方式经常有使用,比如Redux
就是类似这样的观察者模式实现的,connect
的组件订阅了store
的更新,每次store
更新都会通知这些组件来刷新获取新的state
这样的库有一大堆,我找到的是preact
大佬写的mitt
,源码非常短,也非常简单,直接贴出代码分析一下
// event的类型 export type EventType = string | symbol; // Handler可以接收一个可选的event参数,但是不返回任何值 export type Handler = (event?: any) => void; // 通配符的Handler export type WildcardHandler= (type: EventType, event?: any) => void // 当前已经注册的Handler数组 export type EventHandlerList = Array<Handler>; export type WildCardEventHandlerList = Array<WildcardHandler>; // 一个event type的map,每一个event type都有相对应的handler list export type EventHandlerMap = Map<EventType, EventHandlerList | WildCardEventHandlerList>; export interface Emitter { on(type: EventType, handler: Handler): void; on(type: '*', handler: WildcardHandler): void; off(type: EventType, handler: Handler): void; off(type: '*', handler: WildcardHandler): void; emit<T = any>(type: EventType, event?: T): void; emit(type: '*', event?: any): void; } /** Mitt: Tiny (~200b) functional event emitter / pubsub. * @name mitt * @returns {Mitt} */ export default function mitt(all?: EventHandlerMap): Emitter { all = all || new Map(); return { /** * 为给定的event类型注册一个event handler. * @param {string|symbol} type 监听的event类型,通配符"*"监听所有 * @param {Function} handler 响应给定event事件的函数 * @memberOf mitt */ on(type: EventType, handler: Handler) { // 获取map中对应的type的handler数组 const handlers = all.get(type); // 如果有就添加新的进去 const added = handlers && handlers.push(handler); // 没有就添加新的handler数组 if (!added) { all.set(type, [handler]); } }, /** * 移除给定事件的一个event handler * @param {string|symbol} type 要移除的handler的event类型,或者"*" * @param {Function} handler 移除的handler函数 * @memberOf mitt */ off(type: EventType, handler: Handler) { const handlers = all.get(type); if (handlers) { // >>> 0 一定是非负,正数不变,-1变成4294967295 handlers.splice(handlers.indexOf(handler) >>> 0, 1); } }, /** * 触发给定event类型的所有handler函数 * 如果类型存在,则会在执行完所有类型匹配的handler后再执行”*”的handler * * Note: 手动触发“*”是不支持的 * * @param {string|symbol} type 需要调用的event type * @param {Any} [evt] 任何类型的值,推荐object类型,会传递给每一个handler处理函数 * @memberOf mitt */ emit(type: EventType, evt: any) { // slice浅拷贝一次,否则移除会出现问题 https://github.com/developit/mitt/issues/65 // map而不是foreach是因为少4个字母…… ((all.get(type) || []) as EventHandlerList).slice().map((handler) => { handler(evt); }); ((all.get('*') || []) as WildCardEventHandlerList).slice().map((handler) => { handler(type, evt); }); } }; } 复制代码
这个库因为太简单了,所以只有200字节,功能也比较少,但是刚好够用
就像redux-thunk
只有十来行代码但是有14.9k Star一样,凡是大佬,都很牛皮
感觉自己一直都遵循着规则,所以很老套,这次的小思考让我觉得其实有很多可变的东西,而且也很方便
但是我觉得pubsub来快速的通信,还是得适当的使用,可以作为一个快捷的后备选项,我又想起了很早以前写flutter
,因为不会用任何的状态管理库,所以就选择了event_bus
这种pubsub的库,后来页面太多,太乱了
太多的组件传递信息,还是得选一个好用的状态管理库,最近发现一个不错的库pullstate,有机会试一试