TypeScript'teki özelliklere sahip bir işlev nesnesi oluşturun


87

Üzerinde tutulan bazı özelliklere sahip bir işlev nesnesi oluşturmak istiyorum. Örneğin JavaScript'te şunu yapardım:

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

Şimdi TypeScript'te bunun türünü şu şekilde tanımlayabilirim:

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

Ancak, bir kadroya ihtiyaç duymadan gerçekten inşa edemem. Gibi:

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

Bunu bir oyuncu kadrosu olmadan nasıl inşa edersiniz?


2
Bu sorunuza doğrudan bir cevap değil, ama kısaca özelliklere sahip bir fonksiyon nesnesi inşa etmek istiyor ve döküm ile Tamam herkes için, Nesne-Yayılması operatör hileye neden olabilir: var f: { (): any; someValue: number; } = <{ (): any; someValue: number; }>{ ...(() => "Hello"), someValue: 3 };.
Jonathan

Yanıtlar:


31

Öyleyse, gereksinim basitçe bu işlevi "f" işlevine dönüştürme olmadan oluşturmak ve atamaksa, burada olası bir çözüm vardır:

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

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

Esasen, atama yapılmadan önce bu imzayla eşleşecek bir nesneyi "oluşturmak" için kendi kendine çalışan bir işlev hazır bilgisi kullanır. Tek tuhaflık, işlevin iç bildiriminin 'herhangi' türünde olması gerektiğidir, aksi takdirde derleyici, nesnede henüz bulunmayan bir özelliğe atadığınızı ağlar.

DÜZENLEME: Kodu biraz basitleştirdi.


5
Anlayabildiğim kadarıyla bu aslında türü kontrol etmeyecek, bu yüzden .someValue aslında herhangi bir şey olabilir.
shabunc

1
2017/2018 itibarıyla TypeScript 2.0 için daha iyi / güncellenmiş yanıt Meirion Hughes tarafından aşağıda yayınlanmıştır: stackoverflow.com/questions/12766528/… .
user3773048

108

Güncelleme: Bu yanıt, TypeScript'in önceki sürümlerinde en iyi çözümdü, ancak daha yeni sürümlerde daha iyi seçenekler mevcuttur (diğer yanıtlara bakın).

Kabul edilen cevap işe yarar ve bazı durumlarda gerekli olabilir, ancak nesneyi oluşturmak için hiçbir tip güvenlik sağlamamanın dezavantajı vardır. Tanımlanmamış bir özellik eklemeye çalışırsanız, bu teknik en azından bir tür hatası verir.

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

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

// type error
f.notDeclard = 3

1
var fHattın neden bir hataya neden olmadığını anlamıyorum , çünkü o sırada someValueözellik yok .

2
@torazaburo, çünkü yayınladığınızda kontrol etmez. Check yazmak için yapmanız gereken: var f:F = function(){}yukarıdaki örnekte başarısız olacaktır. Bu cevap, ödev aşamasında yazım kontrolünü kaybettiğiniz için daha karmaşık durumlar için harika değildir.
Meirion Hughes

1
doğru ama bunu bir işlev ifadesi yerine bir işlev bildirimi ile nasıl yaparsınız?
Alexander Mills

1
Bu, artık TS'nin son sürümlerinde çalışmayacaktır, çünkü arayüz denetimi çok daha katıdır ve gerekli 'someValue' özelliğinin olmaması nedeniyle işlev dökümü artık derlenmeyecektir. İşe yarayacak olan şey<F><any>function() { ... }
galenus

tslint kullandığınızda: önerilen, typecast çalışmıyor ve kullanmanız gereken şudur: (işlevi bir ifade haline getirmek için parantez içine alın; sonra F olarak kullanın):var f = (function () {}) as any as F;
NikhilWanpal

81

Bu, artık kolayca (typecript 2.x) Object.assign(target, source)

misal:

görüntü açıklamasını buraya girin

Buradaki sihir Object.assign<T, U>(t: T, u: U), kesişme noktasını döndürmek için yazılmış olmasıdır T & U.

Bunun bilinen bir arayüze çözülmesini sağlamak da basittir. Örneğin:

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

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

uyumsuz yazımdan kaynaklanan hatalar:

Hata: foo: foo: string'e atanamayan numara
Hata: [] dizesine atanamayan numara (dönüş türü)

uyarı: Eski tarayıcıları hedefliyorsanız , Object.assign'ı çoklu doldurmanız gerekebilir .


1
Harika, teşekkürler. Şubat 2017 itibariyle, bu en iyi cevaptır (Typescript 2.x kullanıcıları için).
Andrew Faulkner

47

TypeScript, bu durumu bildirim birleştirme yoluyla ele almak için tasarlanmıştır :

ayrıca bir işlev oluşturma ve ardından işlevin üzerine özellikler ekleyerek işlevi daha da genişletme konusunda JavaScript uygulamasına aşina olabilirsiniz. TypeScript, bunun gibi tanımları güvenli bir şekilde oluşturmak için bildirim birleştirmeyi kullanır.

