TypeScript và React - kiểu con?


137

Tôi có một thành phần chức năng rất đơn giản như sau:

import * as React from 'react';

export interface AuxProps  { 
    children: React.ReactNode
 }


const aux = (props: AuxProps) => props.children;

export default aux;

Và một thành phần khác:

import * as React from "react";

export interface LayoutProps  { 
   children: React.ReactNode
}

const layout = (props: LayoutProps) => (
    <Aux>
        <div>Toolbar, SideDrawer, Backdrop</div>
        <main>
            {props.children}
        </main>
    <Aux/>
);

export default layout;

Tôi tiếp tục gặp lỗi sau:

[ts] Loại phần tử JSX 'ReactNode' không phải là một hàm tạo cho các phần tử JSX. Không thể gán loại 'undefined' cho loại 'ElementClass'. [2605]

Làm cách nào để gõ đúng?

Câu trả lời:


132

Chỉ children: React.ReactNode


Đây là câu trả lời chính xác ở đây! JSX.Elementkhông đủ tốt vì React con hợp lệ có thể là một chuỗi, một boolean, null ... ReactChildcũng không hoàn chỉnh vì những lý do tương tự
Pierre Ferry

2
@PierreFerry Vấn đề là, hầu hết mọi thứ đều có thể được gán cho ReactNode. Nó không giúp ích gì về mặt an toàn kiểu gõ, tương tự như gõ childrenbằng any- xem câu trả lời của tôi để biết giải thích.
ford04

Cảm ơn phản hồi của bạn @ ford04. Trong trường hợp sử dụng của tôi, tôi muốn trẻ em nhập một cách lỏng lẻo. Thành phần của tôi chấp nhận bất kỳ loại React con hợp lệ nào. Vì vậy, tôi tin rằng đây vẫn là câu trả lời mà tôi đang tìm kiếm :)
Pierre Ferry

47

Để sử dụng <Aux>trong JSX của bạn, nó cần phải là một hàm trả về ReactElement<any> | null. Đó là định nghĩa của một thành phần chức năng .

Tuy nhiên, nó hiện được định nghĩa là một hàm trả về React.ReactNode, là một kiểu rộng hơn nhiều. Như các cách đánh máy trong React nói:

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

Đảm bảo rằng các loại không mong muốn được vô hiệu hóa bằng cách gói giá trị trả về vào React Fragment ( <></>):

const aux: React.FC<AuxProps> = props =>
  <>{props.children}</>;

Tôi không hiểu tại sao nó cần được bọc childrentrong React Fragment. Quan tâm để giải thích? Trong React, nó hoàn toàn hợp lệ return children;.
sanfilippopablo

1
Không nếu childrenlà một mảng. Nếu đó là trường hợp, bạn cần phải gói chúng trong một nút duy nhất hoặc sử dụng React Fragment.
Karol Majewski,

Vâng, trong mã của tôi, tôi có return React.Children.count(children) > 1 ? <>{children}</> : children:, nhưng bảng chữ phàn nàn về điều đó. Tôi đã thay thế nó bằng chỉ <>{children}</>.
sanfilippopablo

2
Nó phàn nàn vì childrenlà danh sách các phần tử chỉ chứa 1 mục. Tôi nghĩ rằng nó sẽ có tác dụng nếu bạn đã làm return React.Children.count(children) > 1 ? <>{children}</> : children[0]@sanfilippopablo
tamj0rd2

Điều này dường như không hoạt động trong các phiên bản React mới hơn. Tôi đã ghi lại bảng điều khiển và có thể xác nhận rằng tôi có chỗ dựa con cái, nhưng ngay cả với <React.Fragment>xung quanh {props.children}tôi cũng không trả lại được gì.
Đánh dấu

34

Đây là những gì làm việc cho tôi:

interface Props {
  children: JSX.Element[] | JSX.Element
}

Chỉnh sửa Tôi khuyên bạn nên sử dụng children: React.ReactNodethay thế ngay bây giờ.


8
Định nghĩa đó không đủ rộng. Đứa trẻ có thể là một chuỗi.
Mike S

Ít nhất ví dụ này đã làm việc cho tôi vì thành phần của tôi được cho là mong đợi 1 hoặc nhiều JSX.Elements. Cảm ơn!
Italo Borges

1
Điều này sẽ không hoạt động với các biểu thức JavaScript là con <MyComponent>{ condition ? <span>test</span> : null}</MyComponent>. Sử dụng children: ReactNodesẽ hiệu quả.
Nickofthyme

10

bạn có thể khai báo thành phần của mình như sau:

const MyComponent: React.FunctionComponent = (props) => {
    return props.children;
}

9

Bạn có thể sử dụng ReactChildrenReactChild:

import React, { ReactChildren, ReactChild } from 'react';

interface AuxProps {
  children: ReactChild | ReactChildren;
}

const Aux = ({ children }: AuxProps) => (<div>{children}</div>);

export default Aux;

6

