TypeScript: передать ReturnType вложенной функции в объект, который использует интерфейс - PullRequest
0 голосов
/ 15 января 2020

Я хочу создать структуру, в которой у меня есть такой интерфейс:

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. Я хотел бы получить данные с требованием. Как 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

1 Ответ

0 голосов
/ 15 января 2020

Я предлагаю сделать ваши типы PageData и Page родовыми c в типах Page объектов data и methods свойств, например:

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<T> , так как они не нужно беспокоиться о собственном this контексте.)


Тогда возникает проблема с созданием PageData объекта и устранением этих ошибок.

Я полагаю, вы не понимаете не хочу вручную аннотировать параметр this всех его свойств и вместо этого хотел бы, чтобы компилятор выводил соответствующий тип this. К сожалению, это, вероятно, будет невозможно сразу с одним объектом (см. соответствующий комментарий GitHub ); компилятор должен одновременно создавать и выводить тип, что не очень хорошо. Лучше быть способным делать вещи последовательно. Таким образом, если, как в вашем примере, свойство data на самом деле ни от чего не зависит, свойство methods зависит от data, а свойство main зависит от data и methods, тогда вы могли бы рассмотреть возможность создания строитель для PageData объектов, который создает их последовательно:

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

Выглядит хорошо для меня. Надеюсь, это поможет; удачи!

Детская площадка ссылка на код

...