【问题标题】:Typescript typings in chained calls链式调用中的打字稿类型
【发布时间】:2017-07-18 23:31:47
【问题描述】:

更新:

  • 更好地解释我所寻求的
  • 我尝试的部分示例
  • 将问题简化为注入问题

我的问题

我正在尝试编写一些模式,允许一个对象在没有类继承的情况下扩展另一个对象,以实现松散耦合和功能方法(我在示例中用“扩展”函数表示,也可以是称为“管道”)。这很容易。但所有这些都是在保留类型的同时。这很难。

我想知道如何让 Typescript 打字顺其自然。

这是一个简化的代码段,例如 (link for Typescript playground):

abstract class DecoratorAbstract {
    public parent: DecoratorAbstract;

    public extend<T extends DecoratorAbstract>(decorator: T): T {
        decorator.parent = this;
        return decorator;
    }

    abstract decorate(): {}
}

class InitialDecorator extends DecoratorAbstract {
    decorate() {
        return { var: 'value' } as { 'var': string };
    }
}

class Decorator1 extends DecoratorAbstract {
    decorate() {
        let result = this.parent.decorate();
        let newResult = Object.assign(result, { 'foo': 'foo' });
        return newResult;
    }
}

class Decorator2 extends DecoratorAbstract {
    decorate() {
        let result = this.parent.decorate();
        let newResult = Object.assign(result, { 'bar': 'bar' });
        return newResult;
    }
}

// Split to show the return types 
let initialDecorator = new InitialDecorator();
let r0 = initialDecorator.decorate();

let decorator1 = initialDecorator.extend(new Decorator1);
let r1 = decorator1.decorate();

let decorator2 = decorator1.extend(new Decorator2);
let r2 = decorator2.decorate();

console.log(r2);

// The syntax I seek
let someDecorator = new InitialDecorator()
    .extend(new Decorator1);
    .extend(new Decorator2);

let someResult = someDecorator.decorate();
// The type I seek:
// {var: string} & {foo: string} & {bar: string}

我得到的类型: {} &amp; {'foo': string;} 用于r1{} &amp; {'bar': string;}用于r2,将decorator.parent.decorate 的返回类型替换为{}(抽象函数的结果)而不是实际类型。

所以我想知道:

  1. 我尝试做的事情可能吗?
  2. 如果不是,为什么?
  3. 如果是,是否有一个解决方案在语法上不太可怕(并遵循该概念或类似的东西)

更新:我尝试过的事情

有工厂

public extend<T extends DecoratorAbstract>(decoratorName: { new (): T }) {
   let decorator = new decoratorName();
   decorator.parent = this;
   return decorator;
}

使用一些Piper对象

class Piper<T extends {parent: PARENT}, PARENT> {
    public obj: T;
    public parent: PARENT;

    constructor(obj: T, parent: PARENT) {
        this.obj = obj;
        this.obj.parent = this.parent as PARENT;
    }

    out(): T {
        return this.obj;
    }
}

使用集合

但是我的集合对象类型必须在一开始就知道并且所有项目都相同。可能我的做法不对。

链表方法

这是有效的:

class Link<OBJ, PARENTLINK> {
    constructor(public obj: OBJ, public parent:PARENTLINK = null) {}

    pipe<NEWOBJ>(newObj: NEWOBJ):Link<NEWOBJ, this> {
        return new Link(newObj, this);
    }
}

abstract class DecoratorAbstract {
    abstract decorate<T>(result: T): {}
}

class InitialDecorator extends DecoratorAbstract {
    decorate<T>(result:T) {
        return { var: 'value' } as { 'var': string };
    }
}

class Decorator1 extends DecoratorAbstract {
    decorate<T>(result:T) {
        let newResult = Object.assign(result, { 'foo': 'foo' });
        return newResult;
    }
}

class Decorator2 extends DecoratorAbstract {
    decorate<T>(result:T) {
        let newResult = Object.assign(result, { 'bar': 'bar' });
        return newResult;
    }
}

let l = new Link(new InitialDecorator);
let l1 = l.pipe(new Decorator1);
let l2 = l1.pipe(new Decorator2);

我可以创建一个对象链并保留它们的类型。 但这并没有太大帮助... 一旦遇到 this 词,类型就不再跟随(回到 Abstract 类)。

到目前为止...

我还没有成功让 Typescript 保留我的对象类型。主要是因为:

  • 必须在对象声明中明确定义泛型类型
  • 类类型是特定的(Typescript在分析返回类型时不遵循对象继承模型)

我认为我的问题可以通过设计一个可以保留每个元素的类型的链接列表或集合来解决。


更新:将问题简化为注入问题

我认为我的尝试接近解决方案...

但我总是求助于以下问题 (test it in Typescript playground):

abstract class BaseObj<P> { 
    parent: P
    abstract test(): {}
}

class A<P extends BaseObj<any>> extends BaseObj<P> {
    test() { 
        return this.parent.test();
    }   
}

class B<P extends BaseObj<any>> extends BaseObj<P> {
    test() {
        return {hop: 'la'};
    }
}

// let c = new A();  // Won't work 
let c = new A<B<any>>();
c.parent = new B;
c.parent.test(); // return type: {hop: string}

问题很简单:我希望对象A 调用注入对象B 的方法(在以下情况下,注入对象是嵌入的)。

