Ickerday
Ickerday

Reputation: 127

TS + React - Should props interfaces be exported?

I didn't find any info on this anywhere but I'm curious about best practices.

In my work project I'm currently defining my props interfaces as following:

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

Does export-ing the interface or readonly-ing members incur a performance overhead during compilation or runtime? Is there any benefit to doing this other than readability and explicitness?

Upvotes: 10

Views: 3486

Answers (2)

Sam McElligott
Sam McElligott

Reputation: 326

TLDR; Yes, as Martin's Answer states, it is best practice to export the props, so any wrapper components can use them. The readonly flag is unnecessary in my opinion as props should always be readonly (Explained below) and performance is not an issue.

Exporting

Long Answer: To answer the export-ing part of your question, you absolutely should export the props interface. Take Material UI (MUI) for example; the TypeScript convention README from their GitHub states:

  • export interface {ComponentName}classes from {component}Classes.ts and add comment for generating api docs (for internal components, may or may not expose classes but don't need comment)
  • export interface {ComponentName}Props
  • always export props interface (use interface over type) from the component file

The second and third part say that every public component file that a MUI developer writes should export the props. I've used this to my advantage when writing components whose props extend the props of a MUI component, and also when writing my own components. This alone should be enough to convince you to export all props' interfaces. The only feasible reason not to is if a style guide explicitly says so for some reason.

The following code is a component definition in a React + TS site I'm currently working on.

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
}) => {
...

Another lesser known reason for exporting props' interfaces (at least I don't hear much about it) is automatic documentation generation. If you look back to the second part of the MUI quote, it also mentions "adding comments for generating api docs". An example of this can be seen in the code above, where each property has a short explanation.

I use an excellent tool called TypeDoc for generating documentation. What it does is look at the exports and accompanying JSDoc style comment, denoted by the double asterisk (JSDoc is a similar tool for JavaScript) in a file and generates a browsable static HTML site using them. The reason I mention this is that for this to work, you must export the items you want to document, so it may come in handy if you decide to use it later down the line. Plus, even if you don't use this tool, text editors like VSCode use these comments to provide context when you hover over the properties, even in other files, which I personally find extremely useful when I can't remember what a certain property is for.

VSCode Example

Readonly properties

Regarding the readonly tag, I completely agree with Martin; it is very bad practice to reassign to props, which makes the readonly tag redundant. This is explained well in this answer about reassigning to props.

In React you should never try to change props. Properties are what is being passed from the parent component. If you want to change properties, you have to go to the parent component and change what is being passed.

Performance

Regarding performance, it has no overhead at runtime, as all TypeScript only definitions (types, interfaces etc...) are removed by the TypeScript compiler and not present in the output file. During compilation, there may be some slight overhead, but in a real world application this will amount to no more than a couple of milliseconds.

Upvotes: 5

Martin Devillers
Martin Devillers

Reputation: 18002

  • Yes, export-ing props is a good practice, because it allows more advanced use-cases like wrapping components in another. See the below example.
  • Adding readonly to every property I feel is more a personal preference. Yes, props should never be overwritten within a component, but at the same time this is pretty common knowledge and adding readonly everywhere may be a little superfluous.
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} />
  );
};

In terms of performance: there's definitely no performance hit during runtime, because none of the TypeScript constructs exist at runtime. At compile-time, I'd say the performance hit of these changes are negligible and are outweighed by its advantages (e.g. extensibility).

Upvotes: 9

Related Questions