在 Typescript 中封装 WebSocket 消息

Encapsulate WebSocket Message in Typescript

我正在尝试将 websocket 消息封装到定义明确的类型中。
我有主要的 IIncommingMessage,它是所有传入消息的基本接口:

export interface IIncommingMessage {
  className : IClassName;
  methodName : IMethodName;
}

有多种类型class这个websocket可以调用如下:

export type IClassName = IClassA | IClassB | IClassC

以及相关的各种方法 classes

export type IMethodName = IfooinClassA | IbarinClassA | IbazinClassB | IquxinClassB | IquuxinClassB | IcorgeinClassC

以至于看起来像这样

ClassA:
  foo()
  bar()

ClassB:
  baz()
  qux()
  quux()

ClassC:
  corge

这个想法是,如果 websocket 消息到达。它将以

的形式出现
{
  className : "ClassB"
  methodName : "qux"
}

所以这应该调用函数 qux() 的 ClassB。

  1. 我采用的方法看起来很糟糕。想知道是否有更好的方法将网络套接字消息紧密耦合到定义良好的类型

  2. 也很好奇我将如何在 TypeScript 中进行此调用 - 会是 protoype.call('className.method')?

关于你的第一部分有明确定义的类型,这就是我将如何实现它。

class ClassA {
    foo() { }
    bar() { }
}

class ClassB {
    baz() { }
    qux() { }
    quux() { }
}

class ClassC {
    corge() { }
    notAMethod = 1;
}

// This will be a like a dictionary mapping name to class
// Will also be used in the second part.
const Classes = {
    ClassA,
    ClassB,
    ClassC,
};

// This will be 'ClassA'|'ClassB'|'ClassC'
type IClassName = keyof typeof Classes;
type IClassOf<T extends IClassName> = InstanceType<typeof Classes[T]>;
type MethodFilter<T extends IClassName> = { [MN in keyof IClassOf<T>]: IClassOf<T>[MN] extends () => void ? MN : never }
type MethodName<T extends IClassName> = MethodFilter<T>[keyof MethodFilter<T>];

interface IGenericIncomingMessage<T extends IClassName> {
    className: T;
    methodName: MethodName<T>;
}

type IIncomingMessage = IGenericIncomingMessage<'ClassA'> | IGenericIncomingMessage<'ClassB'> | IGenericIncomingMessage<'ClassC'>;

let msg0: IIncomingMessage = {
    className: 'ClassA',
    methodName: 'foo', // valid
}

let msg1: IIncomingMessage = {
    className: 'ClassC',
    methodName: 'corge', // valid
}

let msg2: IIncomingMessage = { // compiler error. Type ... is not assignable to type 'IIncomingMessage'.
    className: 'ClassA',
    methodName: 'corge', 
}

let msg3: IIncomingMessage = {
    className: 'ClassD', // compiler error. ClassD Name is not not in 'ClassA' | 'ClassB' | 'ClassC'
    methodName: 'corge',
}

let msg4: IIncomingMessage = {
    className: 'ClassC',
    methodName: 'notAMethod', // compiler error. Type '"notAMethod"' is not assignable to type '"foo" | "bar" | "baz" | "qux" | "quux" | "corge"'.
}

关于第二部分,我使用之前定义的 类 字典按名称查找 class,创建 class 的新实例。这意味着具有不在字典中的有效 class 名称的恶意邮件将无法工作。

// I omit error handling here.
function invokeFunction<T extends IClassName>(message: IGenericIncomingMessage<T>): void {
    // Look for the class instance in dictionary and create an instance
    const instance = new Classes[message.className]() as IClassOf<T>;
    // Find the method by name. The cast to any is to silence a compiler error;
    // You may need to perform additional login to validate that the method is allowed.
    const fn = instance[message.methodName] as unknown as () => void;
    fn.apply(instance);
}