TypeScript10 de lecture20 octobre 2024

Patterns TypeScript avancés pour 2024

Les patterns et bonnes pratiques TypeScript pour des projets maintenables et type-safe. Découvrez les techniques utilisées par les meilleurs développeurs.

Patterns TypeScript avancés pour 2024

Introduction

TypeScript a révolutionné le développement JavaScript en apportant la sécurité des types. Mais au-delà des bases, certains patterns avancés peuvent transformer votre code.

Utility Types essentiels

Partial, Required, Readonly

interface User {
  id: string
  name: string
  email: string
  avatar?: string
}

// Tous les champs optionnels
type UpdateUser = Partial<User>

// Tous les champs requis
type CompleteUser = Required<User>

// Tous les champs en lecture seule
type ImmutableUser = Readonly<User>

Pick et Omit

// Sélectionner certains champs
type UserPreview = Pick<User, 'id' | 'name'>

// Exclure certains champs
type PublicUser = Omit<User, 'email'>

Patterns avancés

1. Discriminated Unions

Parfait pour gérer différents états :

type AsyncState<T> =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success'; data: T }
  | { status: 'error'; error: Error }

function UserProfile({ state }: { state: AsyncState<User> }) {
  switch (state.status) {
    case 'idle':
      return null
    case 'loading':
      return <Spinner />
    case 'success':
      return <Profile user={state.data} /> // data est typé!
    case 'error':
      return <Error message={state.error.message} />
  }
}

2. Template Literal Types

Créez des types dynamiques avec des string literals :

type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE'
type Endpoint = '/users' | '/posts' | '/comments'

// Génère: "GET /users" | "GET /posts" | "POST /users" | ...
type APIRoute = `${HTTPMethod} ${Endpoint}`

// Pour les événements
type EventName = 'click' | 'focus' | 'blur'
type EventHandler = `on${Capitalize<EventName>}`
// Résultat: "onClick" | "onFocus" | "onBlur"

3. Conditional Types

Types qui changent selon une condition :

type IsArray<T> = T extends any[] ? true : false

type A = IsArray<string[]>  // true
type B = IsArray<string>    // false

// Plus utile : extraire le type d'un tableau
type ArrayElement<T> = T extends (infer U)[] ? U : never

type Element = ArrayElement<string[]>  // string

4. Mapped Types

Transformer tous les champs d'un type :

// Rendre tous les champs nullable
type Nullable<T> = {
  [K in keyof T]: T[K] | null
}

// Créer des getters
type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
}

interface Person {
  name: string
  age: number
}

type PersonGetters = Getters<Person>
// { getName: () => string; getAge: () => number }

Patterns pour React

Props avec variants

type ButtonVariant = 'primary' | 'secondary' | 'ghost'
type ButtonSize = 'sm' | 'md' | 'lg'

interface ButtonProps {
  variant?: ButtonVariant
  size?: ButtonSize
  children: React.ReactNode
}

// Avec des props conditionnelles
type LinkButtonProps = ButtonProps & {
  as: 'a'
  href: string
}

type NormalButtonProps = ButtonProps & {
  as?: 'button'
  onClick?: () => void
}

type PolymorphicButtonProps = LinkButtonProps | NormalButtonProps

Hooks génériques type-safe

function useLocalStorage<T>(key: string, initialValue: T) {
  const [value, setValue] = useState<T>(() => {
    const stored = localStorage.getItem(key)
    return stored ? JSON.parse(stored) : initialValue
  })

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value))
  }, [key, value])

  return [value, setValue] as const
}

// Usage
const [user, setUser] = useLocalStorage<User>('user', defaultUser)

Astuces de pro

1. satisfies operator

Validez un type sans perdre l'inférence :

const config = {
  api: 'https://api.example.com',
  timeout: 5000,
  retries: 3,
} satisfies Record<string, string | number>

// config.api est toujours inféré comme string, pas string | number

2. const assertions

// Sans as const
const routes = ['/', '/about', '/contact']
// Type: string[]

// Avec as const
const routes = ['/', '/about', '/contact'] as const
// Type: readonly ['/', '/about', '/contact']

3. Branded Types

Créez des types nominaux pour plus de sécurité :

type UserId = string & { readonly brand: unique symbol }
type PostId = string & { readonly brand: unique symbol }

function getUser(id: UserId) { /* ... */ }
function getPost(id: PostId) { /* ... */ }

const userId = 'user-123' as UserId
const postId = 'post-456' as PostId

getUser(userId)  // ✅ OK
getUser(postId)  // ❌ Erreur de type!

Conclusion

Ces patterns TypeScript vous permettront d'écrire du code plus robuste et maintenable. L'investissement initial en apprentissage est largement compensé par la réduction des bugs et l'amélioration de la productivité.

#TypeScript#Best Practices#Clean Code
Partager cet article