Построить объект функции со свойствами в 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;

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

8 52

8 ответов:

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

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

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

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

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

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

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

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

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

// type error
f.notDeclard = 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. Это один из единственных случаев, когда было бы целесообразно смешивать внутренние модули (пространства имен) с внешний.

теперь это легко достижимо (typescript 2.х) с Object.assign(target, source)

пример:

enter image description here

магия вот в чем 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: string
Ошибка: номер не присваивается строке[] (возвращаемый тип)

предостережение: вам может понадобиться полифилл

в качестве ярлыка можно динамически назначить значение объекта с помощью метода доступа ['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

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

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

вот общий помощник, который читает свойства некоторого объекта 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;
}

Я не могу сказать, что это очень простой, но это определенно возможно:

interface Optional {
  <T>(value?: T): OptionalMonad<T>;
  empty(): OptionalMonad<any>;
}

const Optional = (<T>(value?: T) => OptionalCreator(value)) as Optional;
Optional.empty = () => OptionalCreator();

Если вам стало интересно это из моего gist с версией TypeScript / JavaScript Optional

это отходит от сильного набора текста, но вы можете сделать

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

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

"вы JavaScript является совершенно допустимым TypeScript" оценивается как false. (Примечание: с помощью 0.95)