Frontend React Best Practices

sergiodxa/agent-skills/skills/frontend-react-best-practices

作者 sergiodxa40e21b46189d無授權條款90 個星標收錄於 2026年10月8日更新於 2026年10月8日儲存庫8 個月前更新

React performance optimization guidelines. Use when writing, reviewing, or refactoring React components to ensure optimal rendering and bundle patterns. Triggers on tasks involving React components, hooks, memoization, or bundle optimization.

AI 產生的概覽

React 效能與組合最佳實務指南,涵蓋打包體積、重新渲染、渲染、Hooks 與用戶端模式。

功能
此技能提供 33 條 React 最佳實務規則,分為六個類別:打包體積最佳化、重新渲染最佳化、渲染效能、用戶端模式、Hooks 與組合模式。每條規則都是獨立的參考檔案,包含簡短說明以及展示不建議與建議做法的程式碼範例。套用後會產出指引與重構後的元件模式,而不是產生檔案或指令碼。
適用情境
適用於撰寫新的 React 元件、審查 React 程式碼的效能問題、重構現有元件或最佳化打包體積時。在涉及 Hooks、狀態、記憶化與元件組合時也同樣適用。
執行需求
不需要指令碼或執行階段相依性,屬於純指示型技能。範例中提及 React、TypeScript 以及 SVGO 等選用工具,但閱讀這些指引不需要安裝任何項目。

React Best Practices

Performance optimization and composition patterns for React components. Contains 33 rules across 6 categories focused on reducing re-renders, optimizing bundles, component composition, and avoiding common React pitfalls.

When to Apply

Reference these guidelines when:

  • Writing new React components
  • Reviewing code for performance issues
  • Refactoring existing React code
  • Optimizing bundle size
  • Working with hooks and state

Rules Summary

Bundle Size Optimization (CRITICAL)

bundle-barrel-imports - @rules/bundle-barrel-imports.md

Import directly from source, avoid barrel files.

tsx
// Bad: loads entire library (200-800ms)import { Check, X } from "lucide-react";
// Good: loads only what you needimport Check from "lucide-react/dist/esm/icons/check";import X from "lucide-react/dist/esm/icons/x";
bundle-conditional - @rules/bundle-conditional.md

Load modules only when feature is activated.

tsx
useEffect(() => {  if (enabled && typeof window !== "undefined") {    import("./heavy-module").then((mod) => setModule(mod));  }}, [enabled]);
bundle-preload - @rules/bundle-preload.md

Preload on hover/focus for perceived speed.

tsx
<button  onMouseEnter={() => import("./editor")}  onFocus={() => import("./editor")}  onClick={openEditor}>  Open Editor</button>

Re-render Optimization (MEDIUM)

rerender-functional-setstate - @rules/rerender-functional-setstate.md

Use functional setState for stable callbacks.

tsx
// Bad: stale closure risk, recreates on items changeconst addItem = useCallback(  (item) => {    setItems([...items, item]);  },  [items],);
// Good: always uses latest state, stable referenceconst addItem = useCallback((item) => {  setItems((curr) => [...curr, item]);}, []);
rerender-derived-state-no-effect - @rules/rerender-derived-state-no-effect.md

Derive state during render, not in effects.

tsx
// Bad: extra state and effect, extra renderconst [fullName, setFullName] = useState("");useEffect(() => {  setFullName(firstName + " " + lastName);}, [firstName, lastName]);
// Good: derived directly during renderconst fullName = firstName + " " + lastName;
rerender-lazy-state-init - @rules/rerender-lazy-state-init.md

Pass function to useState for expensive initial values.

tsx
// Bad: runs expensiveComputation() on every renderconst [data] = useState(expensiveComputation());
// Good: runs only on initial renderconst [data] = useState(() => expensiveComputation());
rerender-dependencies - @rules/rerender-dependencies.md

Use primitive dependencies in effects.

tsx
// Bad: runs on any user field changeuseEffect(() => {  console.log(user.id);}, [user]);
// Good: runs only when id changesuseEffect(() => {  console.log(user.id);}, [user.id]);
rerender-derived-state - @rules/rerender-derived-state.md

Subscribe to derived booleans, not raw values.

tsx
// Bad: re-renders on every pixel changeconst width = useWindowWidth();const isMobile = width < 768;
// Good: re-renders only when boolean changesconst isMobile = useMediaQuery("(max-width: 767px)");
rerender-memo - @rules/rerender-memo.md

Extract expensive work into memoized components.

tsx
// Good: skips computation when loadingconst UserAvatar = memo(function UserAvatar({ user }) {  let id = useMemo(() => computeAvatarId(user), [user]);  return <Avatar id={id} />;});
function Profile({ user, loading }) {  if (loading) return <Skeleton />;  return <UserAvatar user={user} />;}
rerender-memo-with-default-value - @rules/rerender-memo-with-default-value.md

