Unit 6 - Notes
Unit 6: TypeScript for Web Applications and Quality Assurance
1. Integration of TypeScript in Frontend Projects
TypeScript is a superset of JavaScript that adds static typing. Integrating it into a frontend workflow moves error checking from runtime to compile-time, significantly improving code reliability.
Project Configuration (tsconfig.json)
The tsconfig.json file is the root configuration for any TypeScript project. It dictates how the compiler generates JavaScript.
- Compiler Options:
target: Specifies the ECMAScript target version (e.g.,ES6,ES2020).module: Specifies the module code generation (e.g.,CommonJS,ESNext).strict: Enables all strict type-checking options. Critical for QA.outDir: Where compiled.jsfiles are output.baseUrl/paths: Configures module resolution aliases (e.g.,@/componentsinstead of../../components).
Migration Strategies
- Greenfield Projects: Initialize with TypeScript CLI (
tsc --init) or framework templates (e.g.,npm create vite@latest my-app -- --template react-ts). - Brownfield (Existing) Projects:
- Coexistence: Configure bundlers (Webpack/Vite) to handle both
.jsand.tsfiles. allowJs: true: Allows TypeScript to process JavaScript files.- Incremental Migration: Rename files from
.jsto.ts(or.tsx) one by one, fixing type errors progressively usinganyas a temporary placeholder only when necessary.
- Coexistence: Configure bundlers (Webpack/Vite) to handle both
2. Type-Safe Data Models
Creating robust data models involves defining the shape of data structures to prevent access to non-existent properties or incorrect data assignments.
Interfaces vs. Type Aliases
- Interfaces: Best for defining object shapes and contracts that can be extended via declaration merging.
- Type Aliases: Best for unions, intersections, primitives, and tuples.
// Interface for an object structure
interface User {
id: number;
name: string;
}
// Type alias for a Union
type UserRole = 'admin' | 'editor' | 'viewer';
// Intersection
type AdminUser = User & { role: 'admin' };
Advanced Modeling with Generics
Generics allow the creation of reusable components and functions that work with a variety of types rather than a single one.
// Generic API Response Wrapper
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
// Usage
function handleUserResponse(response: ApiResponse<User>) {
console.log(response.data.name); // TS knows this is a string
}
Utility Types for Maintainability
TypeScript provides built-in utilities to transform types without rewriting them, ensuring DRY (Don't Repeat Yourself) principles.
Partial<T>: Makes all properties optional.Required<T>: Makes all properties required.Pick<T, K>: Constructs a type by picking a set of propertiesKfromT.Omit<T, K>: Constructs a type by picking all properties fromTand then removingK.Record<K, T>: Constructs an object type with property keysKand property valuesT.
3. Component and API Typing Strategies
Typing Components (React Context)
- Props: Define interfaces for component props to ensure the parent passes the correct data.
- State: Explicitly type state when the initial value (like
null) implies a different type than the future value. - Event Handling: Use specific React event types (
React.ChangeEvent<HTMLInputElement>,React.FormEvent) rather thanany.
interface ButtonProps {
label: string;
onClick: (event: React.MouseEvent<HTMLButtonElement>) => void;
variant?: 'primary' | 'secondary'; // Optional prop with union
}
const Button = ({ label, onClick, variant = 'primary' }: ButtonProps) => {
return <button className={variant} onClick={onClick}>{label}</button>;
};
API Typing and Runtime Validation
TypeScript types disappear at runtime. If an API returns data that contradicts the interface, the app may crash.
- Strategy 1: Assertions (Risky):
const data = await response.json() as User;(Trusts the API blindly). - Strategy 2: Runtime Validation (Zod/Yup): Validate the schema at runtime to ensure it matches the TypeScript type.
import { z } from 'zod';
// Define schema (Runtime)
const UserSchema = z.object({
id: z.number(),
name: z.string(),
email: z.string().email(),
});
// Infer Type from Schema (Compile time)
type User = z.infer<typeof UserSchema>;
async function fetchUser(id: number): Promise<User> {
const res = await fetch(`/api/users/${id}`);
const data = await res.json();
return UserSchema.parse(data); // Throws error if API contract is broken
}
4. Static Code Analysis
Static analysis involves examining code without executing it to find potential bugs, code smells, and security vulnerabilities.
ESLint with TypeScript
- Purpose: Enforces code quality rules and finds logic errors.
- Configuration: Use
@typescript-eslint/parserto allow ESLint to lint TypeScript code and@typescript-eslint/pluginfor TS-specific rules. - Key Rules:
no-explicit-any: Disallows usage of theanytype.no-unused-vars: Flags variables that are declared but not used.explicit-function-return-type: Requires function return types (optional but good for APIs).
Prettier
- Purpose: Enforces consistent code formatting (indentation, quotes, semicolons).
- Integration: Run Prettier after ESLint or configure ESLint to ignore formatting rules that conflict with Prettier.
Strict Mode (strict: true)
Enabling strict mode in tsconfig.json turns on a family of checks:
noImplicitAny: Raises error on expressions and declarations with an impliedanytype.strictNullChecks:nullandundefinedare not in the domain of every type. Prevents "cannot read property of undefined" errors.
5. Debugging Workflows
Source Maps
Browsers execute JavaScript, not TypeScript. Source maps (.map files) map the compiled JavaScript back to the original TypeScript source code.
- Enable in
tsconfig:"sourceMap": true. - Effect: In Chrome DevTools, you see
App.tsinstead ofApp.js, allowing you to set breakpoints on the original code.
Debugging Techniques
- VS Code Debugger: Configure
launch.jsonto attach to Chrome or launch a Node process. Allows stepping through TS code directly in the IDE. - Type Narrowing: If TypeScript complains an object is "possibly undefined," use the debugger to verify the runtime value, then implement Type Guards.
TYPESCRIPTif (user && typeof user.name === 'string') { // TS knows user.name is safe here } - "Type" Debugging: Use
Hoverin the IDE to see exactly what type TypeScript infers a variable to be. Often, bugs arise because TS inferred a Union type (e.g.,string | number) when the developer expected onlystring.
6. Testing Fundamentals
Quality Assurance relies on a robust testing pyramid.
Levels of Testing
- Unit Testing: Testing individual functions or classes in isolation.
- Tools: Jest, Vitest.
- Focus: Logic correctness, edge cases.
- Integration Testing: Testing how components interact (e.g., Parent-Child communication, Context updates).
- Tools: React Testing Library.
- Focus: Component rendering, user events.
- End-to-End (E2E) Testing: Simulating a real user navigating the application.
- Tools: Cypress, Playwright.
- Focus: Critical user flows (Login, Checkout).
Mocking and Typing in Tests
When testing, external dependencies (APIs, libraries) should often be mocked. TypeScript helps ensure mocks match the expected structure.
// Jest example with TypeScript
import { fetchUser } from './api';
jest.mock('./api');
const mockFetchUser = fetchUser as jest.MockedFunction<typeof fetchUser>;
test('returns user data', async () => {
// Typescript ensures the mock return value matches the User interface
mockFetchUser.mockResolvedValue({ id: 1, name: 'Test', email: 'test@test.com' });
const user = await fetchUser(1);
expect(user.name).toBe('Test');
});
7. Code Quality and Maintainability Practices
SOLID Principles in Frontend
- Single Responsibility Principle (SRP): A component should do one thing (e.g., one component for fetching data, another for displaying it).
- Open/Closed Principle: Components should be open for extension (via props/composition) but closed for modification.
Code Organization
- Barrel Files (
index.ts): Simplify imports by re-exporting modules from a directory. - Feature-Based Architecture: Group files by feature (User, Cart, Auth) rather than type (Components, Hooks, Utils).
TSDoc and Self-Documenting Code
- Type Annotations: Serve as documentation. A function signature
(userId: string) => Promise<User>explicitly tells the developer what inputs and outputs are expected. - TSDoc Comments: Use JSDoc-style comments for complex logic. IDEs display these on hover.
/**
* Calculates total price including tax.
* @param subtotal - The sum of item prices
* @param taxRate - Decimal representation (e.g., 0.05 for 5%)
* @returns The final formatted price string
*/
const calculateTotal = (subtotal: number, taxRate: number): string => { ... }
Automating QA (CI/CD)
Maintainability is enforced via automation in the Continuous Integration pipeline:
- Pre-commit Hooks (Husky/Lint-staged): Prevent bad code from being committed. Runs Linting and Type Checking on changed files.
- CI Pipeline:
- Step 1:
npm install - Step 2:
npm run lint(Static Analysis) - Step 3:
tsc --noEmit(Type Checking without generating files) - Step 4:
npm test(Unit/Integration tests) - Step 5: Build
- Step 1: