เมื่อใดควรใช้ JSX.Element vs ReactNode vs ReactElement


168

ฉันกำลังย้ายแอปพลิเคชัน React ไปยัง TypeScript จนถึงตอนนี้มันใช้งานได้ดี แต่ฉันมีปัญหากับประเภทการส่งคืนของrenderฟังก์ชันตามลำดับส่วนประกอบฟังก์ชันของฉัน

จนถึงตอนนี้ผมได้ใช้เสมอJSX.Elementเป็นชนิดกลับตอนนี้ไม่ได้ทำงานใด ๆ มากขึ้นถ้าเป็นส่วนประกอบตัดสินใจที่จะไม่ทำให้อะไรคือผลตอบแทนnullตั้งแต่ไม่มีค่าที่ถูกต้องสำหรับnull JSX.Elementนี่เป็นจุดเริ่มต้นของการเดินทางของฉันเพราะตอนนี้ฉันค้นหาเว็บและพบว่าคุณควรใช้ReactNodeแทนซึ่งรวมถึงnullสิ่งอื่น ๆ ที่อาจเกิดขึ้นได้ด้วย นี่ดูเหมือนจะเป็นการเดิมพันที่ดีกว่า

อย่างไรก็ตามตอนนี้เมื่อสร้างส่วนประกอบของฟังก์ชัน TypeScript จะบ่นเกี่ยวกับReactNodeประเภท อีกครั้งหลังจากการค้นหาบางอย่างฉันพบว่าสำหรับส่วนประกอบของฟังก์ชันคุณควรใช้ReactElementแทน อย่างไรก็ตามหากฉันทำเช่นนั้นปัญหาความเข้ากันได้จะหายไป แต่ตอนนี้ TypeScript บ่นอีกครั้งว่าnullไม่ใช่ค่าที่ถูกต้อง

ดังนั้นเพื่อตัดเรื่องสั้นให้สั้นฉันมีคำถามสามข้อ:

  1. คือความแตกต่างระหว่างสิ่งที่JSX.Element, ReactNodeและReactElement?
  2. ทำไมrenderวิธีการของส่วนประกอบชั้นกลับReactNodeแต่ชิ้นส่วนฟังก์ชั่นกลับมาReactElement?
  3. ฉันจะแก้ปัญหานี้ได้nullอย่างไร?

