coding-style

TypeScript/JavaScript Coding Style

This file extends with TypeScript/JavaScript specific content.

Types and Interfaces

Use types to make public APIs, shared models, and component props explicit, readable, and reusable.

Public APIs

  • Add parameter types to exported functions, shared utilities, and public class methods
  • Add return types only when TypeScript cannot infer them correctly, when the function is part of a public contract, or when the explicit type prevents accidental contract drift
  • Let TypeScript infer obvious local variable types
  • Extract repeated inline object shapes into named types or interfaces
  • Always use source types from the owning library or module
  • Do not create local duplicate types or substitute types to work around TypeScript errors. Fix the type boundary instead.
typescript
// WRONG: Exported function without parameter types
export function formatUser(user) {
  return `${user.firstName} ${user.lastName}`
}

// CORRECT: Explicit types on public APIs
interface User {
  firstName: string
  lastName: string
}

export function formatUser(user: User): string {
  return `${user.firstName} ${user.lastName}`
}

Interfaces vs. Type Aliases

  • Use interface for object shapes that may be extended or implemented
  • Use type for unions, intersections, tuples, mapped types, and utility types
  • Prefer string literal unions over enum unless an enum is required for interoperability
typescript
interface User {
  id: string
  email: string
}

type UserRole = 'admin' | 'member'
type UserWithRole = User & {
  role: UserRole
}

Avoid any

  • Avoid any in application code
  • Use unknown for external or untrusted input, then narrow it safely
  • Use generics when a value's type depends on the caller
typescript
// WRONG: any removes type safety
function getErrorMessage(error: any) {
  return error.message
}

// CORRECT: unknown forces safe narrowing
function getErrorMessage(error: unknown): string {
  if (error instanceof Error) {
    return error.message
  }

  return 'Unexpected error'
}

React Props

  • Define component props with a named interface or type
  • Type callback props explicitly
  • Do not use React.FC unless there is a specific reason to do so
typescript
interface User {
  id: string
  email: string
}

interface UserCardProps {
  user: User
  onSelect: (id: string) => void
}

function UserCard({ user, onSelect }: UserCardProps) {
  return <button onClick={() => onSelect(user.id)}>{user.email}</button>
}

JavaScript Files

  • In .js and .jsx files, use JSDoc when types improve clarity and a TypeScript migration is not practical
  • Keep JSDoc aligned with runtime behavior
javascript
/**
 * @param {{ firstName: string, lastName: string }} user
 * @returns {string}
 */
export function formatUser(user) {
  return `${user.firstName} ${user.lastName}`
}

Immutability

Use spread operator for immutable updates:

typescript
interface User {
  id: string
  name: string
}

// WRONG: Mutation
function updateUser(user: User, name: string): User {
  user.name = name // MUTATION!
  return user
}

// CORRECT: Immutability
function updateUser(user: Readonly<User>, name: string): User {
  return {
    ...user,
    name
  }
}

Error Handling

Use async/await with try-catch and narrow unknown errors safely:

typescript
interface User {
  id: string
  email: string
}

declare function riskyOperation(userId: string): Promise<User>

function getErrorMessage(error: unknown): string {
  if (error instanceof Error) {
    return error.message
  }

  return 'Unexpected error'
}

const logger = {
  error: (message: string, error: unknown) => {
    // Replace with your production logger (for example, pino or winston).
  }
}

async function loadUser(userId: string): Promise<User> {
  try {
    const result = await riskyOperation(userId)
    return result
  } catch (error: unknown) {
    logger.error('Operation failed', error)
    throw new Error(getErrorMessage(error))
  }
}

Input Validation

Use Zod for schema-based validation and infer types from the schema:

typescript
import { z } from 'zod'

const userSchema = z.object({
  email: z.string().email(),
  age: z.number().int().min(0).max(150)
})

type UserInput = z.infer<typeof userSchema>

const validated: UserInput = userSchema.parse(input)

JSDoc 注释

所有函数使用中文 JSDoc 注释。

Console.log

  • No console.log statements in production code
  • Use proper logging libraries instead
  • See hooks for automatic detection

Tailwind CSS 组件根节点命名

  • 使用 tailwindcss 时,重要组件和高复用组件的根节点必须额外带一个语义化类名,不能只依赖工具类
  • 语义化类名使用 kebab-case,并优先采用 <component-name>-root 格式
  • 示例:ChatInput 组件的根节点应包含 chatinput-root
  • 这类类名的主要目标是提升调试效率,方便在 DevTools 中快速定位组件
tsx
// WRONG: 根节点只有 Tailwind 工具类,调试时难以快速定位
export function ChatInput() {
  return <div className="flex items-center gap-2 rounded-lg border p-3" />
}

// CORRECT: 根节点包含稳定、语义化的组件类名
export function ChatInput() {
  return (
    <div className="chatinput-root flex items-center gap-2 rounded-lg border p-3" />
  )
}