sunmao-ui icon indicating copy to clipboard operation
sunmao-ui copied to clipboard

Proposal: Event Design

Open MrWindlike opened this issue 3 years ago • 2 comments

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.

MrWindlike avatar Feb 25 '22 04:02 MrWindlike

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?

Yuyz0112 avatar Feb 28 '22 01:02 Yuyz0112

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.

MrWindlike avatar Feb 28 '22 07:02 MrWindlike