javascript - JSX.Element, ReactNode ve ReactElement ne zaman kullanılmalı?
Şu anda bir React uygulamasını TypeScript'e geçiriyorum. Şu ana kadar bu oldukça iyi çalışıyor, ancak render fonksiyonlarımın dönüş türleriyle bir sorunum var, özellikle fonksiyonel bileşenlerimde.
Her zaman dönüş tipi olarak JSX.Element kullandım, fakat artık bir bileşen hiçbir şey render etmeye karar vermezse, yani null döndürürse bu çalışmıyor, çünkü null JSX.Element için geçerli bir değer değil. Bu benim yolculuğumun başlangıcıydı. İnternette arama yaptım ve null dahil olmak üzere olabilecek birkaç şeyi içeren ReactNode kullanmanız gerektiğini buldum.
Ancak, bir fonksiyonel bileşen oluştururken TypeScript, ReactNode türü hakkında uyarıda bulunuyor. Yine, biraz araştırma yaptıktan sonra, fonksiyonel bileşenler için ReactElement kullanılması gerektiğini buldum. Ancak, bunu yaparsam uyumluluk sorunu ortadan kalkıyor, fakat şimdi TypeScript yine null'ın geçerli bir değer olmadığını söylüyor.
Kısa kesmek gerekirse, üç sorum var:
JSX.Element,ReactNodeveReactElementarasındaki fark nedir?- Sınıf bileşenlerinin
rendermetodları nedenReactNodedönerken işlevsel bileşenlerReactElementdöner? - Bunu
nullile ilgili olarak nasıl çözerim?
Cevap #1
JSX.Element,ReactNodeveReactElementarasındaki fark nedir?
Bir ReactElement, type, props ve key özelliklerine sahip bir nesnedir:
interface ReactElement<
P = any,
T extends
| string
| JSXElementConstructor<any> = string
| JSXElementConstructor<any>,
> {
type: T;
props: P;
key: string | null;
}
Bir JSX.Element, bir ReactElement<any, any>'tir. Farklı kütüphaneler JSX'i kendi tarzlarında uygulayabildiği için çeşitli biçimlerde var olabilir:
declare global {
// …
namespace JSX {
// …
interface Element extends React.ReactElement<any, any> {}
// …
}
// …
}
Bir ReactPortal, children özelliğine sahip bir ReactElement'tir:
interface ReactPortal extends ReactElement {
children: ReactNode;
}
Bir ReactNode, ReactElement, string, number, Iterable<ReactNode>, ReactPortal, boolean, null veya undefined olabilir:
type ReactNode =
| ReactElement
| string
| number
| Iterable<ReactNode>
| ReactPortal
| boolean
| null
| undefined;
Örnek :
<div> // <- ReactElement
<Component> // <- ReactElement
{condition && 'text'} // <- ReactNode
</Component>
</div>
Sınıf bileşenlerinin render yöntemleri neden ReactNode dönerken, fonksiyon bileşenleri neden ReactElement döner?
Bu tarihsel nedenlerden kaynaklanmaktadır.
Bir Component.render, bir ReactNode döner:
class Component<P, S> {
// …
render(): ReactNode;
// …
}
Bir FunctionComponent, ReactElement<any, any> | null döner:
interface FunctionComponent<P = {}> {
(props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null;
propTypes?: WeakValidationMap<P> | undefined;
contextTypes?: ValidationMap<any> | undefined;
defaultProps?: Partial<P> | undefined;
displayName?: string | undefined;
}
Bunu null ile ilgili olarak nasıl çözerim?
Bunu tıpkı React'in yaptığı gibi ReactElement | null olarak yazın. Veya TypeScript'in türü çıkarmasına izin verin.
Cevap #2
1.) JSX.Element, ReactNode ve ReactElement arasındaki fark nedir?
ReactElement ve JSX.Element, React.createElement'i doğrudan veya JSX transpile işlemi yoluyla çağırmanın sonucudur. type, props ve key içeren bir nesnedir. JSX.Element, props ve type'ı any türünde olan ReactElement'tir, bu yüzden aşağı yukarı aynıdırlar.
const jsx = <div>hello</div>
const ele = React.createElement("div", null, "hello");
ReactNode, sınıf bileşenlerinde render() için dönüş tipi olarak kullanılır. Ayrıca PropsWithChildren ile children özniteliği için varsayılan tiptir.
const Comp: FunctionComponent = props => <div>{props.children}</div>
// children?: React.ReactNode
React tür deklarasyonlarında daha karmaşık görünüyor, ama şununla eşdeğer:
type ReactNode = {} | null | undefined;
// super type `{}` has absorbed *all* other types, which are sub types of `{}`
// so it is a very "broad" type (I don't want to say useless...)
ReactNode'ye neredeyse her şeyi atayabilirsiniz. Genellikle daha güçlü türleri tercih ederim, ama onu kullanmak için geçerli bazı durumlar olabilir.
2.) Neden sınıf bileşenlerinin render metodları ReactNode döndürür, ama fonksiyon bileşenleri ReactElement döndürür?
özet: Bu, çekirdek React ile ilgili olmayan güncel bir TS tür uyumsuzluğudur.
TS sınıf bileşeni:
render()ileReactNodedöndürür, React/JS'den daha izin vericidirTS fonksiyon bileşeni:
JSX.Element | nulldöner, React/JS'den daha kısıtlayıcı
Prensip olarak, React/JS sınıf bileşenlerindeki render() fonksiyonu, bir fonksiyon bileşeniyle aynı dönüş türlerini destekler. TS ile ilgili olarak, farklı türler tarihsel nedenler ve geriye dönük uyumluluk gereği hala korunan bir tür tutarsızlığıdır.
İdeal olarak geçerli bir dönüş türü muhtemelen bu şekilde görünecektir:
type ComponentReturnType = ReactElement | Array<ComponentReturnType> | string | number
| boolean | null // Note: undefined is invalid
3.) Bunu null ile ilgili olarak nasıl çözerim?
// Use type inference; inferred return type is `JSX.Element | null`
const MyComp1 = ({ condition }: { condition: boolean }) =>
condition ? <div>Hello</div> : null
// Use explicit function return types; Add `null`, if needed
const MyComp2 = (): JSX.Element => <div>Hello</div>;
const MyComp3 = (): React.ReactElement => <div>Hello</div>;
// Option 3 is equivalent to 2 + we don't need to use a global (JSX namespace)
// Use built-in `FunctionComponent` or `FC` type
const MyComp4: React.FC<MyProps> = () => <div>Hello</div>;
Not: React.FC'den kaçınmak, JSX.Element | null dönüş tipi kısıtlamasından sizi kurtarmaz.
Create React App yakın zamanda şablonundan React.FC'yi kaldırdı, çünkü {children?: ReactNode} gibi örtük bir tür tanımı gibi bazı tuhaflıkları vardı. Bu yüzden React.FC'yi tedbirli bir şekilde kullanmak tercih edilebilir.
const MyCompFragment: FunctionComponent = () => <>"Hello"</>
const MyCompCast: FunctionComponent = () => "Hello" as any
// alternative to `as any`: `as unknown as JSX.Element | null`
Cevap #3
ReactElement, React'teki öğelerin türüdür; bu öğeler ya JSX ile ya da React.createElement ile oluşturulabilir.
const a = <div/> // this is a ReactElement
ReactNode daha geniştir, metin, sayı, boolean, null, tanımsız, bir portal, bir ReactElement veya ReactNode'lardan oluşan bir dizi olabilir. React'in render edebileceği her şeyi temsil eder.
const a = (
<div>
hello {[1, "world", true]} // this is a ReactNode
</div>
)
JSX.Element, Typescript için dahili bir hook'tur. Her JSX ifadesinin ReactElement olarak tiplenmesi gerektiğini Typescript'e belirtmek için ReactElement'e eşitlenir. Ancak Preact veya JSX kullanan diğer teknolojileri kullanırsak, başka bir şeye eşitlenmiş olur.
Fonksiyonel bileşenler ReactElement | null döner, bu yüzden saf bir string veya ReactElement dizisi döndüremez. Bu bilinen bir sınırlamadır. Çözüm yolu olarak Fragments kullanabilirsiniz :
const Foo = () => {
return <>hello world!</> // this works
}
Sınıf bileşenlerinin render fonksiyonu ReactNode döndürür, bu yüzden herhangi bir sorun olmamalı.
Cevap #4
https://github.com/typescript-cheatsheets/react#useful-react-prop-type-examples
export declare interface AppProps {
children1: JSX.Element; // bad, doesnt account for arrays
children2: JSX.Element | JSX.Element[]; // meh, doesn't accept strings
children3: React.ReactChildren; // despite the name, not at all an appropriate type; it is a utility
children4: React.ReactChild[]; // better, accepts array children
children: React.ReactNode; // best, accepts everything (see edge case below)
functionChildren: (name: string) => React.ReactNode; // recommended function as a child render prop type
style?: React.CSSProperties; // to pass through style props
onChange?: React.FormEventHandler<HTMLInputElement>; // form events! the generic parameter is the type of event.target
// more info: https://react-typescript-cheatsheet.netlify.app/docs/advanced/patterns_by_usecase/#wrappingmirroring
props: Props & React.ComponentPropsWithoutRef<"button">; // to impersonate all the props of a button element and explicitly not forwarding its ref
props2: Props & React.ComponentPropsWithRef<MyButtonWithForwardRef>; // to impersonate all the props of MyButtonForwardedRef and explicitly forwarding its ref
}
Cevap #5
JSX.Element ile React.ReactElement ve React.ReactNode'yu adım adım anlayalım:
JSX.Element ve React.ReactElement
JSX.Element ve React.ReactElement işlevsel olarak aynı türdür. Yerine birbirinin yerine kullanılabilirler.
Aşağıdaki örneği dikkate alın:
const element = <div className="greeting">Hello, world!</div>;
Yukarıdaki JSX kodu çalıştığında, ekranda görebileceğiniz bir şeyi doğrudan oluşturmaz. Bunun yerine, o UI parçasını tanımlayan bir JavaScript nesnesi oluşturur. Bu nesne bir taslak veya tarif gibidir. Bu taslak veya tarif nesnesi şöyle görünecektir:
const element = {
type: 'div',
props: {
className: 'greeting',
children: 'Hello, world!'
}
};
- type: Bu, React'a hangi türde bir öğe oluşturmak istediğimizi söyler. Bu durumda,
div. - props: Bu, öğemize geçirdiğimiz tüm özellikleri (veya "props") tutan bir nesnedir. Burada, değeri
'greeting'olan birclassNameprop geçirdik. - children: Bu, öğemizin içeriğini temsil eden özel bir özelliktir. Bu durumda,
'Hello, world!'stringidir.
Bu nesne, React'in ekrandaki gerçek kullanıcı arayüzünü nasıl oluşturacağını ve güncelleyeceğini anlamak için kullandığı şeydir. React bu nesneyi gördüğünde, bir div elementi oluşturacağını, ona 'greeting' adlı bir sınıf vereceğini ve içine 'Hello, world!' metnini koyacağını bilir.
Kısaca, bir ReactElement'in bir türü ve özellikleri olan bir nesne olduğunu söyleyebiliriz.
Peki JSX.Element ile React.ReactElement arasındaki fark nedir?
JSX.Element, JSX ifadeleri için bir TypeScript türüdür ve React.ReactElement, React öğeleri için bir React türüdür.
Bu doğru mu?
const Component = ({
children,
}: {
children: JSX.Element;
}) => {
return <div>{children}</div>;
};
// Usage
<Component>hello world</Component>
Yukarıda belirtilen örnekte, TypeScript mutlu değil çünkü children tipini JSX.Element olarak ilan ettik, ancak bir JSX.Element olmayan bir string ("hello world") geçiriyoruz. Bu nedenle, string, sayı, null, ReactElement ... kabul edebilecek farklı bir children tipine ihtiyacımız var.
Burası React.ReactNode'un parladığı yer.
React.ReactNode
Aşağıdaki React.ReactNode tanımına göre.
React.ReactNode, bir ReactElement, string, sayı, ReactFragment, ReactPortal, boolean, null ve tanımsız (undefined) olabilir.
declare namespace React {
type ReactNode =
| ReactElement
| string
| number
| ReactFragment
| ReactPortal
| boolean
| null
| undefined;
}
Örnek :
const Component = ({
children,
}: {
children: React.ReactNode
}) => {
return <div>{children}</div>;
};
// Usage
<Component> Hello World </Component>
Artık TypeScript mutlu!!
Mutlu kodlamalar!