Hoist default non-primitive props to constants.

tsx
// Bad: breaks memoization (new function each render)const Button = memo(({ onClick = () => {} }) => ...)
// Good: stable default valueconst NOOP = () => {}const Button = memo(({ onClick = NOOP }) => ...)
rerender-simple-expression-in-memo - @rules/rerender-simple-expression-in-memo.md

Don't wrap simple primitive expressions in useMemo.

tsx
// Bad: useMemo overhead > expression costconst isLoading = useMemo(() => a.loading || b.loading, [a.loading, b.loading]);
// Good: just compute itconst isLoading = a.loading || b.loading;
rerender-move-effect-to-event - @rules/rerender-move-effect-to-event.md

Put interaction logic in event handlers, not effects.

tsx
// Bad: effect re-runs on theme changeuseEffect(() => {  if (submitted) post("/api/register");}, [submitted, theme]);
// Good: in handlerconst handleSubmit = () => post("/api/register");
rerender-transitions - @rules/rerender-transitions.md

Use startTransition for non-urgent updates.

tsx
// Good: non-blocking scroll trackingconst handler = () => {  startTransition(() => setScrollY(window.scrollY));};
rerender-use-ref-transient-values - @rules/rerender-use-ref-transient-values.md

Use refs for transient frequent values.

tsx
// Good: no re-render, direct DOM updateconst lastXRef = useRef(0);const dotRef = useRef<HTMLDivElement>(null);
useEffect(() => {  let onMove = (e) => {    lastXRef.current = e.clientX;    dotRef.current?.style.transform = `translateX(${e.clientX}px)`;  };  window.addEventListener("mousemove", onMove);  return () => window.removeEventListener("mousemove", onMove);}, []);

Rendering Performance (MEDIUM)

rendering-conditional-render - @rules/rendering-conditional-render.md

Use ternary, not && for conditionals with numbers.

tsx
// Bad: renders "0" when count is 0{  count && <Badge>{count}</Badge>;}
// Good: renders nothing when count is 0{  count > 0 ? <Badge>{count}</Badge> : null;}
rendering-hoist-jsx - @rules/rendering-hoist-jsx.md

Extract static JSX outside components.

tsx
// Good: reuses same element, especially for large SVGsconst skeleton = <div className="animate-pulse h-20 bg-gray-200" />;
function Container({ loading }) {  return loading ? skeleton : <Content />;}
rendering-content-visibility - @rules/rendering-content-visibility.md

Use content-visibility for long lists.

css
.list-item {  content-visibility: auto;  contain-intrinsic-size: 0 80px;}
rendering-animate-svg-wrapper - @rules/rendering-animate-svg-wrapper.md

Animate wrapper div, not SVG element (for GPU acceleration).

tsx
// Good: hardware accelerated<div className="animate-spin">  <svg>...</svg></div>
rendering-svg-precision - @rules/rendering-svg-precision.md

Reduce SVG coordinate precision with SVGO.

bash
npx svgo --precision=1 --multipass icon.svg
rendering-hydration-no-flicker - @rules/rendering-hydration-no-flicker.md

Use inline script for client-only data to prevent flicker.

tsx
<div id="theme-wrapper">{children}</div><script dangerouslySetInnerHTML={{ __html: `  var theme = localStorage.getItem('theme') || 'light';  document.getElementById('theme-wrapper').className = theme;` }} />
rendering-hydration-suppress-warning - @rules/rendering-hydration-suppress-warning.md

Suppress expected hydration mismatches.

tsx
<span suppressHydrationWarning>{new Date().toLocaleString()}</span>
rendering-client-only - @rules/rendering-client-only.md

Render browser-only components with ClientOnly and a fallback.

tsx
<ClientOnly fallback={<Skeleton />}>  {() => <Map />}</ClientOnly>
rendering-use-hydrated - @rules/rendering-use-hydrated.md

Use useHydrated for SSR/CSR divergence.

tsx
let hydrated = useHydrated();return hydrated ? <Widget /> : <Skeleton />;
rendering-usetransition-loading - @rules/rendering-usetransition-loading.md

Prefer useTransition over manual loading states.

tsx
const [isPending, startTransition] = useTransition();
let handleSearch = (value) => {  startTransition(async () => {    let data = await fetchResults(value);    setResults(data);  });};
fault-tolerant-error-boundaries - @rules/fault-tolerant-error-boundaries.md

Place error boundaries at feature boundaries.

tsx
<ErrorBoundary fallback={<SidebarError />}>  <Sidebar /></ErrorBoundary>

