🌐 Detecting your location…
📢 Advertisement — Configure AdSense in Appearance → Customize → AdSense Settings

TypeScript React Patterns 2026: componentes genéricos, compostos e ganchos

⏱️6 min read  ·  1,123 words

Os padrões TypeScript React em 2026 vão além da digitação básica de componentes. Componentes compostos, adereços de renderização, ganchos personalizados com tipos de retorno complexos e os novos padrões de componentes de servidor do React 19 se beneficiam do sistema de tipos do TypeScript. Este guia cobre os padrões que os desenvolvedores seniores do React usam diariamente.

Componentes genéricos

// Generic list component — renders any data type
interface ListProps<T> {
  items: T[];
  renderItem: (item: T, index: number) => React.ReactNode;
  keyExtractor: (item: T) => string | number;
  emptyComponent?: React.ReactNode;
  className?: string;
}

function List<T>({
  items,
  renderItem,
  keyExtractor,
  emptyComponent = <p>No items</p>,
  className,
}: ListProps<T>) {
  if (items.length === 0) return <>{emptyComponent}</>;

  return (
    <ul className={className}>
      {items.map((item, index) => (
        <li key={keyExtractor(item)}>
          {renderItem(item, index)}
        </li>
      ))}
    </ul>
  );
}

// Usage — fully type-safe
interface User { id: number; name: string; email: string; }

<List<User>
  items={users}
  keyExtractor={u => u.id}
  renderItem={user => (
    <UserCard user={user} />
  )}
/>

Componentes Polimórficos

// A component that can render as any HTML element or component
type AsProp<T extends React.ElementType> = { as?: T };
type PropsToOmit<T extends React.ElementType, P> = keyof (AsProp<T> & P);

type PolymorphicComponentProp<T extends React.ElementType, Props = {}> = React.PropsWithChildren<
  Props & AsProp<T>
> &
  Omit<React.ComponentPropsWithoutRef<T>, PropsToOmit<T, Props>>;

type PolymorphicRef<T extends React.ElementType> = React.ComponentPropsWithRef<T>["ref"];

type PolymorphicComponentPropWithRef<T extends React.ElementType, Props = {}> =
  PolymorphicComponentProp<T, Props> & { ref?: PolymorphicRef<T> };

// Text component that renders as any element
interface TextProps {
  variant?: "h1" | "h2" | "h3" | "body" | "caption";
  color?: "primary" | "secondary" | "muted";
}

const variantMap = { h1: "h1", h2: "h2", h3: "h3", body: "p", caption: "span" } as const;

function Text<T extends React.ElementType = "span">({
  as,
  variant = "body",
  color = "primary",
  children,
  className,
  ...props
}: PolymorphicComponentProp<T, TextProps>) {
  const Component = as ?? variantMap[variant];
  return (
    <Component
      className={`text-${variant} text-${color} ${className ?? ""}`}
      {...props}
    >
      {children}
    </Component>
  );
}

// Usage
<Text variant="h1">Heading 1</Text>                 // renders <h1>
<Text as="label" htmlFor="email">Email</Text>        // renders <label>
<Text as={Link} to="/about" variant="body">About</Text>  // renders <Link>

Padrão de Componentes Compostos

import { createContext, useContext, useState } from 'react';

// Accordion compound component
interface AccordionContextValue {
  openItem: string | null;
  setOpenItem: (id: string | null) => void;
}

const AccordionContext = createContext<AccordionContextValue | null>(null);

function useAccordion() {
  const ctx = useContext(AccordionContext);
  if (!ctx) throw new Error('Must be used within <Accordion>');
  return ctx;
}

function Accordion({ children, defaultOpen = null }: {
  children: React.ReactNode;
  defaultOpen?: string | null;
}) {
  const [openItem, setOpenItem] = useState<string | null>(defaultOpen);
  return (
    <AccordionContext.Provider value={{ openItem, setOpenItem }}>
      <div className="accordion">{children}</div>
    </AccordionContext.Provider>
  );
}

function AccordionItem({ id, title, children }: {
  id: string;
  title: string;
  children: React.ReactNode;
}) {
  const { openItem, setOpenItem } = useAccordion();
  const isOpen = openItem === id;

  return (
    <div className={`accordion-item ${isOpen ? 'open' : ''}`}>
      <button onClick={() => setOpenItem(isOpen ? null : id)}>
        {title}
        <span>{isOpen ? '▲' : '▼'}</span>
      </button>
      {isOpen && <div className="accordion-content">{children}</div>}
    </div>
  );
}

