sunmao-ui
sunmao-ui copied to clipboard
Proposal: Event Design
Overview
Currently, the ability to communicate between components is supported by the runtime module, but the event handlers are generated by event traits when the state changes.
It's would bring some problems:
- The parameters for event handlers aren't real-time
- The codes of event implementation doesn't put together
Proposal
If want to fix the first problem, it should generate the fresh parameters when the handler is executed.
There are two ways to do this.
Compile Inside The Handler
The first one is to compile the state inside the handler.
const cb = () => {
const rawHandlers = trait.properties.handlers as Static<typeof EventHandlerSchema>[];
// Eval before sending event to assure the handler object is evaled from the latest state.
const evaledHandler = services.stateManager.deepEval(rawHandlers[i]);
services.apiService.send('uiMethod', {
componentId: evaledHandler.componentId,
name: evaledHandler.method.name,
parameters: evaledHandler.method.parameters,
});
};
But the problem is this would compile the state twice. One time is in the ImplWrapper
when the state changed, and another time is in the handler inside. It would waste the computing resource.
Pass emit
Instead Of Real Handlers
We may not have to generate the real handlers for components by event trait. We just need to find the real handlers and compile the state when some events are triggered.
Thus, we should pass a emit
function instead of real handlers to the components by the ImplWrapper
components.
function useEvent (props) {
const { component, services, globalHandlerMap } = props;
if (!globalHandlerMap.has(component.id)) {
globalHandlerMap.set(component.id, {});
}
const handlerMap = useRef(globalHandlerMap.get(component.id)!);
const send = (handler)=> {
services.apiService.send('uiMethod', {
componentId: handler.componentId,
name: handler.method.name,
parameters: handler.method.parameters,
});
}
const emit = (event: string, handlers?: Handler[]) {
if (handlers) {
handlers.forEach(send);
} else {
const handlers = findHandlers(component);
handlers.forEach(send);
}
}
useEffect(() => {
const handler = (s: { componentId: string; name: string; parameters?: any }) => {
...
handlerMap.current[s.name](s.parameters);
};
services.apiService.on('uiMethod', handler);
return () => {
services.apiService.off('uiMethod', handler);
globalHandlerMap.delete(c.id);
};
}, [services.apiService, component.id, globalHandlerMap, handlerMap]);
return {
emit,
}
}
function ImplWrapper(props) {
...
const { emit } = useEvent(props);
return (
...
<Impl emit={emit} {...}>
)
}
This not only solves the problems mentioned above and it makes the codes easy to read.
The parameters for event handlers aren't real-time
I don't think we have parameters for the event handler at this moment, do you mean reading an out-date state in the event handler?
The codes of event implementation doesn't put together
Could you show an example of how the previous way compares to the proposed way?
I don't think we have parameters for the event handler at this moment, do you mean reading an out-date state in the event handler?
Yes.
Could you show an example of how the previous way compares to the proposed way?
The previous way is listening event in the ImplWrapper
component and getting the callback functions that could send the event by the event trait. It split the related codes of events into different modules, which may not so good.
And the proposed way all codes of events are inside the useEvent
hook function. And then the ImplWrapper
component called useEvent
can simply apply the event logic.