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.
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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Alibaba Cloud Developer
Alibaba's official tech channel, featuring all of its technology innovations.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
