diff --git a/src/common/event/__tests__/eventEmitter.test.ts b/src/common/event/__tests__/eventEmitter.test.ts index f00fab824..dab6d4f73 100644 --- a/src/common/event/__tests__/eventEmitter.test.ts +++ b/src/common/event/__tests__/eventEmitter.test.ts @@ -37,4 +37,27 @@ describe('Test the EventEmitter class', () => { event.unsubscribe(['a', 'b']); expect(event.count('a')).toBe(0); }); + + test('Unsubscribe the event by pass the callback', () => { + const evt = new EventEmitter(); + const eventName = 'event1'; + const mockFn1 = jest.fn(); + const mockFn2 = jest.fn(); + evt.subscribe(eventName, mockFn1); + evt.subscribe(eventName, mockFn2); + + expect(evt.count(eventName)).toBe(2); + + evt.unsubscribe(eventName, mockFn1); + expect(evt.count(eventName)).toBe(1); + evt.emit(eventName); + expect(mockFn1).toBeCalledTimes(0); + expect(mockFn2).toBeCalledTimes(1); + + evt.subscribe(eventName, mockFn1); + expect(evt.count(eventName)).toBe(2); + + evt.unsubscribe(eventName); + expect(evt.count(eventName)).toBe(0); + }); }); diff --git a/src/common/event/eventBus.ts b/src/common/event/eventBus.ts index fb6bfedd0..b9606c3e8 100644 --- a/src/common/event/eventBus.ts +++ b/src/common/event/eventBus.ts @@ -6,10 +6,10 @@ export abstract class GlobalEvent { /** * Subscribe the service event * @param name Event name - * @param callback Callback function + * @param listener Listener function */ - public subscribe(name: string | string[], callback: Function) { - EventBus.subscribe(name, callback); + public subscribe(name: string | string[], listener: Function) { + EventBus.subscribe(name, listener); } /** @@ -30,11 +30,11 @@ export abstract class GlobalEvent { } /** - * Unsubscribe the specific event + * Unsubscribe the specific event and the listener function * @param name The event name - * @param callback The subscribed function + * @param listener optional, it unsubscribes events via name if not pass the listener function */ - public unsubscribe(name) { - EventBus.unsubscribe(name); + public unsubscribe(name, listener?: Function) { + EventBus.unsubscribe(name, listener); } } diff --git a/src/common/event/eventEmitter.ts b/src/common/event/eventEmitter.ts index 65cc373f9..7a6f0ccf4 100644 --- a/src/common/event/eventEmitter.ts +++ b/src/common/event/eventEmitter.ts @@ -15,40 +15,43 @@ export class EventEmitter { } } - public subscribe(name: string | string[], callback: Function) { + public subscribe(name: string | string[], listener: Function) { if (Array.isArray(name)) { name.forEach((key: string) => { - this.assignEvent(key, callback); + this.assignEvent(key, listener); }); } else { - this.assignEvent(name, callback); + this.assignEvent(name, listener); } } - /** - * Unsubscribe the specific event by the name - * - * TODO: The `unsubscribe` method delete the all events via the name directly, the developer - * use the `subscribe` method could register many callbacks, so if the developer only want to delete the specific callback by the name, - * this method is no work. - * @param name The removed event name - */ - public unsubscribe(name: string | string[]) { + public unsubscribe(name: string | string[], listener?: Function) { if (Array.isArray(name)) { name.forEach((key: string) => { - this._events.delete(key); + this.deleteEvent(key, listener); }); + } else { + this.deleteEvent(name, listener); + } + } + + public deleteEvent(name: string, listener?: Function) { + if (listener) { + const event = this._events.get(name); + if (event) { + event.splice(event.indexOf(listener), 1); + } } else { this._events.delete(name); } } - public assignEvent(name: string, callback: Function) { + public assignEvent(name: string, listener: Function) { const event = this._events.get(name); if (event) { - event.push(callback); + event.push(listener); } else { - this._events.set(name, [callback]); + this._events.set(name, [listener]); } } } diff --git a/src/components/menu/style.scss b/src/components/menu/style.scss index fe692a3a9..cbb06e118 100644 --- a/src/components/menu/style.scss +++ b/src/components/menu/style.scss @@ -41,6 +41,7 @@ &--horizontal { flex-direction: row; + height: 100%; } &__item { diff --git a/src/react/__tests__/connector.test.tsx b/src/react/__tests__/connector.test.tsx index 772c47139..0d7b04f91 100644 --- a/src/react/__tests__/connector.test.tsx +++ b/src/react/__tests__/connector.test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Component, connect } from 'mo/react'; +import { Component, ComponentEvents, connect } from 'mo/react'; import { fireEvent, render } from '@testing-library/react'; class TestServiceA extends Component { @@ -122,4 +122,27 @@ describe('Test Connector Component', () => { expect(serviceA.removeOnUpdateState).toBeCalled(); }); + + test('The Service connect multiple Components', () => { + const testService = new TestServiceA(); + + const TestView = connect({ A: testService }, TestComponent); + const TestView2 = connect({ A: testService }, TestComponent); + + const { unmount } = render(); + const { unmount: unmount2 } = render(); + expect((testService as any)._event.count(ComponentEvents.Update)).toBe( + 2 + ); + + unmount(); + expect((testService as any)._event.count(ComponentEvents.Update)).toBe( + 1 + ); + + unmount2(); + expect((testService as any)._event.count(ComponentEvents.Update)).toBe( + 0 + ); + }); }); diff --git a/src/react/component.ts b/src/react/component.ts index e3eb3875b..7885bb603 100644 --- a/src/react/component.ts +++ b/src/react/component.ts @@ -19,13 +19,14 @@ export interface IComponent { render(nextState?: S): void; /** * Listen to the Component state update event - * @param callback + * @param listener */ - onUpdateState(callback: (prevState: S, nextState: S) => void): void; + onUpdateState(listener: (prevState: S, nextState: S) => void): void; /** - * Remove the Component update event listening + * Remove the Component update event listening, default is remove all, + * also you can remove one by pass the listener */ - removeOnUpdateState(): void; + removeOnUpdateState(listener?: Function): void; /** * Force to update the Component */ @@ -68,12 +69,12 @@ export abstract class Component this._event.emit(ComponentEvents.Update, this.state, nextState); } - public onUpdateState(callback: (prevState: S, nextState: S) => void) { - this._event.subscribe(ComponentEvents.Update, callback); + public onUpdateState(listener: (prevState: S, nextState: S) => void) { + this._event.subscribe(ComponentEvents.Update, listener); } - public removeOnUpdateState(): void { - this._event.unsubscribe(ComponentEvents.Update); + public removeOnUpdateState(listener?: Function): void { + this._event.unsubscribe(ComponentEvents.Update, listener); } public forceUpdate() { diff --git a/src/react/connector.tsx b/src/react/connector.tsx index fc3517052..f894b2ab2 100644 --- a/src/react/connector.tsx +++ b/src/react/connector.tsx @@ -39,16 +39,10 @@ export function connect( componentWillUnmount() { this._isMounted = false; this.handleService((service) => { - service.removeOnUpdateState(); + service.removeOnUpdateState(this.onChange); }); } - // TODO: 目前会全量触发更新,后期根据字段(watchField)来控制更新粒度 - // const prev = get(prevState, watchFiled); - // const next = get(nextState, watchFiled); - // if (!equals(prev, next)) { - // this.update(); - // } onChange(prevState, nextState) { Logger.info(prevState, nextState, (container as any)._registry); this.update(); diff --git a/stories/components/17-Dialog.stories.tsx b/stories/components/17-Dialog.stories.tsx index 00e1013db..d5fc464b9 100644 --- a/stories/components/17-Dialog.stories.tsx +++ b/stories/components/17-Dialog.stories.tsx @@ -6,7 +6,7 @@ import { storiesOf } from '@storybook/react'; import { withKnobs } from '@storybook/addon-knobs'; const confirm = Modal.confirm; -const stories = storiesOf('dialog', module); +const stories = storiesOf('Dialog', module); stories.addDecorator(withKnobs); stories.add('Basic Usage', () => { diff --git a/stories/extensions/extend-panel/index.tsx b/stories/extensions/extend-panel/index.tsx new file mode 100644 index 000000000..79f94e929 --- /dev/null +++ b/stories/extensions/extend-panel/index.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { IExtensionService } from 'mo/services'; +import { IExtension } from 'mo/model'; +import molecule from 'mo'; + +import { Pane } from './pane'; + +export const ExtendPanel: IExtension = { + id: 'ExtendsProblems', + name: 'Extends Problems', + activate(extensionCtx: IExtensionService) { + molecule.panel.add({ + id: 'TestPanel', + name: 'Test Panel', + renderPane: () => , + }); + }, + dispose() { + molecule.problems.remove(1); + }, +}; diff --git a/stories/extensions/extend-panel/pane.tsx b/stories/extensions/extend-panel/pane.tsx new file mode 100644 index 000000000..08457ae71 --- /dev/null +++ b/stories/extensions/extend-panel/pane.tsx @@ -0,0 +1,9 @@ +import molecule from 'mo'; +import { IEditor } from 'mo/model'; +import { connect } from 'mo/react'; +import React from 'react'; + +export const Pane = connect(molecule.editor, function ({ current }: IEditor) { + const value: string = current?.tab?.data?.value || '!!!'; + return
Editor Input: {value}
; +}); diff --git a/stories/extensions/index.ts b/stories/extensions/index.ts index 834374041..16a3fef42 100644 --- a/stories/extensions/index.ts +++ b/stories/extensions/index.ts @@ -5,9 +5,12 @@ import { ExtendsLocalesPlus } from './locales-plus'; import { ExtendsTestPane } from './test'; +import { ExtendPanel } from './extend-panel'; + export const customExtensions: IExtension[] = [ ExtendsDataSync, ExtendsTestPane, ExtendsProblems, + ExtendPanel, ExtendsLocalesPlus, ];