typcript arayüzü iki özellikten birinin var olmasını gerektirir


108

Olabilecek bir arayüz oluşturmaya çalışıyorum

export interface MenuItem {
  title: string;
  component?: any;
  click?: any;
  icon: string;
}
  1. Gerektirmenin componentveya clickayarlanmanın bir yolu var mı
  2. Her iki özelliğin ayarlanamamasını gerektirmenin bir yolu var mı?

Yanıtlar:


94

Tek bir arabirimle değil, çünkü türlerin koşullu mantığı yoktur ve birbirlerine bağlı olamazlar, ancak arabirimleri bölerek yapabilirsiniz:

export interface BaseMenuItem {
  title: string;
  icon: string;
}

export interface ComponentMenuItem extends BaseMenuItem {
  component: any;
}

export interface ClickMenuItem extends BaseMenuItem {
    click: any;
}

export type MenuItem = ComponentMenuItem | ClickMenuItem;

İlginç. Daha önce hiç "tür" dışa aktarmadım. Yani bu sadece MenuItem'in biri veya diğeri olabileceği anlamına mı geliyor?
Nix

Bunu aşağıda yaptığımdan daha çok beğendim. "Type" stackoverflow.com/a/36783051/256793'ü
Nix

Evet: TS sahiptir ANDve ORonun türleri için operatörler. Böyle bir şey yapıyorsanız, ameliyathane, bir grup bireysel türü bildirdikten sonra size tek bir toplanmış ad vermek güzel olabilir.
ssube

7
Merhaba arkadaşlar, nasıl arayüzlerden birini seçmeye zorlarsınız, ama ikisini birden değil? Bu türle , her ikisine de sahip olan componentve clickhata yapmayacak bir nesne ...
Daniel Ramos

4
@DanielRamos ekleyebilir click?: neverüzerinde ComponentMenuItemve component?: neverüstünde ClickMenuItem.
ethan.roday

161

ExcludeTypeScript 2.8'de eklenen tür yardımıyla, bir dizi özellikten en az birini gerektirmenin genelleştirilebilir bir yolu sağlanır:

