React Event Hooks

背景

在开发中, 往往会需要跨组件传递数据或事件, 而且有时候需要按顺序依次调用, redux不是很好实现, 而react里实现这种跨组件的数据传递的就是使用context, 可以使用context来实现一个事件处理组件, 同时, 为了这个事件处理组件能够在redux里使用, 可以将这个组件绑定到store

开发

ReduxEventContext

提供默认的context内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { createContext } from 'react';
import events from './../events';

const warningObject = {
get on() {
throw new Error('Please use <reduxEventProvider store={store}>');
},
get emit() {
throw new Error('Please use <reduxEventProvider store={store}>');
},
get events() {
return events;
},
};
const ReduxEventContext = createContext(warningObject);

export default ReduxEventContext;

ReduxEventProvider

提供封装好的Provider节点, 将事件处理方法绑定到组件上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React, { useMemo } from 'react';
import _ from 'lodash';
import PropTypes from 'prop-types';
import ReduxEventContext from './reduxEventContext';

const ReduxEventProvider = ({ children, store, events }) => {
const patchedStore = useMemo(() => _.pick(store, ['on', 'emit', 'events']), [store, events]);
return <ReduxEventContext.Provider value={patchedStore}>{children}</ReduxEventContext.Provider>;
};

ReduxEventProvider.propTypes = {
children: PropTypes.node.isRequired,
store: PropTypes.object.isRequired,
};

export default ReduxEventProvider;

storeEnhancer

封装createStore的增强方法, 给store注入事件处理代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import _ from 'lodash';

const applyEventBus =
(events) =>
(createStore) =>
(...args) => {
const store = createStore(...args);
const eventList = [];
const on = (type, fn) => {
const obj = {
type,
fn,
};
eventList.push(obj);
return () => _.remove(eventList, (x) => x === obj);
};
const emit = (type, ...args) => {
return eventList.filter((x) => x.type === type).map((x) => x.fn(...args));
};
const enhanceStore = {
...store,
on,
emit,
events,
};
enhanceStore.getState.getStore = () => enhanceStore;
return enhanceStore;
};

export { applyEventBus };

createStore

使用上面的storeEnhancer创建store

1
2
3
4
5
6
7
8
9
10
11
const store = createStore(rootReducer, compose(applyMiddleware(thunk), applyEventBus(events)));

const App = () => {
return (
<ReduxEventProvider store={store}>
<Provider store={store}>
<Root />
</Provider>
</ReduxEventProvider>
);
};

useEventBus

使用ReduxEventContext来传递事件处理方法

1
2
3
4
5
6
7
8
import { useContext } from 'react';
import { ReduxEventContext } from '../redux';

const useEventBus = () => {
return useContext(ReduxEventContext);
};

export default useEventBus;

使用

添加事件监听

在需要加上事件监听的组件里, 注册对应的事件监听函数即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import React, { useEffect, useRef } from 'react';
import _ from 'lodash';
import Snackbar from './snackbar';
import { useEventBus, useForceUpdate } from '../../use';

const SnackbarList = () => {
const { on, emit, events } = useEventBus();
const snackbarListRef = useRef([]);
const update = useForceUpdate();
useEffect(() => {
const add = (props, ref) => {
const key = _.uniqueId('snackbar-wrapper-');
const component = (
<Snackbar
key={key}
{...props}
ref={ref}
onExited={() => {
const onExited = _.get(props, 'snackbarProps.onExited');
onExited && onExited();
emit(events.EVENT_GLOBAL_REMOVE_SNACKBAR, key);
}}
/>
);
snackbarListRef.current.push({ key, component });
update();
return key;
};
const remove = (key) => _.remove(snackbarListRef.current, (x) => x.key === key) && update();
const off = [
on(events.EVENT_GLOBAL_ADD_SNACKBAR, add),
on(events.EVENT_GLOBAL_SUCCESS_SNACKBAR, (message, props, ref) => add({ message: message, variant: 'success', ...props }, ref)),
on(events.EVENT_GLOBAL_INFO_SNACKBAR, (message, props, ref) => add({ message: message, variant: 'info', ...props }, ref)),
on(events.EVENT_GLOBAL_WARN_SNACKBAR, (message, props, ref) => add({ message: message, variant: 'warn', ...props }, ref)),
on(events.EVENT_GLOBAL_ERROR_SNACKBAR, (message, props, ref) => add({ message: message, variant: 'error', ...props }, ref)),
on(events.EVENT_GLOBAL_REMOVE_SNACKBAR, remove),
];
return () => off.map((x) => x());
}, []);

return snackbarListRef.current.map(({ component }, index) => React.cloneElement(component, { num: index }));
};

export default SnackbarList;
export { Snackbar };

在组件中使用

1
2
3
4
5
6
7
const { emit, events } = useEventBus();

const clickEvent = async (form) => {
await setSavePromise(UTILS.post(api, form));
emit(events.EVENT_GLOBAL_SUCCESS_SNACKBAR, `success`);
refreshPage();
};

reducer中使用

1
2
3
4
5
6
7
8
const loadData = () => async (dispatch, getState) => {
const { emit, events } = getState.getStore();
try {
const result = await UTILS.post(api, data);
} catch (error) {
emit(events.EVENT_GLOBAL_SHOW_MESSAGE_BOX, `error: ${error.message}`, { title: error.name });
}
};
作者

Mosby

发布于

2019-04-09

许可协议

评论