1
ฉันแปลกใจที่สิ่งนี้เกิดขึ้นเนื่องจากโดยปกติคุณไม่จำเป็นต้องระบุประเภทการส่งคืนด้วยส่วนประกอบ คุณกำลังทำลายเซ็นประเภทใดสำหรับส่วนประกอบ ควรมีลักษณะคล้ายclass Example extends Component<ExampleProps> {กับคลาสและconst Example: FunctionComponent<ExampleProps> = (props) => {สำหรับส่วนประกอบของฟังก์ชัน (ซึ่งExamplePropsเป็นอินเทอร์เฟซสำหรับอุปกรณ์ประกอบฉากที่คาดไว้) จากนั้นประเภทเหล่านี้มีข้อมูลเพียงพอที่จะอนุมานประเภทการส่งคืนได้
Nicholas Tower

มีการกำหนดประเภทไว้ที่นี่
Jonas Wilms

1
@NicholasTower กฎผ้าสำลีของเราบังคับให้ระบุประเภทการส่งคืนอย่างชัดเจนและนั่นคือสาเหตุที่เกิดขึ้น (ซึ่ง IMHO เป็นสิ่งที่ดีเพราะคุณคิดเกี่ยวกับสิ่งที่คุณทำมากขึ้นซึ่งจะช่วยให้เข้าใจมากกว่าที่คุณจะปล่อยให้คอมไพเลอร์อนุมานทุกอย่าง) .
Golo Roden

พอใช้ฉันไม่ได้คิดถึงกฎที่เป็นขุย
Nicholas Tower

@JonasWilms ขอบคุณสำหรับลิงค์ แต่ฉันไม่คิดว่านี่จะตอบคำถามของฉัน
Golo Roden

คำตอบ:


182

JSX.Element, ReactNode และ ReactElement แตกต่างกันอย่างไร

ReactElement คือวัตถุที่มีประเภทและอุปกรณ์ประกอบฉาก

 interface ReactElement<P = any, T extends string | JSXElementConstructor<any> = string | JSXElementConstructor<any>> {
    type: T;
    props: P;
    key: Key | null;
}

ReactNode คือ ReactElement, ReactFragment, สตริง, ตัวเลขหรืออาร์เรย์ของ ReactNodes หรือ null หรือไม่ได้กำหนดหรือบูลีน:

type ReactText = string | number;
type ReactChild = ReactElement | ReactText;

interface ReactNodeArray extends Array<ReactNode> {}
type ReactFragment = {} | ReactNodeArray;

type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;

JSX.Element คือ ReactElement โดยประเภททั่วไปสำหรับอุปกรณ์ประกอบฉากและประเภทเป็นแบบใดก็ได้ มีอยู่เนื่องจากไลบรารีต่างๆสามารถใช้ JSX ในแบบของตัวเองได้ดังนั้น JSX จึงเป็นเนมสเปซส่วนกลางที่ถูกกำหนดโดยไลบรารี React จึงตั้งค่าดังนี้:

declare global {
  namespace JSX {
    interface Element extends React.ReactElement<any, any> { }
  }
}

ตามตัวอย่าง:

 <p> // <- ReactElement = JSX.Element
   <Custom> // <- ReactElement = JSX.Element
     {true && "test"} // <- ReactNode
  </Custom>
 </p>

เหตุใดวิธีการเรนเดอร์ของส่วนประกอบคลาสจึงส่งคืน ReactNode แต่คอมโพเนนต์ของฟังก์ชันส่งคืน ReactElement

แท้จริงพวกเขากลับสิ่งที่แตกต่างกัน Componentผลตอบแทน:

 render(): ReactNode;

และฟังก์ชั่นคือ "ส่วนประกอบไร้สัญชาติ":

 interface StatelessComponent<P = {}> {
    (props: P & { children?: ReactNode }, context?: any): ReactElement | null;
    // ... doesn't matter
}

นี้เป็นจริงเนื่องจากเหตุผลทางประวัติศาสตร์

ฉันจะแก้ปัญหานี้เกี่ยวกับโมฆะได้อย่างไร

พิมพ์เหมือนกับReactElement | nullที่ทำปฏิกิริยา หรือให้ typescript อนุมานประเภท

แหล่งที่มาของประเภท


ขอบคุณสำหรับคำตอบโดยละเอียด! คำถามนี้ตอบคำถาม 1 ได้อย่างสมบูรณ์แบบ แต่ยังคงทิ้งคำถามที่ 2 และ 3 ที่ยังไม่มีคำตอบ โปรดให้คำแนะนำเกี่ยวกับพวกเขาด้วยได้ไหม?
Golo Roden

@goloRoden แน่นอนตอนนี้ฉันเหนื่อยนิดหน่อยและต้องใช้เวลาพอสมควรในการเลื่อนดูประเภทต่างๆบนมือถือ ... ;)
Jonas Wilms

สวัสดี @JonasWilms เกี่ยวกับ "They don't. ReactComponent ถูกกำหนดให้เป็น: render (): JSX.Element | null | false;" คุณเห็นสิ่งนั้นที่ไหน ดูเหมือนว่ามันจะกลับมาให้ฉัน ReactNode ( github.com/DefinitelyTyped/DefinitelyTyped/blob/... ) พิมพ์ผิดเล็ก ๆ น้อย ๆ นอกจากนี้ผมคิดว่าReactComponentควรจะเป็นหรือReact.Component Component
Mark Doliner

@MarkDoliner แปลกฉันสาบานได้ว่าฉันคัดลอกประเภทนั้นออกจากไฟล์ ... อย่างไรก็ตามคุณพูดถูกทั้งหมดฉันจะแก้ไข
Jonas Wilms

Guys มีเอกสารอย่างเป็นทางการของ react && typescript ที่ไหนสักแห่งบนเว็บหรือไม่?
Goran_Ilic_Ilke

30

1. ) JSX.Element, ReactNode และ ReactElement แตกต่างกันอย่างไร

