Подтвердить что ты не робот

Создайте объект функции со свойствами в TypeScript

Я хочу создать объект функции, который также имеет некоторые свойства, удерживаемые на нем. Например, в JavaScript я бы сделал:

var f = function() { }
f.someValue = 3;

Теперь в TypeScript я могу описать тип этого как:

var f: { (): any; someValue: number; };

Однако я не могу построить его, не требуя приведения. Например:

var f: { (): any; someValue: number; } =
    <{ (): any; someValue: number; }>(
        function() { }
    );
f.someValue = 3;

Как бы вы построили это без литья?

4b9b3361

Ответ 1

Итак, если требование состоит в том, чтобы просто построить и присвоить эту функцию "f" без приведения, вот возможное решение:

var f: { (): any; someValue: number; };

f = (() => {
    var _f : any = function () { };
    _f.someValue = 3;
    return _f;
})();

По существу, он использует личный экземпляр функции выполнения, чтобы "построить" объект, который будет соответствовать этой сигнатуре до того, как будет выполнено назначение. Единственная странность в том, что внутреннее объявление функции должно быть типа "any", иначе компилятор плачет, что вы назначаете свойство, которое еще не существует на объекте.

EDIT: немного упростите код.

Ответ 2

Обновление: этот ответ был лучшим решением в более ранних версиях TypeScript, но в более новых версиях есть лучшие варианты (см. другие ответы).

Принятый ответ работает и может потребоваться в некоторых ситуациях, но имеет недостаток в обеспечении безопасности типа для создания объекта. Если вы попытаетесь добавить свойство undefined, этот метод по крайней мере выдает ошибку типа.

interface F { (): any; someValue: number; }

var f = <F>function () { }
f.someValue = 3

// type error
f.notDeclard = 3

Ответ 3

TypeScript предназначен для обработки этого случая через слияние объявлений:

вы также можете быть знакомы с практикой JavaScript по созданию функции, а затем расширять функцию, добавив свойства в функцию. TypeScript использует слияние объявлений для создания таких определений безопасным способом.

Слияние декларации позволяет нам сказать, что что-то является функцией и пространством имен (внутренний модуль):

function f() { }
namespace f {
    export var someValue = 3;
}

Это сохраняет ввод и позволяет писать как f(), так и f.someValue. При написании файла .d.ts для существующего кода JavaScript используйте declare:

declare function f(): void;
declare namespace f {
    export var someValue: number;
}

Добавление свойств к функциям часто является запутанным или неожиданным шаблоном в TypeScript, поэтому старайтесь его избегать, но это может быть необходимо при использовании или преобразовании старого JS-кода. Это один из единственных времен, когда было бы целесообразно смешивать внутренние модули (пространства имен) с внешними.

Ответ 4

Это легко достижимо (typescript 2.x) с Object.assign(target, source)

пример:

введите описание изображения здесь

Магия здесь заключается в том, что Object.assign<T, U>(...) набирается для возврата типа объединения T & U.

Принуждение к тому, что это разрешает известный интерфейс, также прямолинейно:

interface Foo {
  (a: number, b: string): string[];
  foo: string;
}

let method: Foo = Object.assign(
  (a: number, b: string) => { return a * a; },
  { foo: 10 }
); 

Ошибка: foo: номер не присваивается foo: строка
   Ошибка: номер не присваивается строке [] (тип возврата)

caveat: вам может потребоваться polyfill Object.assign, если вы используете старые браузеры.

Ответ 5

В качестве ярлыка вы можете динамически назначать значение объекта с помощью аксессуара ['property']:

var f = function() { }
f['someValue'] = 3;

Это обходит проверку типов. Однако это довольно безопасно, потому что вы должны намеренно получить доступ к свойству таким же образом:

var val = f.someValue; // This won't work
var val = f['someValue']; // Yeah, I meant to do that

Однако, если вам действительно нужна проверка типа для значения свойства, это не сработает.

Ответ 6

Обновленный ответ: с добавлением типов пересечений через &, можно "объединить" два предполагаемых типа "на лету".

Вот общий помощник, который читает свойства некоторого объекта from и копирует их по объекту onto. Он возвращает тот же объект onto, но с новым типом, который включает оба набора свойств, поэтому корректно описывает поведение во время выполнения:

function merge<T1, T2>(onto: T1, from: T2): T1 & T2 {
    Object.keys(from).forEach(key => onto[key] = from[key]);
    return onto as T1 & T2;
}

Этот низкоуровневый помощник все еще выполняет утверждение типа, но по типу он безопасен по типу. С помощью этого помощника у нас есть оператор, который мы можем использовать для решения проблемы OP с полной безопасностью типа:

interface Foo {
    (message: string): void;
    bar(count: number): void;
}

const foo: Foo = merge(
    (message: string) => console.log(`message is ${message}`), {
        bar(count: number) {
            console.log(`bar was passed ${count}`)
        }
    }
);

Нажмите здесь, чтобы опробовать его на TypeScript Playground. Обратите внимание, что мы ограничили foo типом foo, поэтому результат merge должен быть полным foo. Поэтому, если вы переименуете bar в bad, вы получите ошибку типа.

NB Однако здесь все еще есть одно отверстие. TypeScript не предоставляет способ ограничить параметр типа "не функцией". Таким образом, вы можете запутаться и передать свою функцию в качестве второго аргумента merge, и это не сработает. Поэтому, пока это не будет объявлено, мы должны поймать его во время выполнения:

function merge<T1, T2>(onto: T1, from: T2): T1 & T2 {
    if (typeof from !== "object" || from instanceof Array) {
        throw new Error("merge: 'from' must be an ordinary object");
    }
    Object.keys(from).forEach(key => onto[key] = from[key]);
    return onto as T1 & T2;
}

Ответ 7

Это отходит от сильного ввода, но вы можете сделать

var f: any = function() { }
f.someValue = 3;

если вы пытаетесь обойти репрессивную сильную типизацию, как я был, когда я нашел этот вопрос. К сожалению, это случай TypeScript не работает на совершенно правильном JavaScript, поэтому вам нужно сказать TypeScript, чтобы отступить.

"Вы полностью работоспособны JavaScript TypeScript" - false. (Примечание: используя 0,95)