Побудуйте об’єкт функції із властивостями в TypeScript


87

Я хочу створити об'єкт функції, який також має деякі властивості. Наприклад, у 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;

Як би ви побудували це без акторського складу?


2
Це не є прямою відповіддю на ваше запитання, але і для тих , хто хоче лаконічно побудувати функціональний об'єкт з властивостями, і все в порядку з литтям, то оператор Object-Spread , здається, зробити трюк: var f: { (): any; someValue: number; } = <{ (): any; someValue: number; }>{ ...(() => "Hello"), someValue: 3 };.
Джонатан,

Відповіді:


31

Отже, якщо вимога полягає у простому побудуванні та призначенні цієї функції на "f" без приведення, ось можливе рішення:

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

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

По суті, він використовує самостійно виконуваний літерал функції, щоб "побудувати" об'єкт, який відповідатиме цьому підпису до того, як буде виконано призначення. Єдина дивина полягає в тому, що внутрішнє оголошення функції повинно мати тип 'any', інакше компілятор плаче, що ви присвоюєте властивість, яка ще не існує в об'єкті.

EDIT: Трохи спростив код.


5
Наскільки я можу зрозуміти, це насправді не перевіряє тип, тому .someValue може бути по суті будь-яким.
shabunc

1
Кращу / оновлену відповідь станом на 2017/2018 для TypeScript 2.0 опублікував Meirion Hughes нижче: stackoverflow.com/questions/12766528/… .
user3773048

108

Оновлення: Ця відповідь була найкращим рішенням у попередніх версіях TypeScript, але в нових версіях доступні кращі варіанти (див. Інші відповіді).

Прийнята відповідь працює і може знадобитися в деяких ситуаціях, але має мінус, оскільки не забезпечує безпеку типу для будівництва об’єкта. Цей прийом принаймні видасть помилку типу, якщо ви спробуєте додати невизначене властивість.

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

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

// type error
f.notDeclard = 3

1
Я не розумію, чому var fрядок не викликає помилку, оскільки на той момент тут немає someValueвластивості.

2
@torazaburo його, тому що він не перевіряє, коли ви робите. Щоб ввести перевірку, потрібно зробити: var f:F = function(){}що не вдасться у наведеному вище прикладі. Ця відповідь не підходить для більш складних ситуацій, оскільки ви втрачаєте перевірку типу на етапі призначення.
Meirion Hughes

1
правильно, але як це зробити за допомогою оголошення функції замість виразу функції?
Олександр Міллс,

1
Це більше не працюватиме в останніх версіях TS, оскільки перевірка інтерфейсу набагато жорсткіша, і функція кастингу більше не буде компілюватися через відсутність необхідної властивості 'someValue'. Що, однак, <F><any>function() { ... }
спрацює

коли ви використовуєте tslint: рекомендується, typecast не працює, і що вам потрібно використовувати: (оберніть функцію в дужки, щоб вона стала виразом; потім використовуйте як будь-яку як F):var f = (function () {}) as any as F;
NikhilWanpal

81

Зараз це легко досягти (машинопис 2.x) за допомогою Object.assign(target, source)

приклад:

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

Магія тут полягає в тому, що Object.assign<T, U>(t: T, u: 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
Помилка: номер не може бути призначений для рядка [] (тип повернення)

Застереження: вам може знадобитися polyfill Object.assign якщо вони націлені на старі браузери.


1
Фантастично, дякую. Станом на лютий 2017 року це найкраща відповідь (для користувачів Typescript 2.x).
Ендрю Фолкнер

47

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. Це один з єдиних випадків, коли було б доречним змішати внутрішні модулі (простори імен) із зовнішніми.


Голос за згадку про навколишні модулі у відповіді. Це дуже типовий випадок при перетворенні або анотації існуючих модулів JS. Саме те, що я шукаю!
Ніферіс

Прекрасний. Якщо ви хочете використовувати це з модулями ES6, ви можете просто зробити function hello() { .. } namespace hello { export const value = 5; } export default hello;IMO, це набагато чистіше, ніж Object.assign або подібні хакі виконання. Ні виконання, ні тверджень типу, нічого.
skrebbel

Ця відповідь чудова, але як приєднати до функції такий символ, як Symbol.iterator?
kzh

Посилання вище не працює більше, правильно було б typescriptlang.org/docs/handbook/declaration-merging.html
iJungleBoy

Ви повинні знати, що злиття оголошень призводить до досить хиткого JavaScript. Це додає додаткове закриття, яке здається мені надмірним для простого присвоєння майна. Це дуже не-JavaScript спосіб це зробити. Він також вводить порядок проблем залежностей, коли отримане значення не обов'язково є функцією, якщо ви не впевнені, що функція є першим, що потрібно .
Джон Лейдегрен,


2

Як ярлик можна динамічно призначати значення об’єкта за допомогою доступу ['властивість']:

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

Це обходить перевірку типу. Однак це досить безпечно, тому що ви повинні навмисно отримати доступ до власності таким же чином:

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

Однак, якщо ви дійсно хочете, щоб тип перевіряв значення властивості, це не спрацює.


1

Оновлена ​​відповідь: з часу додавання типів перетину через & , можна "об'єднати" два виведені типи на льоту.

Ось загальний помічник, який читає властивості якогось об’єкта 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 . Зверніть увагу, що ми обмежувались fooтипом Foo, тому результат mergeповинен бути повним Foo. Отже, якщо ви перейменуєте barнаbad то ви отримаєте помилку типу.

Примітка. Однак тут є ще одна діра типу. 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;
}

1

Старе питання, але для версій TypeScript, починаючи з 3.1, ви можете просто призначити властивість, як це було б у звичайному JS, якщо ви використовуєте оголошення функції або constключове слово для вашої змінної:

function f () {}
f.someValue = 3; // fine
const g = function () {};
g.someValue = 3; // also fine
var h = function () {};
h.someValue = 3; // Error: "Property 'someValue' does not exist on type '() => void'"

Довідково -інформаційний приклад .


0

Це відходить від сильного набору тексту, але ви можете це зробити

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

якщо ви намагаєтеся обійти гнітюче сильне друкування, як це було, коли я знайшов це запитання. На жаль, це випадок виходу TypeScript із цілком дійсного JavaScript, тому вам слід сказати TypeScript відступити.

"Ваш JavaScript є цілком дійсним TypeScript" вважає помилковим. (Примітка: з використанням 0,95)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.