type RequireAtLeastOne<T, Keys extends keyof T = keyof T> =
    Pick<T, Exclude<keyof T, Keys>> 
    & {
        [K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>>
    }[Keys]

Ve bir ve yalnızca birinin sağlanmasını gerektirmenin kısmi, ancak kesin olmayan bir yolu şudur:

type RequireOnlyOne<T, Keys extends keyof T = keyof T> =
    Pick<T, Exclude<keyof T, Keys>>
    & {
        [K in Keys]-?:
            Required<Pick<T, K>>
            & Partial<Record<Exclude<Keys, K>, undefined>>
    }[Keys]

İşte her ikisini de eylem halinde gösteren bir TypeScript oyun alanı bağlantısı.

Bununla ilgili uyarı RequireOnlyOne, TypeScript'in çalışma zamanında var olacak her özelliği derleme zamanında her zaman bilmemesidir. Yani belli ki RequireOnlyOnebilmediği ekstra özellikleri önlemek için hiçbir şey yapamaz. RequireOnlyOneOyun alanı bağlantısının sonunda bir şeyleri nasıl gözden kaçırabileceğime dair bir örnek verdim .

Aşağıdaki örneği kullanarak nasıl çalıştığına hızlı bir genel bakış:

interface MenuItem {
  title: string;
  component?: number;
  click?: number;
  icon: string;
}

type ClickOrComponent = RequireAtLeastOne<MenuItem, 'click' | 'component'>
  1. Pick<T, Exclude<keyof T, Keys>>dan RequireAtLeastOneolur { title: string, icon: string}bulunmayan tuşların değişmeden özellikleri olan,'click' | 'component'

  2. { [K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>> }[Keys]dan RequireAtLeastOneolur

    { 
        component: Required<{ component?: number }> & { click?: number }, 
        click: Required<{ click?: number }> & { component?: number } 
    }[Keys]
    

    Hangisi olur

    {
        component: { component: number, click?: number },
        click: { click: number, component?: number }
    }['component' | 'click']
    

    Hangi nihayet olur

    {component: number, click?: number} | {click: number, component?: number}
    
  3. Yukarıdaki 1. ve 2. adımların kesişimi

    { title: string, icon: string} 
    & 
    ({component: number, click?: number} | {click: number, component?: number})
    

    basitleştirir

    { title: string, icon: string, component: number, click?: number} 
    | { title: string, icon: string, click: number, component?: number}
    

6
Açıklayıcı örnekler için çok teşekkür ederim. Gerçekten bilgilendirici.
Eduard

1
Son derece bilgilendirici ve iyi biçimlendirilmiş bir yanıt için teşekkürler. Daha iyi anlayabilmek için bu örnekle oynuyorum. Başka bir tür verirseniz componentve clickbir tür atarsanız, anyen az bir dizi geçerli özelliğe sahip bir nesne geçecektir. Bunun, türün { title: string, icon: string, component: any} | { title: string, icon: string, click: any}hangi duruma indirgenmesi nedeniyle tipin 4 yerine 3 özellik olduğunu ve biri isteğe bağlı olduğunu varsayıyorum . Harita gösteriminde dizinin kullanımına ilişkin belgeleri bulmaya çalışıyorum ama bulamıyorum.
Sam R

5
İfadedeki kısa çizginin (eksi işareti) ne işe yaradığını kimse açıklayabilir [K in keys]-mi?
2019

2
@Lopsided KOrijinal nesnede bulunabilecek isteğe bağlı değiştiricileri kaldırır T. Bkz stackoverflow.com/questions/49655419/... içinde amacına gelince [K in Keys]-?:özellikle: Sadece biraz test yaptım ve aslında nihai sonucu bir fark yapmaz gibi görünüyor, ama emin olmak için sadece koymak olduğunu RequireAtLeastOnedavranacağını aynı şekilde, belirtilen özelliklerin Keysbaşlangıçta isteğe bağlı olup olmadığına bakılmaksızın .
KPD

2
Not: Bu resmi Microsoft Azure typescript SDK bir parçası olan docs.microsoft.com/en-us/javascript/api/@azure/...
Simon_Weaver

24

Karmaşık koşullu türleri kullanmaya gerek yoktur . Daha basit hale getirebiliriz:

  1. Ayarlanacak bileşen veya tıklama gerektirmenin bir yolu var mı? (dahil OR)
type MenuItemOr = {
    title: string;
    icon: string;
} & ({ component: object } | { click: boolean }) 
// brackets are important here: "&" has precedence over "|"

let testOr: MenuItemOr;
testOr = { title: "t", icon: "i" } // error, none are set
testOr = { title: "t", icon: "i", component: {} } // ✔
testOr = { title: "t", icon: "i", click: true } // ✔
testOr = { title: "t", icon: "i", click: true, component: {} } // ✔

Bir birleşim türü ( |) kapsayıcıya karşılık gelir OR. Bu edilir kesişen -çıkışlı şartsız özelliklere sahiptir.

  1. Her iki özelliğin ayarlanamamasını gerektirmenin bir yolu var mı? (Özel OR/ XOR)
type MenuItemXor = {
    title: string;
    icon: string;
} & (
        | { component: object; click?: never }
        | { component?: never; click: boolean }
    )

let testXor: MenuItemXor;
testXor = { title: "t", icon: "i" } // error, none are set
testXor = { title: "t", icon: "i", component: {} } // ✔
testXor = { title: "t", icon: "i", click: true } // ✔
testXor = { title: "t", icon: "i", click: true, component: {} } // error, both are set

Temelde ya component veya click ayarlanabilir, diğer gerektiğini asla aynı zamanda eklenebilir. TS bir yapabilirsiniz ayrımcılığa uğrayan birlik tipini dışına MenuItemXorhangi gelmektedir, XOR.

Not : için bu XORkoşul kabul edilen cevaplaMenuItemXor mümkün değildir .


Oynamak için kod örneği


2
gerçekten yumuşak cevap
Ivan Caceres

1
testOr.click;Bununla nasıl başa çıkılır, hatayı döndürür Property 'click' does not exist on type 'MenuItemOr'.?
Eduardo Dallmann

2
@EduardoDallmann , nesnede ( Oyun Alanı ) herhangi bir özellik olup olmadığını kontrol etmek için inoperatörü kullanabilirsiniz
ford04

Güzel cevap! Ama nasıl çalıştığını açıklayabilir misin? |Sadece doğru bir operand ile birleşimi ( ) anlamakta zorlanıyorum . Teşekkürler
NorTicUs

@NorTicUs Eğer lider demek durumunda |içini & ( ... )ait MenuItemXor: Bu hatlar üzerinde ayıran biçimlendirme daha iyi türü için birlik operatörü / sadece bir kısa yoldur. Gibi burada - sihirli :) katılan
ford04

13

Birden fazla arayüz içermeyen bir alternatif,

export type MenuItem = {
  title: string;
  component: any;
  icon: string;
} | {
  title: string;
  click: any;
  icon: string;
};

const item: MenuItem[] = [
  { title: "", icon: "", component: {} },
  { title: "", icon: "", click: "" },
  // Shouldn't this error out because it's passing a property that is not defined
  { title: "", icon: "", click: "", component: {} },
  // Does error out :)
  { title: "", icon: "" }
];

Tek bir özelliğin ayarlanmasını gerektiren Kısmi benzeri nasıl oluşturulur? Bölümünde benzer bir soru sordum

Yukarıdakiler basitleştirilebilir, ancak okunması daha kolay olabilir veya olmayabilir

export type MenuItem = {
  title: string;
  icon: string;
} & (
 {component: any} | {click: string}
)

Bunların hiçbirinin her ikisini de eklemenizi engellemediğini unutmayın çünkü TypeScript, VE / VEYA kullanan nesnelerde ekstra özelliklere izin verir Bkz. Https://github.com/Microsoft/TypeScript/issues/15447


1
Kullanabilir ve ayrı bir türe &ayıklayabilirsiniz . :)titleicon
Robert Koritnik

