reactjs - TS + React - Ar trebui ca interfețele pentru props să fie exportate?


Nu am găsit nicio informație despre asta nicăieri, dar sunt curios despre cele mai bune practici.

În proiectul meu de lucru, în prezent îmi definesc interfețele pentru props astfel:

export interface ExampleComponentProps {
  readonly firstExampleProp: string;
  readonly secondExampleProp: number
}

Exportarea interfeței cu export sau declararea membrilor ca readonly implică un cost de performanță în timpul compilării sau al execuției? Există vreun beneficiu în a face acest lucru, în afară de lizibilitate și claritate?

Răspuns #1

  • Da, export-area proprietăților este o practică bună, deoarece permite cazuri de utilizare mai avansate, cum ar fi învăluirea componentelor într-alta. Vezi exemplul de mai jos.
  • Adăugarea lui readonly la fiecare proprietate mi se pare mai mult o preferință personală. Da, proprietățile nu ar trebui niciodată suprascrise într-un component, dar în același timp acesta este un lucru destul de cunoscut și adăugarea lui readonly peste tot poate fi puțin superfluu.
import { ExampleComponent, ExampleComponentProps } from "./ExampleComponent";

type WrappedExampleComponentProps = {
  newProperty: string;
} & ExampleComponentProps; 
// I wouldn't be able to this if I weren't able to import ExampleComponentProps

export const WrappedExampleComponent = (props: WrappedExampleComponentProps ) => {
  const { newProperty, ...otherProps } = props;
  return (
      <ExampleComponent {...otherProps } />
      <SideComponent newProperty={newProperty} />
  );
};

În ceea ce privește performanța: nu există cu siguranță nicio scădere de performanță în timpul execuției, deoarece niciunul dintre constructele TypeScript nu există la rulare. La timpul de compilare, aș spune că impactul asupra performanței acestor modificări este neglijabil și este depășit de avantajele sale (de exemplu, extensibilitatea).

Răspunsul #2

Pe scurt; Da, așa cum afirmă Răspunsul lui Martin, este cea mai bună practică să exportați proprietățile (props), astfel încât orice componente wrapper să le poată folosi. Flag-ul readonly este inutil, în opinia mea, deoarece proprietățile ar trebui să fie întotdeauna readonly (explicat mai jos) și performanța nu este o problemă.

Exportare

Răspuns lung: Pentru a răspunde partea export-antă a întrebării tale, ar trebui absolut să exporți interfața props. Ia ca exemplu Material UI (MUI); README-ul convenției TypeScript de pe GitHub-ul lor afirmă:

  • exporta interfacea {ComponentName}classes din {component}Classes.ts și adaugă comentariu pentru generarea documentației API (pentru componente interne, poate să expună sau nu clasele, dar nu este nevoie de comentariu)
  • exporta interfață {ComponentName}Props
  • exportă întotdeauna interfața props (folosește interfața în loc de tip) din fișierul componentă

A doua și a treia parte spun că fiecare fișier de componentă publică pe care un dezvoltator MUI îl scrie ar trebui să exporte props-urile. Am folosit acest lucru în avantajul meu atunci când scriam componente ale căror props-uri extind props-urile unui component MUI și, de asemenea, atunci când îmi scriam propriile componente. Doar acest lucru ar trebui să fie suficient pentru a te convinge să exporți toate interfețele props. Singurul motiv fezabil să nu o faci este dacă un ghid de stil spune explicit acest lucru dintr-un anumit motiv.

Codul următor este o definiție a unui component într-un site React + TS la care lucrez momentan.

import type {ButtonProps} from "@mui/material/Button";
import { ThrottleClasses } from "@/store/throttle";

/**
 * Props structure for {@link RequestButton}
 */
export interface RequestButtonProps
  extends Omit<ButtonProps, "title" | "variant"> {
  /**
   * Whether or not the form adjacent to this element is valid.
   */
  valid: boolean;
  /**
   * The name of a value in the throttle store to compare against
   * to decide if the request can be sent.
   */
  throttle?: string;
  /**
   * The `isLoading` property returned from an RTK query or mutation.
   */
  isLoading?: boolean;
  /**
   * The `isError` property returned from an RTK query or mutation.
   */
  isError?: boolean;
  /**
   * The action phrase which is displayed on the button's tooltip.
   */
  title?: React.ReactNode;
  /**
   * The button's style type, analogous to those defined in {@link ButtonProps}.
   */
  variant?: "contained" | "text" | "outlined";
}

/**
 * A button to be used when send requests to the backend.
 * This component also listens to the throttle state in 
 * order to prevent excessive requests if necessary.
 * This can be done by dispatching a throttle action when this button is clicked,
 * and passing the corresponding {@link ThrottleClasses} property to this.
 * @props {@link FormSubmitProps}
 * @component
 */
export const RequestButton: React.FC<RequestButtonProps> = ({
  valid,
  children,
  throttle,
  isLoading,
  isError = false,
  title = children,
  variant = "contained",
  ...props
}) => {
...

Un alt motiv mai puțin cunoscut pentru exportarea interfețelor props (cel puțin eu nu aud prea mult despre asta) este generarea automată a documentației. Dacă te uiți înapoi la a doua parte a citatului MUI, se menționează de asemenea „adăugarea comentariilor pentru generarea documentației API”. Un exemplu de acest lucru poate fi văzut în codul de mai sus, unde fiecare proprietate are o scurtă explicație.

Folosesc un instrument excelent numit TypeDoc pentru generarea documentației. Ce face acesta este să analizeze exporturile și comentariile asociate în stil JSDoc, marcate prin asteriscuri duble (JSDoc este un instrument similar pentru JavaScript) într-un fișier și să genereze un site HTML static vizitabil folosindu-le. Motivul pentru care menționez acest lucru este că, pentru ca acest lucru să funcționeze, trebuie să exporți elementele pe care vrei să le documentezi, așa că ar putea fi util dacă decizi să îl folosești mai târziu. În plus, chiar dacă nu folosești acest instrument, editoarele de text precum VSCode folosesc aceste comentarii pentru a oferi context când treci cu mouse-ul peste proprietăți, chiar și în alte fișiere, ceea ce eu personal consider extrem de util atunci când nu îmi amintesc la ce servește o anumită proprietate.

Proprietăți doar pentru citire

În ceea ce privește tag-ul readonly, sunt complet de acord cu Martin; este o practică foarte proastă să reasignezi valori proprietăților (props), ceea ce face ca tag-ul readonly să fie redundant. Acest lucru este explicat bine în acest răspuns despre reasignarea valorilor proprietăților (props).

În React nu ar trebui să încerci niciodată să schimbi props. Proprietățile sunt ceea ce este transmis de componenta părinte. Dacă vrei să schimbi proprietățile, trebuie să mergi la componenta părinte și să schimbi ceea ce este transmis.

Performanță

În ceea ce privește performanța, nu are nicio suprasarcină la rulare, deoarece toate definițiile doar în TypeScript (tipuri, interfețe etc...) sunt eliminate de compilatorul TypeScript și nu sunt prezente în fișierul de ieșire. În timpul compilării, poate exista o ușoară suprasarcină, dar într-o aplicație reală aceasta nu va depăși câteva milisecunde.