TypeScript Skill
When to Use
Activate this skill when:
- Encountering TypeScript compilation errors (TS2322, TS2345, TS7006, etc.)
- Improving type safety in existing code
- Converting JavaScript files to TypeScript
- Writing new TypeScript interfaces, types, or generics
- Working with Vue 3 + TypeScript patterns
Finding TypeScript Errors
CRITICAL: npm run build does NOT catch all TypeScript errors! Vite only transpiles, it doesn't type-check.
Commands to Find Errors
# Vue projects - ALWAYS run this to find TS errors
npm run type-check:watch
# Or one-time check
npx vue-tsc --noEmit
# Non-Vue TypeScript projects
npx tsc --noEmit
# Check specific file
npx vue-tsc --noEmit src/path/to/file.vue
Verification Workflow
- Before claiming code works: Run
npm run type-check:watch - After fixing errors: Run again to verify all resolved
- Before committing: Ensure zero TypeScript errors
Common Gotcha
| Command | What it checks |
|---------|----------------|
| npm run build | Syntax only (Vite transpiles) - NOT SUFFICIENT |
| npm run type-check:watch | Full type checking (vue-tsc) - USE THIS |
| npm run lint | ESLint rules (separate from TS errors) |
Fix Type Errors
TS2322: Type 'X' is not assignable to type 'Y'
Cause: Assigning a value of incompatible type.
// BAD
const count: number = "5"
// GOOD - Option 1: Fix the type
const count: number = 5
// GOOD - Option 2: Change the annotation
const count: string = "5"
// GOOD - Option 3: Parse the value
const count: number = parseInt("5", 10)
TS2345: Argument type mismatch
Cause: Function argument doesn't match parameter type.
// BAD
function process(value: string) { /*...*/ }
process(undefined)
// GOOD - Handle undefined
function process(value: string | undefined) {
if (!value) return
// ...
}
// GOOD - Use type guard
function process(value: unknown) {
if (typeof value === 'string') {
// value is now string
}
}
TS7006: Parameter implicitly has 'any' type
Cause: Missing type annotation on parameter.
// BAD
const handler = (event) => { /*...*/ }
// GOOD
const handler = (event: MouseEvent) => { /*...*/ }
// GOOD - For callbacks
array.map((item: string) => item.toUpperCase())
TS2532: Object is possibly 'undefined'
Cause: Accessing property on potentially undefined value.
// BAD
const name = user.profile.name
// GOOD - Optional chaining
const name = user?.profile?.name
// GOOD - Null check
if (user && user.profile) {
const name = user.profile.name
}
// GOOD - Non-null assertion (when you're certain)
const name = user!.profile!.name
TS2339: Property does not exist on type
Cause: Accessing undeclared property.
// BAD
interface User { name: string }
const user: User = { name: 'John', age: 30 } // age doesn't exist
// GOOD - Add to interface
interface User {
name: string
age?: number // Optional property
}
// GOOD - Use index signature
interface User {
name: string
[key: string]: unknown
}
Write Better Types
Use unknown Instead of any
// BAD - any disables type checking
function parse(data: any) {
return data.value // No error, but unsafe
}
// GOOD - unknown requires type checking
function parse(data: unknown) {
if (typeof data === 'object' && data !== null && 'value' in data) {
return (data as { value: string }).value
}
throw new Error('Invalid data')
}
Use satisfies for Type Constraints
// BAD - Loses specific type info
const config: Record<string, string> = {
apiUrl: 'https://api.example.com',
timeout: '5000' // We want to catch this isn't a number
}
// GOOD - Maintains inference + validates structure
const config = {
apiUrl: 'https://api.example.com',
timeout: 5000
} satisfies Record<string, string | number>
// config.apiUrl is typed as string, not string | number
Discriminated Unions for State
// Model different states with a discriminant property
type LoadingState =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: string[] }
| { status: 'error'; error: Error }
function handle(state: LoadingState) {
switch (state.status) {
case 'idle':
return 'Ready'
case 'loading':
return 'Loading...'
case 'success':
return state.data.join(', ') // TypeScript knows data exists
case 'error':
return state.error.message // TypeScript knows error exists
}
}
Template Literal Types
// Create dynamic string types
type EventName = 'click' | 'focus' | 'blur'
type Handler = `on${Capitalize<EventName>}`
// Result: 'onClick' | 'onFocus' | 'onBlur'
// API routes
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE'
type ApiRoute = `/api/${string}`
type Endpoint = `${HttpMethod} ${ApiRoute}`
// e.g., 'GET /api/users'
Generics with Constraints
// Constrain generic to specific shape
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]
}
// With default type
function createStore<T = Record<string, unknown>>(initial: T) {
let state = initial
return {
get: () => state,
set: (value: T) => { state = value }
}
}
Utility Types Reference
// Partial - Make all properties optional
type PartialUser = Partial<User>
// Required - Make all properties required
type RequiredUser = Required<User>
// Pick - Select specific properties
type UserName = Pick<User, 'firstName' | 'lastName'>
// Omit - Remove specific properties
type PublicUser = Omit<User, 'password' | 'secretKey'>
// Record - Create object type with specific keys
type Flags = Record<'dark' | 'compact', boolean>
// ReturnType - Extract function return type
type Result = ReturnType<typeof fetchData>
// Parameters - Extract function parameters
type Args = Parameters<typeof fetchData>
// NonNullable - Remove null and undefined
type DefiniteValue = NonNullable<string | null | undefined> // string
// Extract/Exclude - Filter union types
type Numbers = Extract<string | number | boolean, number> // number
type NotNumbers = Exclude<string | number | boolean, number> // string | boolean
Convert JavaScript to TypeScript
Migration Workflow
- Rename file:
.js→.ts(or.vuestays same) - Add explicit types to function parameters
- Define interfaces for objects and props
- Handle null/undefined with optional chaining or guards
- Run
npm run buildto find remaining errors
Common Conversions
// JavaScript
function fetchUser(id) {
return fetch(`/api/users/${id}`).then(r => r.json())
}
// TypeScript
interface User {
id: string
name: string
email: string
}
async function fetchUser(id: string): Promise<User> {
const response = await fetch(`/api/users/${id}`)
return response.json() as Promise<User>
}
Vue 3 + TypeScript
See references/vue-typescript.md for:
defineProps<T>()patternsdefineEmits<T>()(old and Vue 3.3+ syntax)- Pinia store typing
- Composable return types
- Template ref typing
Anti-Patterns to Avoid
Never Do This
// DON'T use any
const data: any = fetchData()
// DON'T use @ts-ignore without explanation
// @ts-ignore
brokenCode()
// DON'T use non-null assertion carelessly
user!.profile!.settings!.theme
// DON'T leave implicit any in parameters
array.map(x => x.value) // x is implicitly any
Do This Instead
// Use unknown + type guards
const data: unknown = fetchData()
if (isValidData(data)) { /* use data */ }
// Use @ts-expect-error with reason
// @ts-expect-error - Legacy API returns number as string
const count: number = parseInt(response.value)
// Use optional chaining
user?.profile?.settings?.theme ?? 'default'
// Always type parameters
array.map((x: Item) => x.value)
tsconfig.json Recommendations
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"exactOptionalPropertyTypes": true,
"noPropertyAccessFromIndexSignature": true
}
}
Additional References
references/common-errors.md- Detailed error solutionsreferences/utility-types.md- Full utility type guidereferences/vue-typescript.md- Vue 3 + TypeScript patterns
Scan to join WeChat group