【问题标题】:TypeScript: pass the ReturnType of a nested function in an object that uses InterfaceTypeScript:在使用接口的对象中传递嵌套函数的 ReturnType
【发布时间】:2020-05-02 02:48:50
【问题描述】:

我想创建一个结构,其中我有这样的接口:

interface PageData {
    data: (this: Page) => object;
    methods: (this: Page) => { [methodName: string]: () => any };
    main: (this: Page) => void;
}

以及使用此数据创建新实例的类:

class Page {
    name: string;
    data: object;
    methods: object;
    constructor(name: string, data: PageData) {
        this.name = name;
        this.data = data.data.call(this);
        this.methods = data.methods.call(this);
        data.main.call(this);
    }
}

现在如何将数据和方法函数的 ReturnType 传递给 Page.data 和 Page.methods?我搜索了 ReturnType、Generics 等,此时我很困惑。也许它甚至不可能?我想拥有这个接口,这样我就可以在许多文件中使用它。我的文件夹结构类似于:

src/ src/main.ts src/Page.ts src/pages/demo.ts

在 demo.ts 中,我想用 PageData 接口导出一个对象:

export default {
    data: function() {
        return {
            customName: this.name + "demo"
        }
    },
    methods: function() {
        return {
            doSomething: () => this.data.customName.repeat(2)
        }
    },
    main: function() {
        console.log(this.data.customName);
        console.log(this.methods.doSomething());
    }
} as PageData

在 main.ts 中,我会使用 require 获取数据。喜欢new Page("demo", require("src/pages/demo.ts"))

我试图找到任何类似于我正在尝试做的示例,但我找不到任何东西。我发现的唯一东西是如何获取函数的 ReturnType,而不是使用接口的对象中的函数。

这个完整的例子有三个错误:
27、31行:Property 'customName' does not exist on type 'object'.
第32行:Property 'doSomething' does not exist on type 'object'.

interface PageData {
        data: (this: Page) => object;
        methods: (this: Page) => { [methodName: string]: () => any };
        main: (this: Page) => void;
}

class Page {
        name: string;
        data: object;
        methods: object;
        constructor(name: string, data: PageData) {
                this.name = name;
                this.data = data.data.call(this);
                this.methods = data.methods.call(this);
                data.main.call(this);
        }
}

export default {
        data: function() {
                return {
                        customName: this.name + "demo"
                }
        },
        methods: function() {
                return {
                        doSomething: () => this.data.customName.repeat(2)
                }
        },
        main: function() {
                console.log(this.data.customName);
                console.log(this.methods.doSomething());
        }
} as PageData

【问题讨论】:

  • 我不太了解您所说的“将数据和方法函数的 ReturnType 传递给 Page.data 和 Page.methods”是什么意思。我建议通过准确显示没有按预期工作的内容来将您的代码变成minimal reproducible example(例如,当您尝试访问由new Page()... 创建的Page 实例上的属性时出现错误消息?)运气!
  • 我添加了更多信息并修复了默认方法和主要功能中的错误。
  • 如果我修复了这些错误,您希望const page = new Page("demo", require("src/pages/demo.ts")) 能做什么?你希望它记住page.data.customName 的存在吗?或者你认为page.dataobject,就像你对Page 的定义一样?
  • 您的实现将按原样抛出运行时错误,因为Page 构造函数不会将实际的datamethod 属性复制到this。因此,当您运行它时,this.customNamethis.doSomething 将未定义,从而导致 main 调用内部出现错误。你能解决这个问题吗?
  • 是的,我希望它记住 page.data.customName 的存在。我已经将this.customName 更改为this.data.customName,也许您需要重新加载浏览器?

标签: typescript class types typescript-generics


【解决方案1】:

我的建议是让您的 PageDataPage 类型在 Page 对象的 datamethods 属性的类型中通用,如下所示:

interface PageData<D extends object, M extends Record<keyof M, () => any>> {
  data: (this: Page<D, M>) => D;
  methods: (this: Page<D, M>) => M;
  main: (this: Page<D, M>) => void;
}

class Page<D extends object, M extends Record<keyof M, () => any>> {
  name: string;
  data: D;
  methods: M;
  constructor(name: string, data: PageData<D, M>) {
    this.name = name;
    this.data = data.data.call(this);
    this.methods = data.methods.call(this);
    data.main.call(this);
  }
}

(注意:鉴于您的示例 PageData 明确使用箭头函数属性而不是其 methods 属性中的方法,我不会打扰 ThisType&lt;T&gt; utility type,因为它们没有自己的this 需要担心的上下文。)


然后是创建PageData 对象并解决这些错误的问题。

我假设您不想手动注释 this 参数的所有属性,而是希望编译器推断出适当的 this 类型。不幸的是,这可能无法通过单个对象一次性完成(请参阅relevant GitHub comment);编译器需要同时构建和推断类型,这并不擅长。能够按顺序做事会更好。因此,如果像您的示例一样,data 属性并不真正依赖于任何东西,则methods 属性依赖于data,而main 属性同时依赖于data 和@987654343 @,那么您可以考虑为PageData 对象创建一个builder,并按顺序创建它们:

const pageDataBuilder = {
  data: <D extends object>(data: (this: Page<any, any>) => D) => ({
    methods: <M extends Record<keyof M, () => any>>(methods: (this: Page<D, any>) => M) => ({
      main: (main: (this: Page<D, M>) => void) => ({
        data, methods, main
      })
    })
  })
};

你可以这样使用它:

const data = pageDataBuilder.data(
  function () {
    return {
      customName: this.name + "demo"
    }
  }).methods(function () {
    return {
      doSomething: () => this.data.customName.repeat(2)
    }
  }).main(function () {
    console.log(this.data.customName);
    console.log(this.methods.doSomething());
  });

编译没有错误,然后就可以了:

const page = new Page("demo", data);
page.data.customName; // string

我觉得不错。希望有帮助;祝你好运!

Playground link to code

【讨论】:

  • 是的!非常感谢您提供代码和链接!这对我了解它的工作原理以及我实际尝试做的事情有很大帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-06-13
  • 1970-01-01
  • 2022-01-19
  • 1970-01-01
  • 2019-06-15
  • 2019-11-23
  • 2017-07-02
相关资源
最近更新 更多