Khi nào sử dụng JSX.Element so với ReactNode và ReactElement?


142

Tôi hiện đang chuyển một ứng dụng React sang TypeScript. Cho đến nay, điều này hoạt động khá tốt, nhưng tôi gặp vấn đề với các kiểu trả về của các renderhàm tương ứng với các thành phần hàm của tôi.

Từ trước đến nay, tôi luôn sử dụng JSX.Elementlàm kiểu trả về, bây giờ điều này không hoạt động nữa nếu một thành phần quyết định không hiển thị bất kỳ thứ gì, tức là trả về null, vì nullkhông có giá trị hợp lệ cho JSX.Element. Đây là sự khởi đầu của cuộc hành trình của tôi, bởi vì bây giờ tôi đã tìm kiếm trên web và thấy rằng bạn nên sử dụng ReactNodethay thế, điều này cũng bao gồm nullvà một vài thứ khác có thể xảy ra. Đây dường như là một cuộc đặt cược tốt hơn.

Tuy nhiên, bây giờ khi tạo một thành phần chức năng, TypeScript phàn nàn về ReactNodekiểu. Một lần nữa, sau một số tìm kiếm, tôi đã tìm thấy rằng đối với các thành phần chức năng, bạn nên sử dụng ReactElementthay thế. Tuy nhiên, nếu tôi làm như vậy, vấn đề tương thích đã biến mất, nhưng bây giờ TypeScript lại phàn nàn về việc nullkhông phải là giá trị hợp lệ.

Vì vậy, để cắt ngắn một câu chuyện dài, tôi có ba câu hỏi:

  1. Sự khác biệt giữa JSX.Element, ReactNodevà là ReactElementgì?
  2. Tại sao các renderphương thức của các thành phần lớp trả về ReactNode, nhưng các thành phần hàm lại trả về ReactElement?
  3. Làm cách nào để giải quyết vấn đề này null?

Tôi ngạc nhiên vì điều này sẽ xuất hiện, vì thông thường bạn không cần chỉ định kiểu trả về với các thành phần. Bạn đang làm kiểu chữ ký nào cho các thành phần? Sẽ trông giống như class Example extends Component<ExampleProps> {đối với các lớp và const Example: FunctionComponent<ExampleProps> = (props) => {các thành phần chức năng (đâu ExamplePropslà giao diện cho các đạo cụ dự kiến). Và sau đó các kiểu này có đủ thông tin mà kiểu trả về có thể được suy ra.
Tháp Nicholas vào

Các loại được định nghĩa ở đây
Jonas Wilms

1
@NicholasTower Các quy tắc linting của chúng tôi thực thi việc cung cấp kiểu trả về một cách rõ ràng và đó là lý do tại sao điều này xuất hiện (IMHO là một điều tốt, bởi vì bạn nghĩ nhiều hơn về những gì bạn làm, điều này giúp hiểu rõ hơn là nếu bạn chỉ để trình biên dịch suy luận mọi thứ) .
Golo Roden

Công bằng mà nói, tôi đã không nghĩ ra các quy tắc vẽ tranh.
Tháp Nicholas

@JonasWilms Cảm ơn liên kết, nhưng tôi không nghĩ rằng điều này trả lời câu hỏi của tôi.
Golo Roden

Câu trả lời:


154

Sự khác biệt giữa JSX.Element, ReactNode và ReactElement là gì?

ReactElement là một đối tượng có kiểu và đạo cụ.

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

ReactNode là một ReactElement, một ReactFragment, một chuỗi, một số hoặc một mảng ReactNodes, hoặc null, hoặc không xác định hoặc boolean:

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 là một ReactElement, với loại chung cho các đạo cụ và loại là bất kỳ. Nó tồn tại, vì các thư viện khác nhau có thể triển khai JSX theo cách riêng của chúng, do đó JSX là một không gian tên toàn cục sau đó được thư viện đặt, React đặt nó như thế này:

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

Ví dụ như:

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

Tại sao các phương thức kết xuất của các thành phần lớp trả về ReactNode, nhưng các thành phần chức năng lại trả về ReactElement?

Thật vậy, họ trả lại những thứ khác nhau. Components trả lại:

 render(): ReactNode;

Và các hàm là "thành phần không trạng thái":

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

Điều này thực sự là do lý do lịch sử .

Làm cách nào để giải quyết vấn đề này đối với null?

Nhập nó ReactElement | nullgiống như phản ứng. Hoặc để cho Typecript suy ra loại.

nguồn cho các loại


Cảm ơn các câu trả lời chi tiết! Điều này trả lời hoàn hảo câu hỏi 1, nhưng vẫn để lại câu hỏi 2 và 3 chưa được trả lời. Bạn cũng có thể cung cấp một số hướng dẫn về chúng được không?
Golo Roden,

chắc chắn @goloRoden, tôi chỉ là một chút mệt mỏi ngay bây giờ và phải mất một thời gian để cuộn qua các loại trên một điện thoại di động ...;)
Jonas Wilms

Xin chào @JonasWilms. Về "Họ không. ReactComponent được định nghĩa là: render (): JSX.Element | null | false;", bạn thấy điều đó ở đâu? Có vẻ như nó trả về ReactNode cho tôi ( github.com/DefinifinityTyped/DefinifinityTyped/blob/… ) Cũng có lỗi đánh máy nhỏ, tôi nghĩ ReactComponentnên là React.Componenthoặc Component.
Mark Doliner

@MarkDoliner Weird, tôi có thể thề rằng tôi đã sao chép kiểu đó ra khỏi các tập tin ... Bất cứ điều gì, bạn hoàn toàn đúng, tôi sẽ chỉnh sửa
Jonas Wilms

Các bạn có tài liệu chính thức nào về bản đánh chữ react && ở đâu đó trên web không?
Goran_Ilic_Ilke

25

1.) Sự khác biệt giữa JSX.Element, ReactNode và ReactElement là gì?

