Mastering Reusable Frontend Components: Best Practices & TypeScript Tips

This guide walks developers through the essential mindset, API design, type annotations, DOM properties, controlled vs. uncontrolled patterns, async service handling, hooks, consumer patterns, and documentation practices for building high‑quality, reusable React or Vue components.

Alibaba Cloud Developer
Alibaba Cloud Developer
Alibaba Cloud Developer
Mastering Reusable Frontend Components: Best Practices & TypeScript Tips

Components are the core of front‑end development; reusable components can serve thousands of developers, while project‑specific ones have limited value. To assess a front‑end engineer, look for contributions of reusable components.

Awareness

Adopt a user‑centric mindset and focus on:

Defining a clear TypeScript API.

Providing comprehensive README and mock data; avoid link‑mode development.

Avoiding side‑effect dependencies like global state unless self‑contained.

Writing clean, maintainable code for future reviewers.

Interface Design

Use explicit interfaces to improve developer experience and IDE hints.

type Size = any; // 😖 ❌
type Size = string; // 🤷🏻♀️
type Size = "small" | "medium" | "large"; // ✅

Define basic DOM props for reusable components:

export interface IProps {
  className?: string;
  style?: React.CSSProperties;
}

Include custom data attributes for analytics:

export type CommonDomProps = {
  className?: string;
  style?: React.CSSProperties;
} & Record<`data-${string}`, string>;

Type Annotations

Provide JSDoc‑style comments for each prop to enable quick navigation and generate API docs with tools like vitdoc.

export type IListProps = {
  /** Custom suffix element. Used to append element after list */
  suffix?: React.ReactNode;
  /** List column definition. This makes List act like a Table. */
  columns?: IColumn[];
  /** List dataSource. Used with renderRow */
  dataSource?: Array<Record<string, any>>;
}

Component Slots

Prefer ReactNode over string for slot props to allow any renderable content.

export type IInputProps = {
  label?: React.ReactNode; // ✅
}

Controlled vs. Uncontrolled

Define generic controlled interfaces for value handling:

export type IFormProps<T = string> = {
  value?: T;
  defaultValue?: T;
  onChange?: (value: T, ...args) => void;
}

Example for visibility control:

export type IVisibleProps = {
  /** The visible state of the component. */
  visible?: boolean;
  /** The default visible state. */
  defaultVisible?: boolean;
  /** Callback when visibility changes. */
  onVisibleChange?: (visible: boolean, ...args) => void;
}

Selection Props

export interface ISelection<T extends object = Record<string, any>> {
  mode?: 'single' | 'multiple';
  selectedRowKeys?: string[];
  defaultSelectedRowKeys?: string[];
  maxSelection?: number;
  keepSelected?: boolean;
  getProps?: (record: T, index: number) => { disabled?: boolean; [key: string]: any };
  onChange?: (selectedRowKeys: string[], records?: Array<T>, ...args: any[]) => void;
  onSelect?: (selected: boolean, record: T, records: Array<T>, ...args: any[]) => void;
  onSelectAll?: (selected: boolean, keys: string[], records: Array<T>, ...args: any[]) => void;
}

Service Request Props

Encapsulate async data fetching in a single promise‑based prop.

export type IAsyncProps = {
  requestUrl?: string;
  extParams?: any;
  beforeUpload?: (res: any) => any;
  format?: (res: any) => any;
}

Example component API:

export type ProductList = {
  total: number;
  list: Array<{ id: string; name: string; image: string; }>;
}
export type AsyncGetProductList = (pageInfo: { current: number; pageSize: number }, searchParams: { name: string; id: string }) => Promise<ProductList>;
export interface IComponentProps {
  /** Service to get product list */
  loadProduct?: AsyncGetProductList;
}

Hooks

Wrap service logic in custom hooks for reusability and UI flexibility.

export function Page() {
  const lzdUploadProps = useLzdUpload({ bu: 'seller' });
  return <Upload {...lzdUploadProps} />;
}

Consumer Pattern

Use render‑prop style consumers to expose internal state.

export type IExpandProps = {
  children?: (ctx: { isExpand: boolean }) => React.ReactNode;
}
export function Page() {
  return (
    <Expand>{({ isExpand }) => isExpand ? <Table /> : <AnotherTable />}</Expand>
  );
}

Documentation

Ensure package.json contains correct repository URL and entry fields ( main, module, types, exports).

{
  "name": "xxx-ui",
  "version": "1.0.0",
  "description": "Out-of-box UI solution for enterprise applications from B-side.",
  "author": "[email protected]",
  "exports": {
    ".": {
      "import": "./dist/esm/index.js",
      "require": "./dist/cjs/index.js"
    }
  },
  "main": "./dist/cjs/index.js",
  "module": "./dist/esm/index.js",
  "types": "./dist/cjs/index.d.ts",
  "repository": { "type": "git", "url": "[email protected]:yee94/xxx.git" }
}

Provide a concise README.md with a description, demo link, and usage examples to help users adopt the library.

Component design illustration
Component design illustration
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

VueAPI
Alibaba Cloud Developer
Written by

Alibaba Cloud Developer

Alibaba's official tech channel, featuring all of its technology innovations.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.