ReactElement และ JSX.Element เป็นผลมาจากการเรียกใช้React.createElementโดยตรงหรือผ่านการขนส่ง JSX มันเป็นวัตถุที่มีtype, และprops คือใครและมีประเภทจึงเหมือนกันมากหรือน้อยkeyJSX.ElementReactElementpropstypeany

const jsx = <div>hello</div>
const ele = React.createElement("div", null, "hello");

ReactNodeถูกใช้เป็นประเภทการส่งคืนสำหรับrender()ส่วนประกอบในคลาส นอกจากนี้ยังเป็นชนิดเริ่มต้นสำหรับแอตทริบิวต์กับchildrenPropsWithChildren

const Comp: FunctionComponent = props => <div>{props.children}</div> 
// children?: React.ReactNode

มันดูซับซ้อนกว่าในการประกาศประเภทการตอบสนองแต่เทียบเท่ากับ:

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คุณสามารถกำหนดเกือบทุกอย่างเพื่อ ฉันมักจะชอบประเภทที่แข็งแรงกว่า แต่อาจมีบางกรณีที่ใช้ได้


2. ) เหตุใดวิธีการเรนเดอร์ของส่วนประกอบคลาสจึงส่งคืน ReactNode แต่ส่วนประกอบของฟังก์ชันส่งคืน ReactElement

TL; DR:มันเป็นปัจจุบัน TS ประเภทไม่ลงรอยกันไม่เกี่ยวข้องกับหลักตอบสนอง

  • องค์ประกอบคลาส TS: ส่งกลับReactNodeด้วยrender()อนุญาตมากกว่า React / JS

  • ส่วนประกอบฟังก์ชัน TS: ส่งกลับJSX.Element | nullมีข้อ จำกัด มากกว่า React / JS

โดยหลักการแล้วrender()ในคอมโพเนนต์คลาส React / JS สนับสนุนประเภทการส่งคืนเดียวกันกับคอมโพเนนต์ของฟังก์ชัน เกี่ยวกับ TS ประเภทต่างๆคือประเภทที่ไม่สอดคล้องกันยังคงถูกเก็บไว้เนื่องจากเหตุผลทางประวัติศาสตร์และความจำเป็นในการใช้งานร่วมกันได้แบบย้อนกลับ

ตามหลักการแล้วประเภทผลตอบแทนที่ถูกต้องน่าจะมีลักษณะดังนี้:

type ComponentReturnType = ReactElement | Array<ComponentReturnType> | string | number 
  | boolean | null // Note: undefined is invalid

3. ) ฉันจะแก้ปัญหานี้เกี่ยวกับโมฆะได้อย่างไร?

บางตัวเลือก:
// 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>;

หมายเหตุ: การหลีกเลี่ยงReact.FC จะไม่ช่วยให้คุณประหยัดจากJSX.Element | nullข้อ จำกัด ประเภทการส่งคืน

สร้างแอป React ที่เพิ่งหลุดReact.FCจากเทมเพลตเนื่องจากมีนิสัยแปลก ๆ เช่นการ{children?: ReactNode}กำหนดประเภทโดยนัย ดังนั้นการใช้React.FCเท่าที่จำเป็นอาจดีกว่า

ในกรณี edge คุณสามารถเพิ่มประเภทการยืนยันหรือ Fragments เป็นวิธีแก้ปัญหาชั่วคราว:
const MyCompFragment: FunctionComponent = () => <>"Hello"</>
const MyCompCast: FunctionComponent = () => "Hello" as any 
// alternative to `as any`: `as unknown as JSX.Element | null`
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.