ReactElement và JSX.Element là kết quả của việc gọi React.createElementtrực tiếp hoặc thông qua chuyển đổi JSX. Nó là một đối tượng với type, propskey. JSX.ElementReactElementcủa ai propstypecó loại any, vì vậy chúng ít nhiều giống nhau.

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

ReactNode được sử dụng làm kiểu trả về cho render()các thành phần lớp. Nó cũng là kiểu mặc định cho childrenthuộc tính với PropsWithChildren.

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

Nó trông phức tạp hơn trong các khai báo kiểu React , nhưng tương đương với:

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...)

Bạn có thể gán hầu hết mọi thứ cho ReactNode. Tôi thường thích các loại mạnh hơn, nhưng có thể có một số trường hợp hợp lệ để sử dụng nó.


2.) Tại sao các phương thức kết xuất của các thành phần lớp trả về ReactNode, nhưng các thành phần chức năng lại trả về ReactElement?

tl; dr: Đó là sự không tương thích kiểu TS hiện tại không liên quan đến React lõi .

  • Thành phần lớp TS: trả về ReactNodevới render(), dễ dãi hơn React / JS

  • Thành phần hàm TS: trả về JSX.Element | null, hạn chế hơn React / JS

Về nguyên tắc, render()trong React / JS, các thành phần của lớp hỗ trợ các kiểu trả về giống như một thành phần hàm. Đối với TS, các kiểu khác nhau là một kiểu không thống nhất vẫn được giữ nguyên do lịch sử và nhu cầu tương thích ngược.

Lý tưởng nhất là kiểu trả về hợp lệ có thể trông giống như thế này:

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

3.) Làm cách nào để giải quyết vấn đề này đối với null?

Một số tùy chọn:
// 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>;

Lưu ý: Việc tránh React.FC sẽ không giúp bạn thoát khỏi JSX.Element | nullgiới hạn loại trả lại.

Tạo Ứng dụng React gần đây đã bị loạiReact.FC khỏi mẫu của nó, vì nó có một số điểm kỳ quặc như {children?: ReactNode}định nghĩa kiểu ngầm . Vì vậy, sử dụng một React.FCcách tiết kiệm có thể tốt hơn.

Trong các trường hợp cạnh, bạn có thể thêm xác nhận kiểu hoặc Phân đoạn làm giải pháp thay thế:
const MyCompFragment: FunctionComponent = () => <>"Hello"</>
const MyCompCast: FunctionComponent = () => "Hello" as any 
// alternative to `as any`: `as unknown as JSX.Element | null`
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.