Tìm kiếm trang web

reactjs - Ép thuộc tính của một component thành h2


Tôi đang cố gắng định nghĩa một interface props cho một component sử dụng Typescript, mà yêu cầu giá trị được truyền cho thuộc tính đó phải là một thể hiện của <h2>-<h6> (nhưng không phải <h1>).

Ban đầu tôi đã cố gắng làm điều này, nhưng nó không ép buộc việc gõ như tôi muốn:

type HeaderType = 'h2' | 'h3' | 'h4' | 'h5' | 'h6';

interface OwnProps {
    title?: React.ReactElement<HeaderType>;
}

Điều này cũng không có vẻ hiệu quả (và tôi tin rằng nó sẽ bao gồm h1 mà tôi không muốn):

interface OwnProps {
    title?: React.ReactElement<HTMLHeadingElement>;
}

Tôi hiểu rằng tôi có thể gây ra lỗi chạy thời gian sau khi kiểm tra đầu vào trong component, nhưng có cách nào để giới hạn loại thẻ cho một prop trong React không?

Câu trả lời #1

Đây là một vấn đề khó, chủ yếu xoay quanh việc cố gắng tạo ra trải nghiệm lập trình tốt.

Suy nghĩ đầu tiên của tôi là các H2 đến H6 components, vì chúng ta có thể sau đó giới hạn title theo cách bạn nói bạn muốn (mà tôi không nghĩ chúng ta có thể làm với các thẻ "h2" đến "h6"). Điều đó có vẻ rất hứa hẹn cho đến khi trải nghiệm với dev, hóa ra thật sự đáng thất vọng. (Tôi đã đính kèm nó bên dưới chỉ để tham khảo.)

Suy nghĩ thứ hai của tôi là title có thể là một kiểu đối tượng {tag: HeaderType; text: string;}, nhưng điều đó không tốt hơn về mặt trải nghiệm lập trình so với những gì bạn có và những gì Mike gợi ý.

Nhưng React tận dụng rất tốt tuples, và trải nghiệm phát triển khi biến title thành một tuple có vẻ ổn:

Đây là phần triển khai:

type HeaderType = "h2" | "h3" | "h4" | "h5" | "h6";
type Title = [tag: HeaderType, text: ReactNode];

interface OwnProps {
    title?: Title;
}

Sử dụng nó trên một thành phần Section:

const Section = ({ title, children }: PropsWithChildren<OwnProps>) => {
    return <section>
        {title && createElement(title[0], { children: title[1] })}
        {children}
    </section>;
};

Liên kết sân chơi


Giải pháp thành phần là như sau:

Nếu đó là các thành phần mà chúng ta đang cố gắng làm điều này, chúng ta có thể dễ dàng thực hiện nó như sau:

type HeaderType =
    | React.ReactElement<typeof ThisComponent>
    | React.ReactElement<typeof ThatComponent>
    | React.ReactElement<typeof TheOtherComponent>
    // ...

Vì vậy, chúng ta có thể tạo các thành phần H2 đến H6. Chúng ta có thể làm điều này chỉ bằng cách lặp lại chính mình:

const H2 = (props: ComponentProps<"h2">) => <h2 {...props}/>;
const H3 = (props: ComponentProps<"h3">) => <h3 {...props}/>;
// ....

...nhưng mặc dù chỉ có năm thẻ h2...h6, có lẽ chúng ta cũng muốn làm điều này cho các thẻ khác. Vì vậy, có thể là một hàm tiện ích:

const componentFor = <Tag extends keyof JSX.IntrinsicElements>(tag: Tag) =>
    (props: ComponentProps<Tag>) => React.createElement(tag, props);

Sau đó, việc định nghĩa những điều này (và có lẽ những điều khác vào lúc khác) khá đơn giản:

const H2 = componentFor("h2");
const H3 = componentFor("h3");
const H4 = componentFor("h4");
const H5 = componentFor("h5");
const H6 = componentFor("h6");

Vậy HeaderType của chúng ta là:

type HeaderType =
    | React.ReactElement<typeof H2>
    | React.ReactElement<typeof H3>
    | React.ReactElement<typeof H4>
    | React.ReactElement<typeof H5>
    | React.ReactElement<typeof H6>;

Nhưng devex không được tốt lắm, tính năng gợi ý tự động chỉ hiển thị tất cả các thành phần trong phạm vi:

Liên kết sân chơi

Câu trả lời #2

Cách tốt nhất để giải quyết vấn đề này có lẽ là quay lại một thành phần mà bạn chỉ định cho nó văn bản tiêu đề sẽ sử dụng thông qua thuộc tính title, với một thuộc tính riêng cho "cấp độ tiêu đề này là bao nhiêu", ví dụ.

import * as React from "react";

interface CardProps {
  title: string;
  level: 2 | 3 | 4 | 5 | 6;
}

const Card = function(props: CardProps) {
  const heading = React.createElement(`h${props.level}`, {
    children: props.title,
  });

  return (
    <div className="some-card-class">
      {heading}
      ...etc...
    </div>
  );
};

// TS error:
const card1 = <Card title="this is the title" level={1} />;

// Not a TS error:
const card2 = <Card title="this is the title" level={2} />;

(Mặc dù tất nhiên trong ví dụ này, các câu hỏi về mã nên được đặt ra vì tôi đã viết nó dưới dạng một thẻ, và các thẻ không nên có tiêu đề "cấp"; về ngữ nghĩa, độ sâu tiêu đề liên quan đến tài liệu mà chúng nằm trong đó, và một thẻ về cơ bản là một (nhúng) tài liệu nhỏ riêng trên một trang, vì vậy bạn thực sự chỉ muốn sử dụng h1, làm cho việc cần có thuộc tính cấp trở nên không cần thiết =)

Bài viết liên quan