Skip to content

Commit bde0274

Browse files
authored
fix: Connect component supports to unsubscribe event by pass the callback (#635)
* feat: eventEmitter supports to unsubscribe event by pass the callback * test: unsubscribe the event by pass the callback * test: unsubscribe the event by pass the callback * fix: set the height 100% while in horizontal mode * fix: rename to uppercase * feat: add use case for react/connect function * test: new testService instance * refactor: rename callback to listener
1 parent b352afd commit bde0274

File tree

11 files changed

+118
-40
lines changed

11 files changed

+118
-40
lines changed

src/common/event/__tests__/eventEmitter.test.ts

+23
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,27 @@ describe('Test the EventEmitter class', () => {
3737
event.unsubscribe(['a', 'b']);
3838
expect(event.count('a')).toBe(0);
3939
});
40+
41+
test('Unsubscribe the event by pass the callback', () => {
42+
const evt = new EventEmitter();
43+
const eventName = 'event1';
44+
const mockFn1 = jest.fn();
45+
const mockFn2 = jest.fn();
46+
evt.subscribe(eventName, mockFn1);
47+
evt.subscribe(eventName, mockFn2);
48+
49+
expect(evt.count(eventName)).toBe(2);
50+
51+
evt.unsubscribe(eventName, mockFn1);
52+
expect(evt.count(eventName)).toBe(1);
53+
evt.emit(eventName);
54+
expect(mockFn1).toBeCalledTimes(0);
55+
expect(mockFn2).toBeCalledTimes(1);
56+
57+
evt.subscribe(eventName, mockFn1);
58+
expect(evt.count(eventName)).toBe(2);
59+
60+
evt.unsubscribe(eventName);
61+
expect(evt.count(eventName)).toBe(0);
62+
});
4063
});