Client Patterns (MEDIUM)

client-passive-event-listeners - @rules/client-passive-event-listeners.md

Use passive listeners for scroll/touch.

tsx
document.addEventListener("wheel", handler, { passive: true });document.addEventListener("touchstart", handler, { passive: true });
client-localstorage-schema - @rules/client-localstorage-schema.md

Version and minimize localStorage data.

typescript
const VERSION = "v2";
function saveConfig(config: Config) {  try {    localStorage.setItem(`config:${VERSION}`, JSON.stringify(config));  } catch {} // Handle incognito/quota exceeded}

Hooks (HIGH)

hooks-limit-useeffect - @rules/hooks-limit-useeffect.md

Use useEffect only when absolutely necessary. Prefer derived state or event handlers.

tsx
// Bad: useEffect to derive statelet [filtered, setFiltered] = useState(items);useEffect(() => {  setFiltered(items.filter((i) => i.active));}, [items]);
// Good: derive during renderlet filtered = items.filter((i) => i.active);
// Good: useMemo if expensivelet filtered = useMemo(() => items.filter((i) => i.active), [items]);
hooks-useeffect-named-functions - @rules/hooks-useeffect-named-functions.md

Use named function declarations in useEffect for better debugging and self-documentation.

tsx
// Bad: anonymous arrow functionuseEffect(() => {  document.title = title;}, [title]);
// Good: named functionuseEffect(  function syncDocumentTitle() {    document.title = title;  },  [title],);
// Good: also name cleanup functionsuseEffect(function subscribeToOnlineStatus() {  window.addEventListener("online", handleOnline);  return function unsubscribeFromOnlineStatus() {    window.removeEventListener("online", handleOnline);  };}, []);

Composition Patterns (HIGH)

composition-avoid-boolean-props - @rules/composition-avoid-boolean-props.md

Don't add boolean props to customize behavior. Use composition instead.

tsx
// Bad: boolean prop explosion<Composer isThread isEditing={false} showAttachments />
// Good: explicit variants<ThreadComposer channelId="abc" /><EditComposer messageId="xyz" />
composition-compound-components - @rules/composition-compound-components.md

Structure complex components as compound components with shared context.

tsx
// Good: compound components<Composer.Provider state={state} actions={actions}>  <Composer.Frame>    <Composer.Input />    <Composer.Footer>      <Composer.Submit />    </Composer.Footer>  </Composer.Frame></Composer.Provider>
composition-state-provider - @rules/composition-state-provider.md

Lift state into provider components for cross-component access.

tsx
// Good: state in provider, accessible anywhere inside<ForwardMessageProvider>  <Dialog>    <Composer.Input />    <MessagePreview /> {/* Can read state */}    <ForwardButton /> {/* Can call submit */}  </Dialog></ForwardMessageProvider>
composition-explicit-variants - @rules/composition-explicit-variants.md

Create explicit variant components instead of prop combinations.

tsx
// Good: self-documenting variantsfunction ThreadComposer({ channelId }) {  return (    <ThreadProvider channelId={channelId}>      <Composer.Frame>        <Composer.Input />        <AlsoSendToChannelField />        <Composer.Submit />      </Composer.Frame>    </ThreadProvider>  );}
composition-children-over-render-props - @rules/composition-children-over-render-props.md

Prefer children for composition. Use render props only when passing data back.

tsx
// Good: children for structure<Card>  <Card.Header>Title</Card.Header>  <Card.Body>Content</Card.Body></Card>
// OK: render props when passing data<List renderItem={({ item }) => <Item {...item} />} />
composition-avoid-overabstraction - @rules/composition-avoid-overabstraction.md

Avoid rigid configuration props; prefer composable children APIs.

tsx
<Select value="abc" onChange={...}>  <Option value="abc">ABC</Option>  <Option value="xyz">XYZ</Option></Select>
composition-typescript-namespaces - @rules/composition-typescript-namespaces.md

Use TypeScript namespaces to combine component and its types for single-import access.

tsx
// components/button.tsxexport namespace Button {  export type Variant = "solid" | "ghost" | "outline";  export interface Props {    variant?: Variant;    children: React.ReactNode;  }}
export function Button({ variant = "solid", children }: Button.Props) {  // ...}
// Usage: single importimport { Button } from "~/components/button";
<Button variant="ghost">Click</Button>function wrap(props: Button.Props) { ... }

Important: Namespaces should only contain types, never runtime code.

來源與署名

來源:sergiodxa/agent-skills位於skills/frontend-react-best-practices提交40e21b4

授權條款: 無授權條款

內容歸原作者所有。SourceWeft 從公開儲存庫中收錄這些內容。

檢舉或申請下架