【问题标题】:Encapsulate WebSocket Message in Typescript在 Typescript 中封装 WebSocket 消息
【发布时间】:2020-08-21 02:50:05
【问题描述】:

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

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

这个websocket可以调用各种类型的类,如下:

export type IClassName = IClassA | IClassB | IClassC

以及关联类中的各种方法

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. 我采用的方法看起来很糟糕。想知道是否有更好的方法将 web-socket 消息紧密耦合到明确定义的类型

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

【问题讨论】:

  • 这些方法是静态的吗?
  • 这些必须是类吗?
  • 不强制进入Static就不能工作吗?
  • 是的,它们是不同的类,将处理来自 websocket 连接的特定消息

标签: javascript typescript types websocket encapsulation


【解决方案1】:

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

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"'.
}

所以关于第二部分,我使用之前定义的 Classes 字典按名称查找类,创建该类的新实例。这意味着具有有效类名但不在字典中的恶意消息将不起作用。

// 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);
}

【讨论】:

  • 这太棒了!!!只需一个快速的问题即可对此进行重新评分。如果 className 是 Class.A 之类的,除了映射文件之外,还有其他方法可以将 ClassA 映射到消息 { className : "ClassA"; methodName : "foo"} 吗?
  • 在这种情况下,您必须手动指定 Classes 对象中的名称:const Classes = { 'ClassA': Class.A, 'ClassB': Class.B, 'ClassC': Class.C, };
  • Francis 的建议是在这种情况下可行。他还做了一些很好的编辑来改进我的答案。谢谢弗朗西斯。
  • 非常感谢 SherifElmetainy 和 @francis duvivier !!!非常感谢!
  • 抱歉这个无知的问题是否可以从let Classes = { 'ClassA': Class.A, 'ClassB': Class.B, 'ClassC': Class.C, }; 追加/删除我认为它会像我可以去 Classes.push 或 Classes.add 但似乎没有的集合一样工作工作,因为它是一个对象。不确定是否有办法动态添加到这个 Classes 变量中
猜你喜欢
  • 1970-01-01
  • 2012-11-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-23
  • 1970-01-01
相关资源
最近更新 更多