reactjs - Bir React + TypeScript değişkeni için: bir bileşenin "props" içinde bazı özellikleri zorunlu kıl


Bir React bileşeni tutabilecek bir değişkene (aslında fonksiyon argümanı, ama problemi basitleştirdim) sahip olmak istiyorum ve bu bileşenin özellikleri arasında örneğin aNumber: number olmalı.

Başarısız denemem şuydu:

function MyComp(props: { aNumber: number, aString: string }) {
    return <>{props.aNumber} {props.aString}</>;
}

let f2: FunctionComponent<{ aNumber: number }> = MyComp;

bana hatayı veriyor:

Type '(props: { aNumber: number; aString: string; }) => Element' is not assignable to type 'FunctionComponent<{ aNumber: number; }>'.
  Types of parameters 'props' and 'props' are incompatible.
    Property 'aString' is missing in type 'PropsWithChildren<{ aNumber: number; }>' but required in type '{ aNumber: number; aString: string; }'.ts(2322)

FunctionComponent react'te şöyle tanımlanır:

    interface FunctionComponent<P = {}> {
        (props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null;
        propTypes?: WeakValidationMap<P>;
        contextTypes?: ValidationMap<any>;
        defaultProps?: Partial<P>;
        displayName?: string;
    }

Hatanın ne olduğunu anlamıyorum. Eğer bir değişkeni aNumber olma zorunluluğuyla tanımladıysam ve ona daha zengin bir öğe atadıysam => neden mutlu değil? Ve neden bu 'daha zengin' özelliklerle ilgili şikayet ediyor?

Elbette, aşağıdakiler işe yarar:

let f1: FunctionComponent<any> = MyComp;

Genellikle TS'de gelişmiş yazma yapılarını denediğimde, sonunda birçok kafa karışıklığından sonra çalıştırmayı başarırım. Bu sefer gelişmiş görünmüyor, ama çözmeyi başaramadım.

Şimdiden çok teşekkürler.

Cevap #1

Sorun şu ki, React bileşenleri fonksiyonlardır ve fonksiyonlar zıtvaryantlardır; yani T tipi parametrelere sahip bir fonksiyon gerekirse, T'den daha geniş bir tür fonksiyonla tatmin edilebilir ancak daha dar bir tip ile yetinmeyebilir. Yani, örneğin düz nesnelerin aksine, yani dahadar bir türü kabul eden ama daha geniş bir türü kabul etmeyen.

Aynı şeyi herhangi bir React kodu olmadan görebilirsiniz

function MyFunc(props:{a: number, b: string}){return}

let func2: (props:{a:number})=>void = MyFunc 
//   ^-- error because MyFunc is narrower than typeof func2

Bunu düşünmenin sezgisel bir yolu, bu duruma işlevinizi/React bileşeninizi çağıran tüketicinin perspektifinden bakmaktır. typeof f2 türünde bir işlev çağırmayı bekledikleri durumda, yalnızca aNumber prop'unu geçirmeleri gerektiğini bekliyorlar, ancak f2'ye daha dar bir işlev atayarak, artık beklemedikleri bir şekilde aString prop'unu da geçirmeleri gerekecek.

Bu konu React'te anlaşılabilir bir sebepten dolayı sıkça gündeme gelir. Bir <Button> bileşeniniz olduğunu ve bunun bir prop aldığını, ve bir <FancyButton> bileşeninin ise birden fazla prop aldığını hayal edin. Düşünmek cazip gelebilir; eğer herhangi bir <Button> yeterliyse, o zaman bir <FancyButton> de aynı işi görür. Bu, basit miras gibi geliyor olabilir. Fakat biraz daha düşünürseniz bir hata olduğunu görürsünüz. Eğer bir bileşen olarak çocuklarımdan biri olarak bir <Button> eklemem gerektiğini bekliyorsam, bu yalnızca ona bir prop geçmeyi beklediğim anlamına gelir. Şimdi yerine bir <FancyButton> koyarsam, o bileşenin beklemediğim bir sürü prop’u da beklemesi gerekir.

Sezgisel nokta şudur ki, sadece <Button> bileşenini göstermiyorsunuz -- bu durumda <FancyButton>'ın daha fazla "gösteriş"e sahip olmasıyla ilgilenmeyebilirsiniz. Asıl durum ise, bileşene gerçekten girdiler--props'lar--sağlamanız gerektiği ve bu nedenle daha dar bir bileşenin, sizin (ebeveyn bileşen) daha fazla girdi sağlamanızı gerektireceğidir.

Aynı mantık elbette genel olarak tüm fonksiyonlar için geçerlidir, ama sanırım React'te daha sık gündeme geliyor çünkü bileşenleri genellikle 'görüntülenecek nesneler' olarak düşünme eğilimindeyiz.

İşte bu konudaki daha kapsamlı bir SO cevabı (başta zor olabilir!): Typescript'te kovaryant ve kontravaryant pozisyonlar arasındaki fark

İlgili makaleler