reactjs - Solusi sementara TypeScript untuk properti sisa di React


Diperbarui untuk TypeScript 2.1

TypeScript 2.1 sekarang mendukung spread/rest objek, jadi tidak perlu solusi sementara lagi!

Pertanyaan Asli

TypeScript mendukung atribut spread JSX yang umum digunakan di React untuk meneruskan atribut HTML dari sebuah komponen ke elemen HTML yang di-render:

interface LinkProps extends React.HTMLAttributes {
  textToDisplay: string;
}

class Link extends React.Component<LinkProps, {}> {
  public render():JSX.Element {
    return (
      <a {...this.props}>{this.props.textToDisplay}</a>
    );
  }
}

<Link textToDisplay="Search" href="http://google.com" />

Namun, React memperkenalkan peringatan jika Anda mengirimkan properti yang tidak dikenal ke elemen HTML. Contoh di atas akan menghasilkan peringatan runtime React bahwa textToDisplay adalah properti yang tidak dikenal dari <a>. Solusi yang disarankan untuk kasus seperti contoh ini adalah menggunakan properti rest objek untuk mengekstrak properti kustom Anda dan menggunakan sisa untuk atribut penyebaran (spread) JSX:

const {textToDisplay, ...htmlProps} = this.props;
return (
  <a {...htmlProps}>{textToDisplay}</a>
);

Tapi TypeScript belum mendukung sintaks ini. Saya tahu semoga suatu hari nanti kita akan bisa melakukan ini di TypeScript. (Pembaruan: TS 2.1 sekarang mendukung object spread/rest! Kenapa kamu masih membaca ini??) Sementara itu, apa saja solusi alternatifnya? Saya sedang mencari solusi yang tidak mengorbankan keamanan tipe dan ternyata cukup sulit menemukannya. Misalnya, saya bisa melakukan ini:

const customProps = ["textDoDisplay", "otherCustomProp", "etc"];
const htmlProps:HTMLAttributes = Object.assign({}, this.props);
customProps.forEach(prop => delete htmlProps[prop]);

Tapi ini memerlukan penggunaan nama properti string yang tidak divalidasi terhadap properti sebenarnya dan dengan demikian rentan terhadap kesalahan ketik dan dukungan IDE yang buruk. Apakah ada cara yang lebih baik untuk melakukan ini?

Jawaban #1

Sebenarnya ini lebih mudah daripada semua jawaban di atas. Anda hanya perlu mengikuti contoh di bawah ini:

type Props = {
  id: number,
  name: string;
  // All other props
  [x:string]: any;
}

const MyComponent:React.FC<Props> = props => {
  // Any property passed to the component will be accessible here
}

Jawaban #2

Anda mungkin tidak bisa menghindari membuat objek baru dengan subset dari properti this.props, tapi Anda bisa melakukannya dengan keamanan tipe.

Sebagai contoh:

interface LinkProps {
    textToDisplay: string;
}

const LinkPropsKeys: LinkProps = { textToDisplay: "" };

class Link extends React.Component<LinkProps & React.HTMLAttributes, {}> {
    public render(): JSX.Element {
        return (
            <a { ...this.getHtmlProps() }>{ this.props.textToDisplay }</a>
        );
    }

    private getHtmlProps(): React.HTMLAttributes {
        let htmlProps = {} as React.HTMLAttributes;

        for (let key in this.props) {
            if (!(LinkPropsKeys as any)[key]) {
                htmlProps[key] = this.props[key];
            }
        }

        return htmlProps;
    }
}

Menggunakan objek LinkPropsKeys, yang perlu sesuai dengan LinkProps, akan membantu Anda menjaga kunci antara antarmuka dan pencarian waktu berjalan tetap sinkron.

Jawaban #3

gunakan ...rest

type ButtonProps = {
    disabled: boolean;
};

function Button(props: ButtonProps): JSX.Element {
    const {disabled = false, ...rest} = props;
...
return (
    <button disabled={disabled} {...rest}>
....

Jawaban #4

Bagi mereka yang mungkin tidak segera mengerti apa yang dilakukan [x:string]: any; dalam jawaban yang diterima: Meskipun ini sangat mirip dengan sintaks array, sebenarnya ini menandai sebuah objek dengan kunci yang bertipe string dan nilai yang bertipe any. Ini disebut "Index Signature" dalam istilah TypeScript.

Namun, perhatikan juga bahwa terkadang, sebagai alternatif dan solusi yang kurang longgar terhadap tipe, sebuah pustaka yang Anda gunakan mungkin juga mengekspor tipe, sehingga Anda bisa menggunakan tipe tersebut.

Sebagai contoh, saat memperluas Tombol Ant, seseorang bisa melakukan ini:

import { ReactNode } from "react";
import { Button as AntButton } from "antd";
import { NativeButtonProps } from "antd/lib/button/button";

interface IButtonProps {
  children?: ReactNode;
}

const Button = ({
  children,
  ...rest
}: IButtonProps & NativeButtonProps): JSX.Element => {
  return <AntButton {...rest}>{children}</AntButton>;
};

export default Button;

CATATAN1: Operator ampersand (&) dalam IButtonProps & NativeButtonProps hanya melakukan "penggabungan" tipe di TypeScript. Sekarang Anda tidak kehilangan intellisense untuk properti Button Ant pada Button Anda sendiri, karena Anda tidak lagi menggunakan any. Tipe Button Ant dan IButtonProps Anda digabungkan sehingga keduanya ada.

CATATAN2: Anda mungkin juga bertanya-tanya di mana saya menemukan tipe ini. Tipe ini diekspor di sini: https://github.com/ant-design/ant-design/blob/master/components/button/button.tsx#L124 Dan juga jalur inklusinya bisa diketahui menggunakan intellisense, cukup mulai mengetik NativeButton... dan itu pasti akan disarankan kepada Anda.

Jawaban #5

React.HtmlAttributes dalam contoh di atas sekarang bersifat generik sehingga saya perlu memperluas dari React.AnchorHTMLAttributes<HTMLAnchorElement>.

Contoh :

import React from 'react';

type  AClickEvent = React.MouseEvent<HTMLAnchorElement>;

interface LinkPropTypes extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
    to: string;
    onClick?: (x: AClickEvent) => void;
}

class Link extends React.Component<LinkPropTypes> {
  public static defaultProps: LinkPropTypes = {
    to: '',
    onClick: null,
  };

private handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
   ...
    event.preventDefault();
    history.push(this.props.to);
 };

  public render() {
    const { to, children, ...props } = this.props;
    return (
      <a href={to} {...props} onClick={this.handleClick}>
        {children}
      </a>
    );
    }
}

export default Link;

Artikel terkait