Bildirim birleştirme, bir şeyin hem işlev hem de ad alanı (dahili modül) olduğunu söylememizi sağlar:

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

Bu, yazmayı korur ve hem f()ve hem de yazmamızı sağlar f.someValue. .d.tsMevcut JavaScript kodu için bir dosya yazarken , şunu kullanın declare:

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

TypeScript'te işlevlere özellik eklemek genellikle kafa karıştırıcı veya beklenmedik bir kalıptır, bu nedenle bundan kaçınmaya çalışın, ancak eski JS kodunu kullanırken veya dönüştürürken gerekli olabilir. Bu, dahili modülleri (ad alanları) harici ile karıştırmanın uygun olacağı tek durumlardan biridir.


Yanıtta ortam modüllerinden bahsetmek için oy verin. Bu, mevcut JS modüllerini dönüştürürken veya not alırken çok tipik bir durumdur. Tam olarak aradığım şey!
Nipheris

Güzel. Bunu ES6 modülleri ile kullanmak istiyorsanız, sadece function hello() { .. } namespace hello { export const value = 5; } export default hello;IMO yapabilirsiniz, bu Object.assign veya benzeri çalışma zamanı korsanlarından çok daha temizdir. Çalışma zamanı yok, tür iddiası yok, hiçbir şey yok.
skrebbel

Bu cevap harika, ancak Symbol.iterator gibi bir Sembolü bir işleve nasıl eklersiniz?
kzh

Yukarıdaki Bağlantı olacağını artık doğru çalışmıyor typescriptlang.org/docs/handbook/declaration-merging.html
iJungleBoy

Bildirim birleştirmenin oldukça riskli bir JavaScript ile sonuçlandığını bilmelisiniz. Basit bir mülk tahsisi için bana aşırı görünen ek bir kapanış ekliyor. Bunu yapmanın hiç JavaScript olmayan bir yolu. Ayrıca, gerekli olan ilk şeyin işlev olduğundan emin olmadığınız sürece, ortaya çıkan değerin mutlaka bir işlev olmadığı bir bağımlılıklar sıralaması sorunu ortaya çıkarır .
John Leidegren


2

Kısayol olarak, ['özellik'] erişimcisini kullanarak nesne değerini dinamik olarak atayabilirsiniz:

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

Bu, tip kontrolünü atlar. Ancak, mülke kasıtlı olarak aynı şekilde erişmeniz gerektiğinden oldukça güvenlidir:

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

Ancak, gerçekten özellik değerini kontrol etmek istiyorsanız, bu işe yaramayacaktır.


1

Güncellenmiş bir cevap: Kesişim türlerinin eklenmesinden bu yana, &iki çıkarsanan türü anında "birleştirmek" mümkündür.

İşte bazı nesnelerin özelliklerini okuyan fromve bunları bir nesnenin üzerine kopyalayan genel bir yardımcı onto. Aynı nesneyi döndürür, ontoancak her iki özellik kümesini de içeren yeni bir türle döndürür , bu nedenle çalışma zamanı davranışını doğru bir şekilde açıklar:

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

Bu düşük düzeyli yardımcı, hala bir tür iddiası gerçekleştirir, ancak tasarım gereği tür açısından güvenlidir. Bu yardımcı yerinde olduğunda, OP'nin problemini tam tip emniyetle çözmek için kullanabileceğimiz bir operatörümüz var:

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'da denemek için burayı tıklayın . Tipik olmak için kısıtladığımıza foodikkat edin Foo, bu nedenle sonucunun mergeeksiksiz olması gerekir Foo. Yani adlandırmak eğer barüzere bado zaman bir tür hatası alıyorum.

NB Bununla birlikte, burada hala bir tip delik var. TypeScript, bir tür parametresini "işlev değil" olarak sınırlandırmanın bir yolunu sağlamaz. Böylece kafanız karışabilir ve ikinci argüman olarak işlevinizi iletebilirsiniz mergeve bu işe yaramaz. Dolayısıyla, bu ilan edilebilene kadar, onu çalışma zamanında yakalamalıyız:

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

Eski soru, ancak TypeScript'in 3.1 ile başlayan sürümleri constiçin, değişkeniniz için bir işlev bildirimi veya anahtar kelime kullandığınız sürece, özellik atamasını düz JS'de yaptığınız gibi yapabilirsiniz :

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'"

Referans ve çevrimiçi örnek .


0

Bu, güçlü yazımdan uzaktır, ancak yapabilirsiniz

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

Bu soruyu bulduğum zamanki gibi baskıcı güçlü yazımın üstesinden gelmeye çalışıyorsanız. Maalesef bu, TypeScript'in mükemmel şekilde geçerli JavaScript'te başarısız olduğu bir durumdur, bu nedenle TypeScript'e geri çekilmesini söylemeniz gerekir.

"JavaScript'iniz tamamen geçerli TypeScript" yanlış olarak değerlendirilir. (Not: 0.95 kullanarak)

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.