//
Busque em todas as páginas da documentação
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Estas receitas de skill são projetadas para Claude Code, mas também funcionam com outros agentes de codificação de IA que suportam arquivos de skill/instrução.
O conteúdo completo do SKILL.md que você pode copiar para .claude/skills/typescript-react-patterns/SKILL.md:
---
name: typescript-react-patterns
description: "Padrões avançados de TypeScript para componentes React e Next.js. Use quando solicitado para: tipar este componente, ajuda com TypeScript, componente genérico, tipagem de props, uniões discriminadas, tipos utilitários, tipagem de hooks, tipagem de server components."
allowed-tools: "Read, Write, Edit, Glob, Grep, Bash(npm:*), Bash(npx:*), Agent"
---
# Padrões React com TypeScript
Você é um especialista em TypeScript com foco em padrões React e Next.js. Forneça os tipos mais precisos e estritos para cada cenário.
## Princípios Fundamentais
1. **Prefira tipos estreitos a tipos amplos** - Use literais de string em vez de `string`, objetos específicos em vez de `Record`
2. **Use uniões discriminadas para renderização condicional** - Nunca use optional chaining para percorrer props de variantes
3. **Derive tipos a partir de dados** - Use `typeof`, `ReturnType` e inferência Zod em vez de declarações de tipo manuais
4. **Modo estrito sempre** - Habilite `strict: true` e `noUncheckedIndexedAccess: true`
## Biblioteca de Padrões
### 1. Componente Polimórfico (prop 'as')
```tsx
type PolymorphicProps<E extends React.ElementType> = \{
as?: E;
children: React.ReactNode;
\} & Omit<React.ComponentPropsWithoutRef<E>, "as" | "children">;
function Box<E extends React.ElementType = "div">(\{
as,
children,
...props
\}: PolymorphicProps<E>) \{
const Component = as ?? "div";
return <Component \{...props\}>\{children\}</Component>;
\}
// Uso - totalmente tipado
<Box as="a" href="/about">Link</Box> // href é válido
<Box as="button" onClick=\{handleClick\}>Go</Box> // onClick é válido// Em vez de props opcionais que dependem umas das outras:
// RUIM
type BadProps = \{ variant?: "link"; href?: string; onClick?: () => void \};
// BOM - união discriminada
type ButtonProps =
| \{ variant: "button"; onClick: () => void; href?: never \}
| \{ variant: "link"; href: string; onClick?: never \}
| \{ variant: "submit"; onClick?: never; href?: never \};
function Action(props: ButtonProps) \{
switch (props.variant) \{
case "button":
return <button onClick=\{props.onClick\}>Click</button>;
case "link":
return <a href=\{props.href\}>Link</a>;
case "submit":
return <button type="submit">Submit</button>;
\}
\}type ListProps<T> = \{
items: T[];
renderItem: (item: T, index: number) => React.ReactNode;
keyExtractor: (item: T) => string;
emptyMessage?: string;
\};
function List<T>(\{ items, renderItem, keyExtractor, emptyMessage \}: ListProps<T>) \{
if (items.length === 0) \{
return <p>\{emptyMessage ?? "No items"\}</p>;
\}
return (
<ul>
\{items.map((item, i) => (
<li key=\{keyExtractor(item)\}>\{renderItem(item, i)\}</li>
))\}
</ul>
);
\}
// Uso - T é inferido a partir de items
<List
items=\{users\}
renderItem=\{(user) => <span>\{user.name\}</span>\} // user é tipado como User
keyExtractor=\{(user) => user.id\}
/>import type \{ ComponentProps, ComponentRef \} from "react";
// Extrai props de qualquer componente
type InputProps = ComponentProps<"input">;
type ButtonProps = ComponentProps<typeof Button>;
// Extrai o tipo de ref
type InputRef = ComponentRef<"input">; // HTMLInputElement
// Seleciona props específicas
type PartialInputProps = Pick<ComponentProps<"input">, "value" | "onChange" | "placeholder">;// Retorna uma tupla (como useState)
function useToggle(initial = false) \{
const [value, setValue] = useState(initial);
const toggle = useCallback(() => setValue((v) => !v), []);
const setTrue = useCallback(() => setValue(true), []);
const setFalse = useCallback(() => setValue(false), []);
return [value, \{ toggle, setTrue, setFalse \}] as const;
\}
// Tipo de retorno: readonly [boolean, \{ toggle, setTrue, setFalse \}]
// Hook genérico
function useLocalStorage<T>(key: string, initialValue: T) \{
const [stored, setStored] = useState<T>(() => \{
if (typeof window === "undefined") return initialValue;
const item = window.localStorage.getItem(key);
return item ? (JSON.parse(item) as T) : initialValue;
\});
const setValue = useCallback(
(value: T | ((prev: T) => T)) => \{
setStored((prev) => \{
const next = value instanceof Function ? value(prev) : value;
window.localStorage.setItem(key, JSON.stringify(next));
return next;
\});
\},
[key]
);
return [stored, setValue] as const;
\}// Props do Server Component - params e searchParams são Promises no Next.js 15+
type PageProps = \{
params: Promise<\{ slug: string \}>;
searchParams: Promise<\{ [key: string]: string | string[] | undefined \}>;
\};
export default async function Page(\{ params, searchParams \}: PageProps) \{
const \{ slug \} = await params;
const \{ q \} = await searchParams;
// ...
\}
// Props do Layout
type LayoutProps = \{
children: React.ReactNode;
params: Promise<\{ slug: string \}>;
\};
export default async function Layout(\{ children, params \}: LayoutProps) \{
const \{ slug \} = await params;
return <div>\{children\}</div>;
\}// Server Action com estado tipado
type FormState = \{
errors?: \{
name?: string[];
email?: string[];
\};
message?: string;
success: boolean;
\};
export async function createUser(
prevState: FormState,
formData: FormData
): Promise<FormState> \{
// valida e processa
return \{ success: true, message: "Usuário criado" \};
\}
// Componente cliente usando a action
"use client";
import \{ useActionState \} from "react";
function Form() \{
const [state, action, pending] = useActionState(createUser, \{
success: false,
\});
// state é tipado como FormState
\}// Tipos de evento específicos em vez de React.SyntheticEvent genérico
function Form() \{
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => \{
console.log(e.target.value); // string
\};
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => \{
e.preventDefault();
const formData = new FormData(e.currentTarget);
\};
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => \{
if (e.key === "Enter") submit();
\};
return (
<form onSubmit=\{handleSubmit\}>
<input onChange=\{handleChange\} onKeyDown=\{handleKeyDown\} />
</form>
);
\}// Torna chaves específicas obrigatórias
type WithRequired<T, K extends keyof T> = T & Required<Pick<T, K>>;
// Torna chaves específicas opcionais
type WithOptional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
// Partial profundo
type DeepPartial<T> = \{
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
\};
// Omit estrito (gera erro se a chave não existir)
type StrictOmit<T, K extends keyof T> = Omit<T, K>;
// Extrai membro de união
type ExtractMember<T, U> = T extends U ? T : never;// Função type guard
function isError(result: SuccessResult | ErrorResult): result is ErrorResult \{
return "error" in result;
\}
// Switch exaustivo
function assertNever(value: never): never \{
throw new Error(`Valor inesperado: ${value}`);
\}
function handleStatus(status: "idle" | "loading" | "success" | "error") \{
switch (status) \{
case "idle": return null;
case "loading": return <Spinner />;
case "success": return <Done />;
case "error": return <Error />;
default: return assertNever(status); // erro de compilação se o case for perdido
\}
\}any - Use unknown e estreite o tipo, ou use um genéricoas para asserções de tipo - Use type guards ou uniões discriminadassatisfies em vez de as const quando precisar de inferência e validação
## Exemplo de Trabalho
### Exemplo 1: Usuário pergunta "Como eu tipa um componente que pode ser um botão ou um link?"
**Prompt do usuário:** "Preciso de um componente Button que renderize como um link quando href for fornecido."
**A resposta guiada pela skill produziria:**
```tsx
type ButtonBaseProps = {
children: React.ReactNode;
size?: "sm" | "md" | "lg";
variant?: "primary" | "secondary" | "ghost";
};
type ButtonAsButton = ButtonBaseProps &
Omit<React.ComponentPropsWithoutRef<"button">, keyof ButtonBaseProps> & {
href?: never;
};
type ButtonAsLink = ButtonBaseProps &
Omit<React.ComponentPropsWithoutRef<"a">, keyof ButtonBaseProps> & {
href: string;
};
type ButtonProps = ButtonAsButton | ButtonAsLink;
function Button(props: ButtonProps) {
if (props.href !== undefined) {
const { size, variant, ...rest } = props;
return <a className={getStyles(size, variant)} {...rest} />;
}
const { size, variant, ...rest } = props;
return <button className={getStyles(size, variant)} {...rest} />;
}
A resposta guiada pela skill usaria inferência Zod:
import { z } from "zod";
const UserSchema = z.object({
id: z.string().uuid(),
name: z.string(),
email: z.string().email(),
role: z.enum(["admin", "user", "moderator"]),
});
type User = z.infer<typeof UserSchema>;
// Agora o tipo User é derivado do schema - única fonte de verdadeEsta skill fornece ao Claude uma biblioteca abrangente de padrões cobrindo:
any, sem as)tsconfigmkdir -p .claude/skills/typescript-react-patterns
# Cole o conteúdo da Receita em .claude/skills/typescript-react-patterns/SKILL.mdComponentPropsWithoutRef por padrão. Use ComponentPropsWithRef apenas quando precisar explicitamente encaminhar refs.memo em uma asserção de tipo ou use uma estratégia de memoização diferente.satisfies não estreita o tipo - satisfies valida, mas a variável retém seu tipo inferido, não o tipo verificado.| Abordagem | Quando Usar |
|---|---|
| Tipos JSDoc | Projetos que não podem adotar TypeScript |
| io-ts | Validação em tempo de execução com integração fp-ts |
| Valibot | Alternativa de bundle menor para Zod |
| ArkType | Validação de schema mais rápida com sintaxe nativa TypeScript |
string)z.infer, ReturnType, typeof)strict: true e noUncheckedIndexedAccess: true)type ButtonProps =
| { variant: "button"; onClick: () => void; href?: never }
| { variant: "link"; href: string; onClick?: never }
| { variant: "submit"; onClick?: never; href?: never };never)href para uma variante "button"type ListProps<T> = {
items: T[];
renderItem: (item: T, index: number) => React.ReactNode;
keyExtractor: (item: T) => string;
};
function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
return (
<ul>
{items.map((item, i) => (
<li key={keyExtractor(item)}>{renderItem(item, i)}</li>
))}
</ul>
);
}T é inferido automaticamente a partir do array items passado pelo chamadortype PageProps = {
params: Promise<{ slug: string }>;
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
};
export default async function Page({ params, searchParams }: PageProps) {
const { slug } = await params;
const { q } = await searchParams;
}params quanto searchParams são tipos Promise no Next.js 15+ e devem ser aguardados (awaited)React.memoT não é preservado através da fronteira do memomemo ou encontre uma estratégia de memoização alternativaComponentPropsWithoutRef<"input"> -- extrai props sem ref (use por padrão)ComponentPropsWithRef<"input"> -- inclui o tipo de refComponentPropsWithRef apenas quando precisar explicitamente encaminhar refstype PolymorphicProps<E extends React.ElementType> = {
as?: E;
children: React.ReactNode;
} & Omit<React.ComponentPropsWithoutRef<E>, "as" | "children">;
function Box<E extends React.ElementType = "div">({
as, children, ...props
}: PolymorphicProps<E>) {
const Component = as ?? "div";
return <Component {...props}>{children}</Component>;
}ashref é válido quando as="a")WithRequired<T, K> -- torna chaves específicas obrigatóriasWithOptional<T, K> -- torna chaves específicas opcionaisDeepPartial<T> -- torna recursivamente todas as chaves opcionaisStrictOmit<T, K> -- Omit que gera erro se a chave não existirExtractMember<T, U> -- extrai um membro específico de uma uniãosatisfies valida que um valor corresponde a um tipo, mas retém o tipo inferido (não estreita)as const torna o valor profundamente somente leitura (readonly) com tipos literaissatisfies quando precisar de inferência e validaçãotype FormState = {
errors?: { name?: string[]; email?: string[] };
message?: string;
success: boolean;
};
export async function createUser(
prevState: FormState,
formData: FormData
): Promise<FormState> {
return { success: true, message: "Usuário criado" };
}prevState (tipado como FormState) e formData (FormData)Promise<FormState>, correspondendo à forma do estado inicialany desabilita toda a verificação de tipo e permite que bugs passem despercebidosunknown e estreite com type guards em vez de anyas contornam o verificador de tipo e podem mascarar errosRevisado por Chris St. John·Última atualização: 10 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