6. Typescript
6.1. Общие правила

6.1. Общие правила

6.1.1. Any vs unknown (opens in a new tab)

⚡ Стараемся не использовать any. Unknown предпочтительнее чем any.

function f(a: any) {
  return a + 100
}
 
function f(a: unknown) {
  if (typeof a === 'number') {
    return a + 100
  } else {
    return 0
  }
}

Пример реального использования

const handleServerNetworkError = (err: unknown, dispatch: AppDispatch): void => {
  let errorMessage = 'Some error occurred'
 
  if (axios.isAxiosError(err)) {
    errorMessage = err.response?.data?.message || err?.message || errorMessage
  } else if (err instanceof Error) {
    errorMessage = `Native error: ${err.message}`
  } else {
    errorMessage = JSON.stringify(err)
  }
  dispatch(appActions.setAppError({ error: errorMessage }))
  dispatch(appActions.setAppStatus({ status: 'failed' }))
}

6.1.2. Типизация возвращаемого значения функции (opens in a new tab)

  • Более точная документация для удобства чтения кода.
  • Более быстрое обнаружение потенциальных ошибок типа в будущем, если в коде произойдут изменения, изменяющие возвращаемый тип функции.
const minutesToHHMM = (minutes: number): string => {
  let hours = Math.floor(minutes / 60)
  let remainderMinutes = minutes % 60
  let formattedHours = hours < 10 ? '0' + hours : hours
  let formattedMinutes = remainderMinutes < 10 ? '0' + remainderMinutes : remainderMinutes
 
  return `${formattedHours}:${formattedMinutes}`
}

6.1.3. Типизация массивов

⚡ Типизируем массив через квадратные скобки (opens in a new tab)

✅ Good❌ Bad
User[]Array<User>

6.1.4. Что использовать type или interface?

6.1.5. Nullable/undefined type aliases (opens in a new tab)

В union (объединенных) типах не следует использовать комбинации |null или |undefined.

  • Подобные псевдонимы, которые допускают значение null или undefined, обычно указывают на то, что нулевые значения передаются через множество уровней приложения, и это усложняет определение источника первоначальной проблемы, из-за которой появился null.
  • Это также делает неясным, когда конкретные значения в классе или интерфейсе могут отсутствовать.
  • Вместо этого код должен добавлять |null или |undefined только тогда, когда это действительно необходимо. Код должен обращаться с нулевыми значениями там, где они возникают, используя вышеуказанные методы.
// ❌ Bad
type Status1 = 'success' | 'failed' | null
 
const func1 = (status: Status1) => {}
 
// ✅ Better. Код должен работать с нулевыми значениями в непосредственной близости от места их возникновения
type Status = 'success' | 'failed'
 
const func2 = (status: Status2 | null) => {}
 
// ✅✅ Best. Используем кастомный Nullable type
export type Nullable<T> = T | null
 
type Status = 'success' | 'failed'
 
const func3 = (status: Nullable<Status>) => {}

6.1.6. Prettify type aliase (opens in a new tab)

В intersected типах желательно использование кастомного типа Prettify.

⚡ Это упрощает чтение, т. к. не будет вложенных типов с неочевидными значениями, для уточнения которых требуется переходить к типу.

// ❌ Bad
type User = {
  name: string
  age: number
}
 
type Student = User & {
  isGraduate: boolean
}
 

notPrettified

prettify.ts
// ✅ Good for prettifying and easier reading intersected types
export type Prettify<T> = {
    [K in keyof T]: T[K]
}
 
type User = {
  name: string
  age: number
}
 
type Student = User & {
  isGraduate: boolean
}
 
type PrettifiedStudent = Prettify<Student>
 

prettify

6.1.7. Wrapper types (String, Boolean, Number)

❗ Для типизации примитивов никогда не используем wrapper types (opens in a new tab)