// Attach as namespaced components
Accordion.Item = AccordionItem;

// Usage
<Accordion defaultOpen="faq-1">
  <Accordion.Item id="faq-1" title="What is TypeScript?">
    TypeScript is a typed superset of JavaScript...
  </Accordion.Item>
  <Accordion.Item id="faq-2" title="Why use React?">
    React provides a declarative way to build UIs...
  </Accordion.Item>
</Accordion>

Ganchos personalizados digitados

import { useCallback, useReducer } from 'react';

// Async state management hook
type AsyncState<T> =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success'; data: T }
  | { status: 'error'; error: Error };

type AsyncAction<T> =
  | { type: 'start' }
  | { type: 'success'; data: T }
  | { type: 'error'; error: Error }
  | { type: 'reset' };

function asyncReducer<T>(state: AsyncState<T>, action: AsyncAction<T>): AsyncState<T> {
  switch (action.type) {
    case 'start': return { status: 'loading' };
    case 'success': return { status: 'success', data: action.data };
    case 'error': return { status: 'error', error: action.error };
    case 'reset': return { status: 'idle' };
  }
}

function useAsync<T, Args extends unknown[]>(
  fn: (...args: Args) => Promise<T>
) {
  const [state, dispatch] = useReducer(
    asyncReducer as React.Reducer<AsyncState<T>, AsyncAction<T>>,
    { status: 'idle' } as AsyncState<T>
  );

  const execute = useCallback(
    async (...args: Args) => {
      dispatch({ type: 'start' });
      try {
        const data = await fn(...args);
        dispatch({ type: 'success', data });
        return data;
      } catch (err) {
        const error = err instanceof Error ? err : new Error(String(err));
        dispatch({ type: 'error', error });
        throw error;
      }
    },
    [fn]
  );

  const reset = useCallback(() => dispatch({ type: 'reset' }), []);

  return { ...state, execute, reset };
}

// Usage with full type inference
function UserProfile({ userId }: { userId: number }) {
  const { status, data, error, execute } = useAsync(fetchUser);

  useEffect(() => { execute(userId); }, [userId]);

  if (status === 'loading') return <Spinner />;
  if (status === 'error') return <Error message={error.message} />;
  if (status === 'success') return <Profile user={data} />;  // data is User
  return null;
}

Componentes do servidor React 19 com TypeScript

// Server Component — no 'use client', runs on server
// app/blog/[slug]/page.tsx
import type { Metadata } from 'next';

interface Props {
  params: Promise<{ slug: string }>;
  searchParams: Promise<{ view?: string }>;
}

// Type-safe metadata generation
export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { slug } = await params;
  const post = await fetchPost(slug);

  return {
    title: post.title,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      description: post.excerpt,
      images: [{ url: post.coverImage }],
    },
  };
}

export default async function PostPage({ params, searchParams }: Props) {
  const { slug } = await params;
  const { view = 'full' } = await searchParams;

  const post = await fetchPost(slug);
  if (!post) notFound();

  return (
    <article>
      <h1>{post.title}</h1>
      {view === 'full' ? (
        <div dangerouslySetInnerHTML={{ __html: post.contentHtml }} />
      ) : (
        <p>{post.excerpt}</p>
      )}
      <Suspense fallback={<CommentSkeleton />}>
        <Comments postId={post.id} />
      </Suspense>
    </article>
  );
}

// Client component with typed props
'use client';

interface CommentFormProps {
  postId: number;
  onSubmit: (comment: { text: string; author: string }) => Promise<void>;
}

function CommentForm({ postId, onSubmit }: CommentFormProps) {
  // ...client-side interactivity
}

Os padrões TypeScript React em 2026 estão maduros – componentes genéricos eliminam código duplicado, componentes polimórficos criam APIs flexíveis, componentes compostos substituem perfuração de suporte complexa e ganchos personalizados digitados tornam os padrões assíncronos seguros. A combinação dos componentes de servidor do React 19 com TypeScript cria aplicativos full-stack totalmente seguros para tipos.

✍️ Leave a Comment

Your email address will not be published. Required fields are marked *

🌐 Read in:🇬🇧 English🇩🇪 Deutsch🇧🇷 Português🇸🇦 العربية🇮🇳 हिन्दी🇧🇩 বাংলা