Побудуйте об’єкт функції із властивостями в 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.