Создайте объект функции со свойствами в 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;
Как бы вы построили это без литья?
Ответы
Ответ 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)