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
interfacefor object shapes that may be extended or implemented - Use
typefor unions, intersections, tuples, mapped types, and utility types - Prefer string literal unions over
enumunless anenumis required for interoperability
typescript
interface User {
id: string
email: string
}
type UserRole = 'admin' | 'member'
type UserWithRole = User & {
role: UserRole
}
Avoid any
- Avoid
anyin application code - Use
unknownfor 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
interfaceortype - Type callback props explicitly
- Do not use
React.FCunless 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
.jsand.jsxfiles, 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.logstatements 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" />
)
}