为了以一般方式可行:

  • A必须知道B的类型,否则会使用它扩展的类型
  • 为此,我必须能够指定泛型类型,或者确保对象检测到它

它在这个例子中有效,因为一切都非常具体,但不是:

let c = new A<B<any>>();
c.parent = new B;

我想要的是一些允许注入类/对象的函数,这样:

pipe(MyObj, new InjectedObj());let a = pipe(new MyObj(), new InjectedObj());

问题是我没有设法从pipe 函数中返回一个A 的实例,它被键入为A&lt;B&lt;any&gt;&gt;,总是A&lt;any&gt;...

检测pipe 条目中的类型不是问题。但是让一个对象知道另一个(注入的)对象类型是我总是失败的地方......

无论我用什么方式写它,都要回到这个明显的问题上。


我的尝试:

【问题讨论】:

  • 目前无法为您提供完整的答案。这是可行的,但是您可以通过利用 javascript 具有更高阶函数的事实来实现某些(IMO)更清洁,这与可能需要这种模式的其他 OOP 语言不同。这是我写给一个无关问题的答案,但有一个您可能会感兴趣的代码示例:stackoverflow.com/questions/37263773/…
  • 有趣的是,管道确实是我尝试做的(并且我的“扩展”函数在原始代码中被命名为“管道”)。在这种情况下,可以有多个“装饰”功能。我不太喜欢设计,而是尝试重新设计一些可以保留类型的东西。 Typescript,通过在面向原型的语言上应用对象范式,在某种程度上迫使我以比具有函数式方法的纯 JS 提供的方式更重的方式做事。几天后,我将有更多时间深入研究您的样本。
  • @Paarth 你关于函数组合的例子很鼓舞人心。我试图在课堂上应用这个概念。但是没有成功...
  • 我没有得到“A必须知道B的类型”。 B 是从 A 调用的,即使类型是 A&lt;BaseObj&lt;any&gt;&gt;。有问题吗?
  • @RodrigoPedrosa 是的,用于打字。我正在寻找的是一种编写类型的方法。链接/管道扩展 BaseObj 的一些对象(以获取组合对象),并为其常用功能返回组合返回类型。并且由于类型推断,没有显式的泛型声明(它是更大设计的一部分,这部分是一个孤立的问题)。

标签: typescript dependency-injection linked-list method-chaining


【解决方案1】:

如果没有在任何地方定义返回类型A&lt;B&lt;any&gt;&gt;,TypeScript 就无法知道它。如果你想在运行时挂载管道,它只会在运行时知道它的类型。

如何将其定义为最终返回,例如A&lt;string&gt;?它仍然可以调用B,但最后,重要的是最终结果将是一个字符串。

type PipeConstructor<TOut, TIn> = { new(): Pipe<TOut, TIn> };

abstract class PipeEnd<TOut> {
    abstract test(): TOut;
}

abstract class Pipe<TOut, TIn> extends PipeEnd<TOut> {
    parent: PipeEnd<TIn>;

    static mount<TOut extends T, T>(...pipes: PipeConstructor<T, T>[]): PipeEnd<TOut> {
        let pipeEnd: Pipe<TOut, T>;
        let pipe: Pipe<T, T>;
        let pipePrevious: Pipe<T, T>;

        for (var pipeConstructor of pipes) {
            pipe = new pipeConstructor();
            if (!pipeEnd) pipeEnd = <Pipe<TOut, T>>pipe;
            if (pipePrevious) {
                pipePrevious.parent = pipe;
            }
            pipePrevious = pipe;
        }

        return pipeEnd;
    }
}

class A extends Pipe<string, string> {
    test() {
        return this.parent.test().toString();
    }
}

class B extends Pipe<string, number> {
    test() {
        return this.parent.test().toString();
    }
}

class C extends Pipe<number, void> {
    test() {
        return 123;
    }
}

let pipe = Pipe.mount<string, number | string | void>(A, B, C);

console.dir(pipe); // A.B.C
console.log(pipe.test()); // 123

【讨论】:

  • 感谢您的共同兴趣我已经把这个问题搁置了一段时间......这个想法是扩展对象,并通过类型推断来扩展它们的结果(通过累积生成的对象结构与工会)。指定类型是我尽量避免的(否则我不会在 Stack Overflow 上发布)。我还没有走这么远,要是那样的话:stackoverflow.com/questions/43688748/…
【解决方案2】:

使用极简的函数式方法可能更容易管理泛型:

const decorate1 = <T>(item: T) => Object.assign({}, item, { b: 'b' })
const decorate2 = <T>(item: T) => Object.assign({}, item, { c: 'c' })
const multiDecorate = <T>(item: T) => decorate2(decorate1(item));

const initial = { a: 'a' };
const multiDecorated = multiDecorate(initial);

console.log({ multiDecorated });

Try it in TypeScript Playground

类型流过使得multiDecorated为交集类型:

{} & { a: string; } & { b: string; } & { c: string; }

【讨论】:

  • 感谢您的回答,但这不是我要找的。我更新了我的示例以更准确。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-14
  • 2022-11-17
  • 2021-07-02
  • 2017-03-03
  • 2020-07-09
  • 1970-01-01
相关资源
最近更新 更多