Framework Integration Patterns
1. React TypeScript Patterns and Component Typing
| Component Type | Syntax | Description | Use Case |
|---|---|---|---|
| Function Component | React.FC<Props> |
Function component with props type - includes children implicitly | Simple components, hooks-based |
| Function Component (Direct) | (props: Props) => JSX.Element |
Direct function typing - more explicit, no implicit children | Preferred modern approach |
| Props Interface | interface Props { } |
Define component props with TypeScript interface | Type-safe prop passing |
| Children Prop | React.ReactNode |
Type for React children - elements, strings, numbers, fragments | Components accepting children |
| Event Handlers | React.MouseEvent<T> |
Typed DOM events specific to React | onClick, onChange, onSubmit |
| Refs | React.RefObject<T> |
Typed reference to DOM elements or component instances | Direct DOM access, imperative APIs |
| Hooks | useState<T>() |
Generic hooks with type parameters | Typed state management |
Example: Function components with props
import React from 'react';
// Props interface
interface ButtonProps {
text: string;
onClick: () => void;
disabled?: boolean;
variant?: 'primary' | 'secondary';
}
// Modern approach - direct function typing
function Button({ text, onClick, disabled, variant = 'primary' }: ButtonProps) {
return (
<button
onClick={onClick}
disabled={disabled}
className={`btn-${variant}`}
>
{text}
</button>
);
}
// With children
interface CardProps {
title: string;
children: React.ReactNode;
}
function Card({ title, children }: CardProps) {
return (
<div className="card">
<h2>{title}</h2>
<div className="card-body">{children}</div>
</div>
);
}
// Generic component
interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
}
function List<T>({ items, renderItem }: ListProps<T>) {
return (
<ul>
{items.map((item, index) => (
<li key={index}>{renderItem(item)}</li>
))}
</ul>
);
}
Example: Event handlers and refs
import React, { useState, useRef } from 'react';
interface FormProps {
onSubmit: (data: FormData) => void;
}
interface FormData {
username: string;
email: string;
}
function Form({ onSubmit }: FormProps) {
const [formData, setFormData] = useState<FormData>({
username: '',
email: ''
});
// Typed ref
const inputRef = useRef<HTMLInputElement>(null);
// Typed event handler
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setFormData(prev => ({ ...prev, [name]: value }));
};
// Form submit handler
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
onSubmit(formData);
};
// Button click handler
const handleFocus = (e: React.MouseEvent<HTMLButtonElement>) => {
inputRef.current?.focus();
};
return (
<form onSubmit={handleSubmit}>
<input
ref={inputRef}
type="text"
name="username"
value={formData.username}
onChange={handleChange}
/>
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
/>
<button type="button" onClick={handleFocus}>Focus Input</button>
<button type="submit">Submit</button>
</form>
);
}
Example: Hooks with TypeScript
import { useState, useEffect, useReducer, useContext } from 'react';
// useState with explicit type
const [count, setCount] = useState<number>(0);
const [user, setUser] = useState<User | null>(null);
// useState with type inference
const [name, setName] = useState(''); // string inferred
// useReducer with types
type State = { count: number };
type Action = { type: 'increment' } | { type: 'decrement' } | { type: 'reset' };
function reducer(state: State, action: Action): State {
switch (action.type) {
case 'increment': return { count: state.count + 1 };
case 'decrement': return { count: state.count - 1 };
case 'reset': return { count: 0 };
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</div>
);
}
// useContext with types
interface ThemeContextType {
theme: 'light' | 'dark';
toggleTheme: () => void;
}
const ThemeContext = React.createContext<ThemeContextType | undefined>(undefined);
function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
}
// Custom hook with generics
function useFetch<T>(url: string) {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
fetch(url)
.then(res => res.json())
.then((data: T) => setData(data))
.catch(err => setError(err))
.finally(() => setLoading(false));
}, [url]);
return { data, loading, error };
}
2. Vue.js TypeScript Integration and Composition API
| Feature | Syntax | Description | Use Case |
|---|---|---|---|
| defineComponent | defineComponent({ }) |
Define component with TypeScript inference | Options API with types |
| Ref<T> | ref<T>(value) |
Typed reactive reference | Reactive primitive values |
| Reactive<T> | reactive<T>(obj) |
Typed reactive object | Reactive complex objects |
| Computed<T> | computed<T>(() => T) |
Typed computed property | Derived reactive state |
| PropType<T> | type: Object as PropType<T> |
Type assertion for complex props | Object/array props |
| EmitsOptions | emits: { eventName: (payload: T) => boolean } |
Typed event emissions | Component events |
Example: Vue 3 Composition API with TypeScript
<script setup lang="ts">
import { ref, reactive, computed, watch } from 'vue';
// Typed refs
const count = ref<number>(0);
const message = ref<string>('Hello');
// Typed reactive object
interface User {
name: string;
email: string;
age: number;
}
const user = reactive<User>({
name: 'John',
email: 'john@example.com',
age: 30
});
// Computed with type inference
const doubleCount = computed(() => count.value * 2);
// Explicit computed type
const displayName = computed<string>(() => {
return `${user.name} (${user.age})`;
});
// Watch with types
watch(count, (newVal: number, oldVal: number) => {
console.log(`Count changed from ${oldVal} to ${newVal}`);
});
// Function with types
function increment(): void {
count.value++;
}
function updateUser(updates: Partial<User>): void {
Object.assign(user, updates);
}
</script>
<template>
<div>
<p>{{ message }}</p>
<p>Count: {{ count }} (Double: {{ doubleCount }})</p>
<p>{{ displayName }}</p>
<button @click="increment">Increment</button>
</div>
</template>
Example: Props and emits with types
<script setup lang="ts">
import { PropType } from 'vue';
// Define props interface
interface User {
id: number;
name: string;
email: string;
}
// Props with types
const props = defineProps({
title: {
type: String,
required: true
},
count: {
type: Number,
default: 0
},
user: {
type: Object as PropType<User>,
required: true
},
tags: {
type: Array as PropType<string[]>,
default: () => []
}
});
// Typed emits
const emit = defineEmits<{
(e: 'update', value: number): void;
(e: 'delete', id: number): void;
(e: 'change', user: User): void;
}>();
// Or with validation
const emit = defineEmits({
update: (value: number) => value >= 0,
delete: (id: number) => typeof id === 'number',
change: (user: User) => user.email.includes('@')
});
// Use emits
function handleUpdate() {
emit('update', props.count + 1);
}
function handleDelete() {
emit('delete', props.user.id);
}
</script>
3. Angular TypeScript Features and Decorators
| Decorator | Target | Description | Example |
|---|---|---|---|
| @Component | Class | Define Angular component with metadata | @Component({ selector: 'app-root' }) |
| @Injectable | Class | Mark class as available for dependency injection | @Injectable({ providedIn: 'root' }) |
| @Input | Property | Define input property for component | @Input() name: string; |
| @Output | Property | Define output event emitter | @Output() change = new EventEmitter(); |
| @ViewChild | Property | Query for child element or component | @ViewChild('input') inputEl!: ElementRef; |
| @HostListener | Method | Listen to host element events | @HostListener('click') |
Example: Angular component with TypeScript
import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
// Interface for component data
interface User {
id: number;
name: string;
email: string;
}
@Component({
selector: 'app-user-card',
template: `
<div class="card">
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
<button (click)="handleEdit()">Edit</button>
<button (click)="handleDelete()">Delete</button>
</div>
`,
styleUrls: ['./user-card.component.css']
})
export class UserCardComponent implements OnInit {
// Input with type
@Input() user!: User;
@Input() editable: boolean = true;
// Output with typed EventEmitter
@Output() edit = new EventEmitter<User>();
@Output() delete = new EventEmitter<number>();
ngOnInit(): void {
console.log('User card initialized', this.user);
}
handleEdit(): void {
if (this.editable) {
this.edit.emit(this.user);
}
}
handleDelete(): void {
this.delete.emit(this.user.id);
}
}
Example: Angular service with dependency injection
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
interface User {
id: number;
name: string;
email: string;
}
interface ApiResponse<T> {
data: T;
status: number;
}
@Injectable({
providedIn: 'root'
})
export class UserService {
private apiUrl = 'https://api.example.com';
constructor(private http: HttpClient) {}
getUsers(): Observable<User[]> {
return this.http.get<ApiResponse<User[]>>(`${this.apiUrl}/users`)
.pipe(
map(response => response.data),
catchError(this.handleError)
);
}
getUser(id: number): Observable<User> {
return this.http.get<ApiResponse<User>>(`${this.apiUrl}/users/${id}`)
.pipe(map(response => response.data));
}
createUser(user: Omit<User, 'id'>): Observable<User> {
return this.http.post<ApiResponse<User>>(`${this.apiUrl}/users`, user)
.pipe(map(response => response.data));
}
updateUser(id: number, updates: Partial<User>): Observable<User> {
return this.http.patch<ApiResponse<User>>(`${this.apiUrl}/users/${id}`, updates)
.pipe(map(response => response.data));
}
private handleError(error: any): Observable<never> {
console.error('API Error:', error);
throw error;
}
}
4. Node.js TypeScript Development and @types/node
| Feature | Package/Type | Description | Use Case |
|---|---|---|---|
| @types/node | npm i -D @types/node |
TypeScript definitions for Node.js built-in modules | Core Node.js development |
| Process | NodeJS.Process |
Type for process object - env, argv, exit, etc. | Environment variables, CLI args |
| Buffer | Buffer |
Binary data handling type | File I/O, streams, binary data |
| EventEmitter | EventEmitter |
Event-driven architecture base class | Custom event systems |
| Stream | Readable, Writable, Duplex |
Stream interface types | Data streaming, pipes |
| Module Resolution | "moduleResolution": "node16" |
Node.js-specific module resolution | ESM and CommonJS interop |
Example: Node.js modules with TypeScript
import * as fs from 'fs/promises';
import * as path from 'path';
import { EventEmitter } from 'events';
import { createReadStream, createWriteStream } from 'fs';
// File operations
async function readConfig(): Promise<Record<string, any>> {
const configPath = path.join(__dirname, 'config.json');
const content = await fs.readFile(configPath, 'utf-8');
return JSON.parse(content);
}
async function writeLog(message: string): Promise<void> {
const logPath = path.join(__dirname, 'app.log');
const timestamp = new Date().toISOString();
await fs.appendFile(logPath, `[${timestamp}] ${message}\n`);
}
// Environment variables with type safety
interface Env {
NODE_ENV: 'development' | 'production' | 'test';
PORT: string;
DATABASE_URL: string;
API_KEY: string;
}
function getEnv(): Env {
return {
NODE_ENV: (process.env.NODE_ENV as Env['NODE_ENV']) || 'development',
PORT: process.env.PORT || '3000',
DATABASE_URL: process.env.DATABASE_URL || '',
API_KEY: process.env.API_KEY || ''
};
}
// Streams with types
async function copyFile(source: string, dest: string): Promise<void> {
return new Promise((resolve, reject) => {
const readStream = createReadStream(source);
const writeStream = createWriteStream(dest);
readStream.on('error', reject);
writeStream.on('error', reject);
writeStream.on('finish', resolve);
readStream.pipe(writeStream);
});
}
// EventEmitter with types
interface DataEvents {
data: (value: string) => void;
error: (error: Error) => void;
complete: () => void;
}
class DataProcessor extends EventEmitter {
on<K extends keyof DataEvents>(event: K, listener: DataEvents[K]): this {
return super.on(event, listener);
}
emit<K extends keyof DataEvents>(
event: K,
...args: Parameters<DataEvents[K]>
): boolean {
return super.emit(event, ...args);
}
process(data: string[]): void {
data.forEach(item => {
this.emit('data', item);
});
this.emit('complete');
}
}
5. Express.js Type Safety and Middleware Typing
| Type | Syntax | Description | Use Case |
|---|---|---|---|
| Request<P, ResBody, ReqBody, Query> | Request<ParamsDictionary, any, RequestBody> |
Typed Express request - params, body, query | Type-safe route handlers |
| Response<ResBody> | Response<ResponseBody> |
Typed response body | Type-safe responses |
| NextFunction | NextFunction |
Middleware next callback | Middleware chain |
| RequestHandler<P, ResBody, ReqBody> | RequestHandler<Params, Response, Body> |
Complete handler type with all generics | Reusable typed handlers |
| ErrorRequestHandler | (err, req, res, next) => void |
Error handling middleware type | Global error handlers |
| Router | Router() |
Express router with types | Modular routes |
Example: Express routes with TypeScript
import express, { Request, Response, NextFunction, RequestHandler } from 'express';
const app = express();
app.use(express.json());
// Interfaces for type safety
interface User {
id: string;
name: string;
email: string;
}
interface CreateUserBody {
name: string;
email: string;
password: string;
}
interface UpdateUserBody {
name?: string;
email?: string;
}
interface UserParams {
userId: string;
}
interface UserQuery {
page?: string;
limit?: string;
}
// Typed route handlers
const getUsers: RequestHandler<{}, User[], {}, UserQuery> = async (req, res) => {
const page = parseInt(req.query.page || '1');
const limit = parseInt(req.query.limit || '10');
const users = await fetchUsers(page, limit);
res.json(users);
};
const getUser: RequestHandler<UserParams, User | { error: string }> = async (req, res) => {
const { userId } = req.params;
const user = await findUserById(userId);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
};
const createUser: RequestHandler<{}, User, CreateUserBody> = async (req, res) => {
const { name, email, password } = req.body;
const newUser = await createNewUser({ name, email, password });
res.status(201).json(newUser);
};
const updateUser: RequestHandler<UserParams, User, UpdateUserBody> = async (req, res) => {
const { userId } = req.params;
const updates = req.body;
const updatedUser = await updateUserData(userId, updates);
res.json(updatedUser);
};
// Routes
app.get('/users', getUsers);
app.get('/users/:userId', getUser);
app.post('/users', createUser);
app.patch('/users/:userId', updateUser);
Example: Typed middleware
import { Request, Response, NextFunction, RequestHandler } from 'express';
// Extend Express Request with custom properties
declare global {
namespace Express {
interface Request {
user?: User;
startTime?: number;
}
}
}
interface User {
id: string;
email: string;
role: 'admin' | 'user';
}
// Authentication middleware
const authenticate: RequestHandler = async (req, res, next) => {
try {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'No token provided' });
}
const user = await verifyToken(token);
req.user = user;
next();
} catch (error) {
res.status(401).json({ error: 'Invalid token' });
}
};
// Authorization middleware
function authorize(...roles: User['role'][]): RequestHandler {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({ error: 'Not authenticated' });
}
if (!roles.includes(req.user.role)) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
}
// Logging middleware
const logger: RequestHandler = (req, res, next) => {
req.startTime = Date.now();
res.on('finish', () => {
const duration = Date.now() - (req.startTime || 0);
console.log(`${req.method} ${req.path} - ${res.statusCode} (${duration}ms)`);
});
next();
};
// Validation middleware
function validateBody<T>(schema: (body: any) => body is T): RequestHandler {
return (req, res, next) => {
if (!schema(req.body)) {
return res.status(400).json({ error: 'Invalid request body' });
}
next();
};
}
// Error handling middleware
const errorHandler: express.ErrorRequestHandler = (err, req, res, next) => {
console.error(err.stack);
res.status(500).json({
error: 'Internal server error',
message: err.message
});
};
// Usage
app.use(logger);
app.get('/admin', authenticate, authorize('admin'), (req, res) => {
res.json({ message: 'Admin only' });
});
app.use(errorHandler);
6. Next.js TypeScript Configuration and API Routes
| Feature | Type | Description | Use Case |
|---|---|---|---|
| GetStaticProps | GetStaticProps<Props> |
Type for static generation function | Build-time data fetching |
| GetServerSideProps | GetServerSideProps<Props> |
Type for server-side rendering function | Request-time data fetching |
| GetStaticPaths | GetStaticPaths |
Type for dynamic route paths generation | Dynamic static routes |
| NextApiRequest | NextApiRequest |
API route request type | API endpoints |
| NextApiResponse | NextApiResponse<Data> |
API route response type | Type-safe API responses |
| NextPage | NextPage<Props> |
Page component type | Next.js pages |
| AppProps | AppProps<PageProps> |
_app.tsx component props type | Custom App component |
Example: Next.js pages with data fetching
import { GetStaticProps, GetServerSideProps, NextPage } from 'next';
// Page props interface
interface Post {
id: number;
title: string;
content: string;
author: string;
}
interface HomeProps {
posts: Post[];
}
// Static generation
export const getStaticProps: GetStaticProps<HomeProps> = async (context) => {
const res = await fetch('https://api.example.com/posts');
const posts: Post[] = await res.json();
return {
props: {
posts
},
revalidate: 60 // Revalidate every 60 seconds
};
};
// Page component
const Home: NextPage<HomeProps> = ({ posts }) => {
return (
<div>
<h1>Blog Posts</h1>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.content}</p>
<small>By {post.author}</small>
</article>
))}
</div>
);
};
export default Home;
// Dynamic route with params
interface PostPageProps {
post: Post;
}
export const getServerSideProps: GetServerSideProps<PostPageProps> = async (context) => {
const { id } = context.params as { id: string };
const res = await fetch(`https://api.example.com/posts/${id}`);
const post: Post = await res.json();
return {
props: {
post
}
};
};
Example: Next.js API routes
import type { NextApiRequest, NextApiResponse } from 'next';
// Response types
interface User {
id: number;
name: string;
email: string;
}
interface ErrorResponse {
error: string;
details?: string;
}
type UserResponse = User | ErrorResponse;
// GET /api/users/[id]
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<UserResponse>
) {
const { id } = req.query;
if (req.method !== 'GET') {
return res.status(405).json({ error: 'Method not allowed' });
}
try {
const user = await fetchUser(Number(id));
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.status(200).json(user);
} catch (error) {
res.status(500).json({
error: 'Internal server error',
details: error instanceof Error ? error.message : 'Unknown error'
});
}
}
// POST /api/users - Create user
interface CreateUserBody {
name: string;
email: string;
}
export default async function createUserHandler(
req: NextApiRequest,
res: NextApiResponse<User | ErrorResponse>
) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}
const { name, email } = req.body as CreateUserBody;
if (!name || !email) {
return res.status(400).json({ error: 'Name and email required' });
}
try {
const newUser = await createUser({ name, email });
res.status(201).json(newUser);
} catch (error) {
res.status(500).json({ error: 'Failed to create user' });
}
}
// Middleware pattern for API routes
type ApiHandler<T = any> = (
req: NextApiRequest,
res: NextApiResponse<T>
) => Promise<void> | void;
function withAuth<T>(handler: ApiHandler<T>): ApiHandler<T | ErrorResponse> {
return async (req, res) => {
const token = req.headers.authorization;
if (!token) {
return res.status(401).json({ error: 'Unauthorized' });
}
try {
await verifyToken(token);
return handler(req, res);
} catch (error) {
return res.status(401).json({ error: 'Invalid token' });
}
};
}
// Protected route
export default withAuth<User>(async (req, res) => {
const user = await getCurrentUser(req);
res.status(200).json(user);
});
Note: Framework integration best practices:
- React - Use direct function typing over React.FC, properly type hooks and event handlers
- Vue 3 - Leverage <script setup lang="ts"> with Composition API for best inference
- Angular - Enable strict mode, use interfaces for all data models, leverage RxJS types
- Node.js - Install @types/node, use proper module resolution (node16/nodenext)
- Express - Extend Express namespace for custom properties, type all middleware
- Next.js - Use proper data fetching types, type API routes with NextApiRequest/Response
Framework Integration Summary
- React - Type props, events, hooks, and refs with React.* types and generics
- Vue 3 - Use defineProps, defineEmits, ref, reactive with proper type annotations
- Angular - Leverage decorators (@Component, @Injectable) with typed services and RxJS
- Node.js - Install @types/node for built-in modules, use proper async/stream types
- Express - Type Request<P, ResBody, ReqBody, Query> and middleware with generics
- Next.js - Use GetStaticProps, GetServerSideProps, and NextApiRequest types for data fetching and API routes