Skip to content

Commit a30a26c

Browse files
committed
fix: 改进修复Scope类型推断
1 parent d3647f5 commit a30a26c

File tree

6 files changed

+220
-31
lines changed

6 files changed

+220
-31
lines changed

docs/zh/guide/use/scopes.md

Lines changed: 94 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,8 @@ const scope = emitter.scope('user', {
139139

140140
作用域继承父级的事件类型定义:
141141

142-
```typescript
142+
```typescript twoslash
143+
import { FastEvent } from 'fastevent';
143144
interface AppEvents {
144145
'user/login': { userId: string };
145146
'user/logout': { userId: string };
@@ -152,11 +153,101 @@ const userScope = emitter.scope('user');
152153
// 类型检查正常
153154
userScope.emit('login', { userId: '123' }); //
154155

156+
userScope.on('login', (message) => {
157+
// ^|
158+
message.type; // 'login'
159+
message.payload // { userId: string }
160+
});
161+
155162
// 类型错误
156-
userScope.emit('login', { userId: 123 }); //
157-
userScope.emit('unknown', {}); //
163+
// userScope.emit('login', { userId: 123 }); // ❌
164+
// userScope.emit('unknown', {}); // ❌
158165
```
159166

167+
### 扩展类
168+
169+
可以继承自`FastEventScope`来扩展作用域,并且提供类型推断安全。
170+
171+
```typescript twoslash
172+
import { FastEvent,FastEventScope } from 'fastevent';
173+
interface AppEvents {
174+
'user/login': { userId: string };
175+
'user/logout': { userId: string };
176+
'user/profile/update': { name: string };
177+
}
178+
179+
const emitter = new FastEvent<AppEvents>();
180+
const userScope = emitter.scope('user');
181+
182+
class Room extends FastEventScope {
183+
join(name: string) {}
184+
leave() {}
185+
}
186+
187+
const room2 = emitter.scope('user')
188+
const room = emitter.scope('user',new Room())
189+
190+
type events = typeof room.types.events
191+
192+
193+
room.join('x')
194+
195+
// 类型检查正常
196+
//userScope.emit('login', { userId: '123' }); // ✅
197+
198+
room.on('login', (message) => {
199+
message.type; // 'login'
200+
message.payload // { userId: string }
201+
});
202+
room.once('logout', (message) => {
203+
message.type; // 'logout'
204+
message.payload // { userId: string }
205+
});
206+
room.on('profile/update', (message) => {
207+
message.type; // 'profile/update'
208+
message.payload // { name: string }
209+
});
210+
```
211+
212+
当事件名称中包括通配符时,同样可以类型推断:
213+
214+
```typescript twoslash
215+
import { FastEvent,FastEventScope } from 'fastevent';
216+
interface AppEvents {
217+
'user/*/login': { userId: string };
218+
'user/*/logout': { userId: string };
219+
'user/*/profile/update': { name: string };
220+
}
221+
222+
const emitter = new FastEvent<AppEvents>();
223+
const userScope = emitter.scope('user');
224+
225+
class Room extends FastEventScope {
226+
join(name: string) {}
227+
leave() {}
228+
}
229+
230+
const room = emitter.scope('user/tom',new Room())
231+
232+
type events = typeof room.types.events
233+
234+
room.join('x')
235+
236+
room.on('login', (message) => {
237+
message.type; // 'login'
238+
message.payload // { userId: string }
239+
});
240+
room.once('logout', (message) => {
241+
message.type; // 'logout'
242+
message.payload // { userId: string }
243+
});
244+
room.on('profile/update', (message) => {
245+
message.type; // 'profile/update'
246+
message.payload // { name: string }
247+
});
248+
```
249+
250+
160251
## 使用场景
161252

162253
### 模块化开发

packages/native/src/__tests__/types/scope.types.test.ts

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { describe, test, expect } from 'vitest';
44
import type { Equal, Expect, NotAny } from '@type-challenges/utils';
55
import { FastEvent } from '../../event';
6-
import { FastEventScope, FastEventScopeMeta } from '../../scope';
6+
import { FastEventScope, FastEventScopeExtend, FastEventScopeMeta } from '../../scope';
77
import { ChangeFieldType, FastEventMeta, FastEvents, RequiredItems, ScopeEvents } from '../../types';
88

99
declare module '../../types' {
@@ -194,14 +194,13 @@ describe('作用域上下文类型系统', () => {
194194
};
195195
const emitter = new FastEvent<Events>();
196196

197-
class CustomScope extends FastEventScope<ScopeEvents<Events, 'rooms/a'>> {
197+
class CustomScope extends FastEventScope {
198198
test() {}
199199
}
200200
type S = ScopeEvents<Events, 'rooms/a'>;
201201

202202
function getRoomScope<Prefix extends string>(prefix: Prefix) {
203-
// return emitter.scope(prefix, new CustomScope<`rooms/${Prefix}`>());
204-
return emitter.scope(prefix, new CustomScope());
203+
return emitter.scope(`rooms/${prefix}`, new CustomScope());
205204
}
206205

207206
const scope = getRoomScope('y');
@@ -261,4 +260,65 @@ describe('作用域上下文类型系统', () => {
261260
b1.test;
262261
b1.test2;
263262
});
263+
test('继承scope类32', () => {
264+
type Events = {
265+
'rooms/*/users/online': { name: string; status?: number };
266+
'rooms/*/users/*/online': { name: string; status?: number };
267+
'rooms/*/users/*/offline': boolean;
268+
'rooms/*/posts/**': number;
269+
'rooms/*/posts/*/online': number;
270+
};
271+
const emitter = new FastEvent<Events>();
272+
273+
class CustomScope extends FastEventScope {
274+
join(name: string) {}
275+
leave() {}
276+
}
277+
type S = ScopeEvents<Events, 'rooms/y'>;
278+
279+
function getRoom<Prefix extends string>(prefix: Prefix) {
280+
return emitter.scope(`rooms/${prefix}`, new CustomScope()); // as FastEventScopeExtend<Events, `rooms/${Prefix}`, CustomScope>;
281+
}
282+
283+
const room = getRoom('y');
284+
type RoomEvents = typeof room.types.events;
285+
room.join('fisher');
286+
room.leave();
287+
room.on('posts/a', (message) => {
288+
message.type;
289+
message.payload;
290+
});
291+
room.on('users/online', (message) => {
292+
type cases = [Expect<Equal<typeof message.type, 'users/online'>>, Expect<Equal<typeof message.payload, { name: string; status?: number }>>];
293+
});
294+
});
295+
296+
test('继承scope类4', () => {
297+
type Events = {
298+
'rooms/*/users/online': { name: string; status?: number };
299+
'rooms/*/users/*/online': { name: string; status?: number };
300+
'rooms/*/users/*/offline': boolean;
301+
'rooms/*/posts/**': number;
302+
'rooms/*/posts/*/online': number;
303+
};
304+
const emitter = new FastEvent<Events>();
305+
306+
class User extends FastEventScope {
307+
login(name: string) {}
308+
logout() {}
309+
}
310+
311+
const useScope = emitter.scope(`rooms/x`);
312+
const jack = useScope.scope('users/jack', new User());
313+
314+
jack.login('');
315+
jack.logout();
316+
317+
jack.on('online', (message) => {
318+
type cases = [Expect<Equal<typeof message.type, 'online'>>, Expect<Equal<typeof message.payload, { name: string; status?: number }>>];
319+
});
320+
jack.on('offline', (message) => {
321+
type cases = [Expect<Equal<typeof message.type, 'offline'>>, Expect<Equal<typeof message.payload, boolean>>];
322+
});
323+
});
264324
});

packages/native/src/channel.ts

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,43 @@
11
/**
2-
*
3-
*
4-
* const emitter = new FastEvent()
5-
*
6-
*
7-
* channel = emitter.channel("a.b.c.d", *(channel)=>{
8-
*
9-
* })
10-
*
11-
* emitter.on("a.b.c.d",*(channel) => {
12-
* while(true){
2+
*
3+
* Channel 用于创建一个异步的数据流,可以用于在多个协程之间传递数据
4+
*
5+
*
6+
*
7+
*
8+
* const emitter = new FastEvent()
9+
*
10+
*
11+
* channel = emitter.channel("a.b.c.d", *(channel)=>{
12+
*
13+
* })
14+
*
15+
* channel.push(1)
16+
* channel.push(2)
17+
*
18+
* channel.close()
19+
*
20+
* emitter.on("a.b.c.d",*(channel) => {
21+
* while(true){
1322
* const value = yield channel.pop(1)
14-
* }
15-
* })
16-
*
23+
* }
24+
* })
25+
*
26+
* emitter.emit("a.b.c.d",1)
27+
*
28+
* for(let data of await channel){
29+
* console.log(d)
30+
* }
31+
*
32+
* emitter.on('ssss',*(channel)=>{
33+
* while(true){
34+
* const value = yield channel.pop(1)
35+
* console.log(value)
36+
* }
37+
* })
38+
*
39+
*
40+
*
1741
*/
1842

19-
20-
export {}
43+
export {};

packages/native/src/event.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { FastEventScope, type FastEventScopeOptions } from './scope';
1+
import { FastEventScope, type FastEventScopeOptions, FastEventScopeExtend } from './scope';
22
import {
33
TypedFastEventListener,
44
FastEventOptions,
@@ -26,6 +26,7 @@ import {
2626
FastMessagePayload,
2727
OmitTransformedEvents,
2828
ClosestWildcardEvents,
29+
Class,
2930
} from './types';
3031
import { parseEmitArgs } from './utils/parseEmitArgs';
3132
import { isPathMatched } from './utils/isPathMatched';
@@ -1068,22 +1069,27 @@ export class FastEvent<
10681069
prefix: P,
10691070
options?: DeepPartial<FastEventScopeOptions<Meta & M, C>>,
10701071
): FastEventScope<ScopeEvents<AllEvents, P> & E, Meta & M, C>;
1072+
scope<E extends Record<string, any> = Record<string, any>, P extends string = string, C = Context, ScopeObject extends InstanceType<Class> = InstanceType<Class>>(
1073+
prefix: P,
1074+
scopeObj: ScopeObject,
1075+
options?: DeepPartial<FastEventScopeOptions<Meta>>,
1076+
): FastEventScopeExtend<AllEvents, P, ScopeObject>;
10711077
scope<
10721078
E extends Record<string, any> = Record<string, any>,
10731079
P extends string = string,
10741080
M extends Record<string, any> = Record<string, any>,
10751081
C = Context,
10761082
ScopeObject extends FastEventScope<any, any, any> = FastEventScope<any, any, any>,
10771083
>(prefix: P, scopeObj: ScopeObject, options?: DeepPartial<FastEventScopeOptions<Meta & M, C>>): ScopeObject & FastEventScope<ScopeEvents<AllEvents, P> & E, Meta & M, C>;
1078-
scope<E extends Record<string, any> = Record<string, any>, P extends string = string, M extends Record<string, any> = Record<string, any>, C = Context>() {
1084+
scope() {
10791085
const [prefix, scopeObj, options] = parseScopeArgs(arguments, this.options.meta, this.options.context);
10801086
let scope;
10811087
if (scopeObj) {
10821088
scope = scopeObj;
10831089
} else {
1084-
scope = new FastEventScope<ScopeEvents<AllEvents, P> & E, Meta & M, C>();
1090+
scope = new FastEventScope();
10851091
}
1086-
scope.bind(this as any, prefix, options as FastEventScopeOptions<Meta & M, C>);
1092+
scope.bind(this as any, prefix, options);
10871093
return scope;
10881094
}
10891095
}

packages/native/src/scope.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@ import {
1515
Fallback,
1616
Dict,
1717
TypedFastEventMessageOptional,
18-
FastEventListeners,
1918
RecordValues,
2019
WildcardEvents,
2120
FastEventListenerFlags,
2221
OmitTransformedEvents,
2322
PickTransformedEvents,
2423
FastEventMessage,
2524
ClosestWildcardEvents,
25+
Class,
2626
} from './types';
2727
import { parseEmitArgs } from './utils/parseEmitArgs';
2828
import { parseScopeArgs } from './utils/parseScopeArgs';
@@ -186,13 +186,13 @@ export class FastEventScope<
186186
// 处理使用 NotPayload 标识的事件类型
187187
public once<T extends keyof PickTransformedEvents<Events>>(
188188
type: T,
189-
listener: (message: PickPayload<RecordValues<WildcardEvents<Events, Exclude<T, number | symbol>>>>, args: FastEventListenerArgs<Meta>) => any | Promise<any>,
189+
listener: (message: PickPayload<RecordValues<ClosestWildcardEvents<Events, Exclude<T, number | symbol>>>>, args: FastEventListenerArgs<Meta>) => any | Promise<any>,
190190
options?: FastEventListenOptions<Events, Meta>,
191191
): FastEventSubscriber;
192192

193193
public once<T extends Exclude<string, Types>>(
194194
type: T,
195-
listener: TypedFastEventAnyListener<WildcardEvents<Events, T>, Meta, Fallback<Context, typeof this>>,
195+
listener: TypedFastEventAnyListener<ClosestWildcardEvents<Events, T>, Meta, Fallback<Context, typeof this>>,
196196
options?: FastEventListenOptions,
197197
): FastEventSubscriber;
198198
public once(): FastEventSubscriber {
@@ -342,6 +342,11 @@ export class FastEventScope<
342342
prefix: P,
343343
options?: DeepPartial<FastEventScopeOptions<Partial<FinalMeta> & M, C>>,
344344
): FastEventScope<ScopeEvents<Events, P> & E, FinalMeta & M, C>;
345+
scope<E extends Record<string, any> = Record<string, any>, P extends string = string, C = Context, ScopeObject extends InstanceType<Class> = InstanceType<Class>>(
346+
prefix: P,
347+
scopeObj: ScopeObject,
348+
options?: DeepPartial<FastEventScopeOptions<Meta>>,
349+
): FastEventScopeExtend<Events, P, ScopeObject>;
345350
scope<
346351
E extends Record<string, any> = Record<string, any>,
347352
P extends string = string,
@@ -371,3 +376,6 @@ export class FastEventScope<
371376
// eslint-disable-next-line
372377
onMessage(message: TypedFastEventMessage<Events, FinalMeta>, args: FastEventListenerArgs<FinalMeta>) {}
373378
}
379+
380+
export type FastEventScopeExtend<Events extends Record<string, any>, Prefix extends string, T extends InstanceType<Class> = never> = FastEventScope<ScopeEvents<Events, Prefix>> &
381+
T;

packages/native/src/types/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import type { FastListenerExecutor } from '../executors/types';
22
import { type FastListenerPipe } from '../pipes/types';
33
import { ExpandWildcard } from './ExpandWildcard';
4-
54
// 用来扩展全局Meta类型
65
export interface FastEventMeta {}
76
export interface FastEventMessageExtends {}
@@ -398,3 +397,5 @@ export * from './WildcardEvents';
398397
export * from './ScopeEvents';
399398
export * from './ExpandWildcard';
400399
export * from './Keys';
400+
401+
export type Class = (new (...args: any[]) => any) | (abstract new (...args: any[]) => any);

0 commit comments

Comments
 (0)