src/common/event/eventBus.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ export abstract class GlobalEvent {
66
/**
77
* Subscribe the service event
88
* @param name Event name
9-
* @param callback Callback function
9+
* @param listener Listener function
1010
*/
11-
public subscribe(name: string | string[], callback: Function) {
12-
EventBus.subscribe(name, callback);
11+
public subscribe(name: string | string[], listener: Function) {
12+
EventBus.subscribe(name, listener);
1313
}
1414

1515
/**
@@ -30,11 +30,11 @@ export abstract class GlobalEvent {
3030
}
3131

3232
/**
33-
* Unsubscribe the specific event
33+
* Unsubscribe the specific event and the listener function
3434
* @param name The event name
35-
* @param callback The subscribed function
35+
* @param listener optional, it unsubscribes events via name if not pass the listener function
3636
*/
37-
public unsubscribe(name) {
38-
EventBus.unsubscribe(name);
37+
public unsubscribe(name, listener?: Function) {
38+
EventBus.unsubscribe(name, listener);
3939
}
4040
}

src/common/event/eventEmitter.ts

+19-16
Original file line numberDiff line numberDiff line change
@@ -15,40 +15,43 @@ export class EventEmitter {
1515
}
1616
}
1717

18-
public subscribe(name: string | string[], callback: Function) {
18+
public subscribe(name: string | string[], listener: Function) {
1919
if (Array.isArray(name)) {
2020
name.forEach((key: string) => {
21-
this.assignEvent(key, callback);
21+
this.assignEvent(key, listener);
2222
});
2323
} else {
24-
this.assignEvent(name, callback);
24+
this.assignEvent(name, listener);
2525
}
2626
}
2727

28-
/**
29-
* Unsubscribe the specific event by the name
30-
*
31-
* TODO: The `unsubscribe` method delete the all events via the name directly, the developer
32-
* use the `subscribe` method could register many callbacks, so if the developer only want to delete the specific callback by the name,
33-
* this method is no work.
34-
* @param name The removed event name
35-
*/
36-
public unsubscribe(name: string | string[]) {
28+
public unsubscribe(name: string | string[], listener?: Function) {
3729
if (Array.isArray(name)) {
3830
name.forEach((key: string) => {
39-
this._events.delete(key);
31+
this.deleteEvent(key, listener);
4032
});
33+
} else {
34+
this.deleteEvent(name, listener);
35+
}
36+
}
37+
38+
public deleteEvent(name: string, listener?: Function) {
39+
if (listener) {
40+
const event = this._events.get(name);
41+
if (event) {
42+
event.splice(event.indexOf(listener), 1);
43+
}
4144
} else {
4245
this._events.delete(name);
4346
}
4447
}
4548

46-
public assignEvent<T>(name: string, callback: Function) {
49+
public assignEvent<T>(name: string, listener: Function) {
4750
const event = this._events.get(name);
4851
if (event) {
49-
event.push(callback);
52+
event.push(listener);
5053
} else {
51-
this._events.set(name, [callback]);
54+
this._events.set(name, [listener]);
5255
}
5356
}
5457
}

src/components/menu/style.scss

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141

4242
&--horizontal {
4343
flex-direction: row;
44+
height: 100%;
4445
}
4546

4647
&__item {

src/react/__tests__/connector.test.tsx

+24-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import { Component, connect } from 'mo/react';
2+
import { Component, ComponentEvents, connect } from 'mo/react';
33
import { fireEvent, render } from '@testing-library/react';
44

55
class TestServiceA extends Component {
@@ -122,4 +122,27 @@ describe('Test Connector Component', () => {
122122

123123
expect(serviceA.removeOnUpdateState).toBeCalled();
124124
});
125+
126+
test('The Service connect multiple Components', () => {
127+
const testService = new TestServiceA();
128+
129+
const TestView = connect({ A: testService }, TestComponent);
130+
const TestView2 = connect({ A: testService }, TestComponent);
131+
132+
const { unmount } = render(<TestView />);
133+
const { unmount: unmount2 } = render(<TestView2 />);
134+
expect((testService as any)._event.count(ComponentEvents.Update)).toBe(
135+
2
136+
);
137+
138+
unmount();
139+
expect((testService as any)._event.count(ComponentEvents.Update)).toBe(
140+
1
141+
);
142+
143+
unmount2();
144+
expect((testService as any)._event.count(ComponentEvents.Update)).toBe(
145+
0
146+
);
147+
});
125148
});

src/react/component.ts

+9-8
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,14 @@ export interface IComponent<S = any> {
1919
render(nextState?: S): void;
2020
/**
2121
* Listen to the Component state update event
22-
* @param callback
22+
* @param listener
2323
*/
24-
onUpdateState(callback: (prevState: S, nextState: S) => void): void;
24+
onUpdateState(listener: (prevState: S, nextState: S) => void): void;
2525
/**
26-
* Remove the Component update event listening
26+
* Remove the Component update event listening, default is remove all,
27+
* also you can remove one by pass the listener
2728
*/
28-
removeOnUpdateState(): void;
29+
removeOnUpdateState(listener?: Function): void;
2930
/**
3031
* Force to update the Component
3132
*/
@@ -68,12 +69,12 @@ export abstract class Component<S = any>
6869
this._event.emit(ComponentEvents.Update, this.state, nextState);
6970
}
7071

71-
public onUpdateState(callback: (prevState: S, nextState: S) => void) {
72-
this._event.subscribe(ComponentEvents.Update, callback);
72+
public onUpdateState(listener: (prevState: S, nextState: S) => void) {
73+
this._event.subscribe(ComponentEvents.Update, listener);
7374
}
7475

75-
public removeOnUpdateState(): void {
76-
this._event.unsubscribe(ComponentEvents.Update);
76+
public removeOnUpdateState(listener?: Function): void {
77+
this._event.unsubscribe(ComponentEvents.Update, listener);
7778
}
7879

7980
public forceUpdate() {

src/react/connector.tsx

+1-7
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,10 @@ export function connect<T = any>(
3939
componentWillUnmount() {
4040
this._isMounted = false;
4141
this.handleService((service) => {
42-
service.removeOnUpdateState();
42+
service.removeOnUpdateState(this.onChange);
4343
});
4444
}
4545

46-
// TODO: 目前会全量触发更新,后期根据字段(watchField)来控制更新粒度
47-
// const prev = get(prevState, watchFiled);
48-
// const next = get(nextState, watchFiled);
49-
// if (!equals(prev, next)) {
50-
// this.update();
51-
// }
5246
onChange(prevState, nextState) {
5347
Logger.info(prevState, nextState, (container as any)._registry);
5448
this.update();

stories/components/17-Dialog.stories.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { storiesOf } from '@storybook/react';
66
import { withKnobs } from '@storybook/addon-knobs';
77

88
const confirm = Modal.confirm;
9-
const stories = storiesOf('dialog', module);
9+
const stories = storiesOf('Dialog', module);
1010
stories.addDecorator(withKnobs);
1111

1212
stories.add('Basic Usage', () => {
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import React from 'react';
2+
import { IExtensionService } from 'mo/services';
3+
import { IExtension } from 'mo/model';
4+
import molecule from 'mo';
5+
6+
import { Pane } from './pane';
7+
8+
export const ExtendPanel: IExtension = {
9+
id: 'ExtendsProblems',
10+
name: 'Extends Problems',
11+
activate(extensionCtx: IExtensionService) {
12+
molecule.panel.add({
13+
id: 'TestPanel',
14+
name: 'Test Panel',
15+
renderPane: () => <Pane />,
16+
});
17+
},
18+
dispose() {
19+
molecule.problems.remove(1);
20+
},
21+
};
+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import molecule from 'mo';
2+
import { IEditor } from 'mo/model';
3+
import { connect } from 'mo/react';
4+
import React from 'react';
5+
6+
export const Pane = connect(molecule.editor, function ({ current }: IEditor) {
7+
const value: string = current?.tab?.data?.value || '!!!';
8+
return <div style={{ padding: 20 }}>Editor Input: {value}</div>;
9+
});

stories/extensions/index.ts

+3
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@ import { ExtendsLocalesPlus } from './locales-plus';
55

66
import { ExtendsTestPane } from './test';
77

8+
import { ExtendPanel } from './extend-panel';
9+
810
export const customExtensions: IExtension[] = [
911
ExtendsDataSync,
1012
ExtendsTestPane,
1113
ExtendsProblems,
14+
ExtendPanel,
1215
ExtendsLocalesPlus,
1316
];

0 commit comments

Comments
 (0)