前言
最近一直都在思考,如何写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
emitter是如何实现的
这样的库有一大堆,我找到的是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,有机会试一试