Die TypeScript-React-Muster im Jahr 2026 gehen über die grundlegende Komponententypisierung hinaus. Zusammengesetzte Komponenten, Render-Requisiten, benutzerdefinierte Hooks mit komplexen Rückgabetypen und die neuen Serverkomponentenmuster von React 19 profitieren alle vom Typsystem von TypeScript. Dieser Leitfaden behandelt die Muster, die erfahrene React-Entwickler täglich verwenden.
📋 Table of Contents
Generische Komponenten
// 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} />
)}
/>
Polymorphe Komponenten
// 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>
Muster zusammengesetzter Komponenten
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>
Typisierte benutzerdefinierte Haken
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;
}
Reagieren Sie auf 19 Serverkomponenten mit 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
}
Die TypeScript-React-Muster im Jahr 2026 sind ausgereift – generische Komponenten eliminieren doppelten Code, polymorphe Komponenten erstellen flexible APIs, zusammengesetzte Komponenten ersetzen komplexes Prop-Drilling und typisierte benutzerdefinierte Hooks machen asynchrone Muster sicher. Durch die Kombination der Serverkomponenten von React 19 mit TypeScript entstehen vollständig typsichere Full-Stack-Anwendungen.
🔗 Share this article
✍️ Leave a Comment