5

Ben yaptım:

export interface MenuItem {
  title: string;
  icon: string;
}

export interface MenuItemComponent extends MenuItem{
  component: any;
}

export interface MenuItemClick extends MenuItem{
  click: any;
}

Sonra kullandım:

 appMenuItems: Array<MenuItemComponent|MenuItemClick>;

Ancak bunu tek bir arayüzle modellemenin bir yolu olduğunu umuyordum.


1
Aslında Microsoft şu şekilde olmasını öneriyor:<MenuItemComponent|MenuItemClick>[]
Loolooii

3

PickBu tür koşullu gereksinimleri oluşturmak için tüm özellikleri içeren bir temel türle birlikte kullanmayı seviyorum .

interface MenuItemProps {
  title: string;
  component: any;
  click: any;
  icon: string;
}

export interface MenuItem =
  Pick<MenuItemProps, "title" | "icon" | "component"> |
  Pick<MenuItemProps, "title" | "icon" | "click">

Bu temiz ve aynı zamanda esnektir. Beyanınızı basit ve okunaklı tutarken, "tüm özellikleri, yalnızca bu iki özelliği veya yalnızca bu özelliği gerektirdiğini" vb.


1
arayüzler bir tür "=" olamaz. Bu export interface MenuItem = ...olsaydı geçerli olurdu.
J'e

2

Bunu kullanıyorum:

type RequireField<T, K extends keyof T> = T & Required<Pick<T, K>>

Kullanım:

let a : RequireField<TypeA, "fieldA" | "fieldB">;

Bu yapar fieldAve fieldB gereklidir .


0

Sadece yukarıdaki harika cevapları genişletmek için! Ve yetenek gerektiren bir Kısmi versiyon ararken buraya inen insanlar için ! İşte almak için yaptığım bir pasaj!

KısmiReq

Bir arayüzün bir Kısmına sahip olmak istiyorsunuz, ancak bu arada bazı alanlara ihtiyacınız var! İşte nasıl yapılır

export type PartialReq<T, Keys extends keyof T = keyof T> =
    Pick<Partial<T>, Exclude<keyof T, Keys>>
    & {
        [K in Keys]: T[K]
    };

Örnek kullanın

export interface CacheObj<SigType = any, ValType = any> {
    cache: Map<SigType, ValType>,
    insertionCallback: InsertionCallback<SigType, ValType> // I want this to be required
}

// ...

export class OneFlexibleCache<SigType = any, ValType = any> {
    private _cacheObj: CacheObj<SigType, ValType>;

    constructor(
        cacheObj: PartialReq<CacheObj<SigType, ValType>, 'insertionCallback'> // <-- here
                                                                           //  i used it
    ) {
        cacheObj = cacheObj || {};

        this._cacheObj = {

// ...

// _______________ usage
this._caches.set(
    cacheSignature,
    new OneFlexibleCache<InsertionSigType, InsertionValType>({
        insertionCallback // required need to be provided
    })
);

Burada mükemmel çalıştığını görebilirsiniz

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

Gerekli sağlanmadıysa

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

GÜNCELLEME: Yukarıda ima ettiğim kullanım için daha iyi bir cevap

Doktora baktım ve Omit'i buldum .

https://www.typescriptlang.org/docs/handbook/utility-types.html#omittk

Eklemeye geldim. Ama bunu yapmadan önce, bu harika cevabı gördüm. Hepsini kapsar:

https://stackoverflow.com/a/48216010/7668448

Sadece kontrol edin! Typescript'in tüm farklı sürümleri için nasıl yapılacağını gösterir! Ve tekrarlamamak uğruna! Git ve kontrol et!

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.