Từ trang TypeScript: https://github.com/Microsoft/TypeScript/issues/6471

Phương pháp được khuyến nghị là viết loại đạo cụ là {children ?: any}

Điều đó đã làm việc cho tôi. Nút con có thể là nhiều thứ khác nhau, vì vậy việc nhập rõ ràng có thể bỏ sót các trường hợp.

Có một cuộc thảo luận dài hơn về vấn đề tiếp theo tại đây: https://github.com/Microsoft/TypeScript/issues/13618 , nhưng mọi phương pháp vẫn hoạt động.


6

Kiểu trả về của thành phần hàm được giới hạn JSXElement | nulltrong TypeScript. Đây là một hạn chế về kiểu hiện tại, React thuần túy cho phép nhiều kiểu trả về hơn .

Đoạn mã trình diễn tối thiểu

Bạn có thể sử dụng xác nhận kiểu hoặc Phân đoạn làm giải pháp thay thế:

const Aux = (props: AuxProps) => <>props.children</>; 
const Aux2 = (props: AuxProps) => props.children as ReactElement; 

ReactNode

children: React.ReactNodecó thể là không tối ưu, nếu mục tiêu là có các loại mạnh cho Aux.

Hầu hết mọi thứ đều có thể được gán cho ReactNodekiểu hiện tại , tương đương với {} | undefined | null. Một loại an toàn hơn cho trường hợp của bạn có thể là:

interface AuxProps {
  children: ReactElement | ReactElement[]
}

Thí dụ:

Do Auxnhu cầu của các phần tử React children, chúng tôi đã vô tình thêm a stringvào nó. Sau đó, giải pháp trên sẽ lỗi ngược lại ReactNode- hãy xem các sân chơi được liên kết.

Đã nhập childrencũng hữu ích cho các đạo cụ không phải JSX, chẳng hạn như lệnh gọi lại Render Prop .


5

Bạn cũng có thể dùng React.PropsWithChildren<P> .

type ComponentWithChildProps = React.PropsWithChildren<{example?: string}>;

3

Cách chung để tìm bất kỳ loại nào là bằng ví dụ. Cái hay của cách đánh chữ là bạn có quyền truy cập vào tất cả các loại, miễn là bạn có@types/ tệp .

Để tự trả lời điều này, tôi chỉ nghĩ về một thành phần phản ứng sử dụng có childrenhỗ trợ. Điều đầu tiên nghĩ đến? Làm thế nào về một<div /> ?

Tất cả những gì bạn cần làm là mở vscode và tạo một .tsxtệp mới trong một dự án phản ứng với @types/react.

import React from 'react';

export default () => (
  <div children={'test'} />
);

Di chuột qua chỗ dựa childrensẽ hiển thị cho bạn loại. Và bạn biết gì - Loại của nó là ReactNode(không cần ReactNode[]).

nhập mô tả hình ảnh ở đây

Sau đó, nếu bạn nhấp vào định nghĩa kiểu nó mang đến cho bạn thẳng đến định nghĩa của childrentừ DOMAttributesgiao diện.

// node_modules/@types/react/index.d.ts
interface DOMAttributes<T> {
  children?: ReactNode;
  ...
}

Lưu ý: Quá trình này nên được sử dụng để tìm bất kỳ loại nào chưa biết! Tất cả chúng ở đó chỉ chờ bạn tìm thấy chúng :)


2

Là một loại có chứa trẻ em, tôi đang sử dụng:

type ChildrenContainer = Pick<JSX.IntrinsicElements["div"], "children">

Loại vùng chứa con này đủ chung để hỗ trợ tất cả các trường hợp khác nhau và cũng phù hợp với API ReactJS.

Vì vậy, ví dụ của bạn, nó sẽ giống như:

const layout = ({ children }: ChildrenContainer) => (
    <Aux>
        <div>Toolbar, SideDrawer, Backdrop</div>
        <main>
            {children}
        </main>
    <Aux/>
)

1

Những câu trả lời này dường như đã lỗi thời - React hiện đã được tích hợp sẵn PropsWithChildren<{}>. Nó được định nghĩa tương tự như một số câu trả lời đúng trên trang này:

type PropsWithChildren<P> = P & { children?: ReactNode };


1
Trong Ptrường hợp của loại đó là gì?
sunpietro

Plà loại đạo cụ thành phần của bạn
Tim Iles

-2

Các thành phần React phải có một nút trình bao bọc duy nhất hoặc trả về một mảng các nút.

<Aux>...</Aux>Thành phần của bạn có hai nút divmain.

Cố gắng quấn trẻ em của bạn trong một divtrong Auxthành phần.

import * as React from 'react';

export interface AuxProps  { 
  children: React.ReactNode
}

const aux = (props: AuxProps) => (<div>{props.children}</div>);

export default aux;

1
Không đúng. Trong Phản ứng 16+ này không còn là trường hợp, cộng với lỗi sẽ nói điều này nếu nó là những trường hợp
Andrew Li
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.