Перейти к основному содержимому

7. TypeScript

Основы TypeScript

TypeScript — это язык программирования с открытым исходным кодом, разработанный Microsoft как надмножество JavaScript. Он добавляет статическую типизацию к JavaScript, что позволяет обнаруживать ошибки на этапе компиляции, а не во время выполнения. Это позволяет повысить качество кода и ускорить разработку, особенно в больших проектах. TypeScript является одним из самых популярных языков программирования в мире, активно развивается и поддерживается сообществом разработчиков. В этом разделе мы рассмотрим основы TypeScript и его практическое применение в проекте React-приложения медицинской клиники.

История и философия TypeScript

TypeScript был создан в 2012 году под руководством Андерса Хейлсберга, также известного как создатель C#. Основная философия TypeScript — обеспечить статическую типизацию для JavaScript, сохраняя при этом совместимость с существующим JavaScript-кодом. TypeScript следует принципу "постепенной типизации" (gradual typing), который позволяет постепенно внедрять типы в существующие проекты. Это делает TypeScript прагматичным выбором как для новых, так и для существующих приложений.

Принципы работы TypeScript

TypeScript не выполняется напрямую в браузере или Node.js. Вместо этого, код на TypeScript проходит процесс компиляции, преобразуясь в обычный JavaScript:

  1. Разработчик пишет код на TypeScript (.ts файлы)
  2. Компилятор TypeScript (tsc) преобразует его в JavaScript
  3. Получившийся JavaScript код используется в приложении

Этот процесс означает, что TypeScript добавляет только этап компиляции, но не изменяет процесс выполнения.

Преимущества TypeScript:

  • Раннее обнаружение ошибок — проблемы выявляются при компиляции, а не в рантайме.
  • Улучшенная документация кода — типы служат встроенной документацией и помогают понять структуру кода
  • Улучшенная поддержка IDE — автодополнение, подсказки, рефакторинг, проверка типов и т.д.
  • Безопасные рефакторинги — компилятор проверяет корректность изменений и предотвращает ошибки
  • Поддержка современных возможностей JavaScript — TypeScript совместим с последними стандартами ECMAScript и позволяет использовать все возможности JavaScript

Ключевые концепции TypeScript на примере React-приложения медицинской клиники

В данном разделе рассмотрим практическое применение TypeScript в проекте системы управления медицинской клиникой, автоматически сгенерированном с помощью GraphQL Code Generator в предыдущем разделе. Проект демонстрирует современные подходы к типизации в React-приложениях.

1. Типизация React-компонентов

TypeScript позволяет четко определить типы для React-компонентов, включая их свойства (props) и состояние:

// Путь: src/App.tsx
export const App: React.FC = () => {
const [apolloClient, setAppoloClient] = useState<ApolloClient<NormalizedCacheObject>>()

const initClient = async (): Promise<ApolloClient<NormalizedCacheObject> | undefined> => {
if (process.env.NODE_ENV === 'production')
await initEnv()

if (!apolloClient) {
return new ApolloClient({
cache: cache,
uri: process.env.NODE_ENV === 'production' ? process.env.DS_ENDPOINT : '/graphql',
})
}
}
}

В данном примере используется типизация:

  • React.FC — тип для функционального компонента React
  • useState<ApolloClient<NormalizedCacheObject>>() — хук состояния с генериком, указывающим тип состояния
  • Promise<ApolloClient<NormalizedCacheObject> | undefined> — возвращаемый тип асинхронной функции

Типизация позволяет:

  • Определить типы для всех компонентов и функций
  • Определить типы для всех данных, которые используются в приложении
  • Определить типы для всех свойств и методов компонентов
  • Определить типы для всех параметров и возвращаемых значений функций
  • Определить типы для всех переменных и констант
  • Определить типы для всех классов и интерфейсов

Таким образом, типизация позволяет создавать более безопасный и надежный код, а также облегчает понимание и поддержку проекта. А это очень важно, особенно в больших проектах.

2. Интерфейсы и типы данных

TypeScript позволяет создавать строго типизированные структуры данных для предметной области приложения:

// Путь: tsgen/src/utils/types/Basic.ts
export interface TypedField {
fieldAlias: string
fieldName: string
fieldPath: string
fieldType: string | TypedField[]
}

export interface Query {
queryAlias: string
queryName: string
queryType: string
fieldList: TypedField[]
}

export interface Entity {
name: string
queryList: Query[]
mutationList: Mutation[]
childEntityRefList: RefField[]
}

Эти интерфейсы определяют структуру метаданных для генерации кода, обеспечивая типобезопасность при работе с конфигурацией генератора. Таким образом, мы можем быть уверены, что все данные, которые мы получаем из GraphQL API, имеют правильные типы.

3. Перечисления (Enums)

Перечисления в TypeScript обеспечивают типобезопасность при работе с предопределенными значениями:

// Путь: tsgen/src/utils/types/Basic.ts
export enum MutationKind {
SIMPLE = 'SIMPLE',
ON_INSTANCE = 'ON_INSTANCE'
}

Такие перечисления используются для категоризации типов мутаций в GraphQL схеме, что исключает возможность передачи неверных значений. Перечисления также могут быть использованы для типизации данных, которые мы получаем из GraphQL API.

4. Обобщения (Generics)

Generics позволяют создавать переиспользуемые компоненты и функции, работающие с различными типами данных:

// Путь: src/components/__generate/Doctor/Doctor/DoctorList.tsx
export const DoctorList: FC<{
selectedDoctor?: string | null,
setSelectedDoctor?: (value: string) => void
}> = ({ selectedDoctor, setSelectedDoctor }) => {

const { data, loading, error: queryError } = useSearchDoctorQuery({
variables: {
cond: (filterStr && filterStr !== 'undefined' && selectedDoctor !== null )
? `it.${extractParamName(filterStr)}.id=='${extractParamValue(filterStr)}'`
: null
}
})

const elemList = data?.searchDoctor.elems

const mapToView = (list: typeof elemList) => {
return list?.map(elem => ({
key: elem.id ?? "",
id: elem.id,
actions: <Space>
<Link to={`/DoctorAgg/Doctor/Update/${elem.id}`}>Update</Link>
<Link to={`/DoctorAgg/Doctor/Delete/${elem.id}`}>Delete</Link>
</Space>
}))
}
}

В данном примере FC<{ selectedDoctor?: string | null, setSelectedDoctor?: (value: string) => void }> использует generic для типизации пропсов компонента, а typeof elemList позволяет автоматически выводить тип на основе существующей переменной. Перечисления в TypeScript позволяют создавать типы для данных, которые мы получаем из GraphQL API.

5. Условная типизация и Optional Chaining

TypeScript поддерживает условную типизацию и операторы безопасного доступа к свойствам:

// Путь: src/components/__generate/Doctor/Doctor/DoctorCreateForm.tsx
interface FormProps{
inputParameters: InputParameters
changeInputParameters: (input: InputParameters) => void
}

export const DoctorCreateForm: FC<FormProps> = ({ inputParameters, changeInputParameters }) => {
const [selectedDoctorTypeId, setSelectedDoctorTypeId] = useState<string | undefined | null>()

if (selectedDoctorTypeId) {
changeInputParameters({ doctorType: selectedDoctorTypeId })
setSelectedDoctorTypeId(undefined)
}

return (
<Form.Item label="person">
<Input
value={inputParameters.person?.entityId!}
onChange={e => changeInputParameters({ person: { entityId: e.target.value }})}
/>
</Form.Item>
)
}

Здесь используется:

  • string | undefined | null — union типы для nullable значений
  • inputParameters.person?.entityId! — optional chaining с non-null assertion оператором

6. Автогенерированные типы из GraphQL схемы

Одним из мощных преимуществ TypeScript является возможность автоматической генерации типов из внешних схем:

// Путь: src/__generate/graphql-frontend.ts
export type Scalars = {
ID: { input: string; output: string; }
String: { input: string; output: string; }
Boolean: { input: boolean; output: boolean; }
Int: { input: number; output: number; }
Float: { input: number; output: number; }
_DateTime: { input: any; output: any; }
BigDecimal: { input: any; output: any; }
}

export type _CreateDoctorInput = {
doctorType: Scalars['ID']['input'];
id?: InputMaybe<Scalars['ID']['input']>;
person: _SingleReferenceInput;
};

export function useSearchDoctorQuery(
baseOptions?: Apollo.QueryHookOptions<SearchDoctorQuery, SearchDoctorQueryVariables>
) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<SearchDoctorQuery, SearchDoctorQueryVariables>(SearchDoctorDocument, options);
}

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

7. Настройка TypeScript проекта

Конфигурация компилятора TypeScript определяется в файле tsconfig.json:

// Путь: tsconfig.json
{
"compilerOptions": {
"target": "es5",
"outDir": "./dist/",
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx":"react",
"strict": true,
"noEmit": true
},
"include": [
"./src"
]
}

Ключевые настройки:

  • "strict": true — включает все строгие проверки типов
  • "jsx": "react" — настройка для работы с React JSX
  • "noEmit": true — отключает генерацию JavaScript файлов (используется только для проверки типов)

8. Обработка ошибок с типизацией

TypeScript позволяет типизировать обработку ошибок, что делает код более предсказуемым:

// Путь: src/components/__generate/Doctor/Doctor/DoctorList.tsx
const [error, setError] = useState<Error | null>(null)

const { data, loading, error: queryError } = useSearchDoctorQuery({
variables: {
cond: (filterStr && filterStr !== 'undefined' && selectedDoctor !== null )
? `it.${extractParamName(filterStr)}.id=='${extractParamValue(filterStr)}'`
: null
}
})

if (loading) return (<Spin tip="Loading..." />);
if (queryError) {
return (<ErrorModal error={queryError} setError={setError} />)
}

Здесь Error | null четко определяет, что состояние ошибки может быть либо объектом ошибки, либо null, что помогает избежать runtime ошибок при обращении к свойствам ошибки.

Это одни из многих примеров, как TypeScript помогает создавать более безопасный и надежный код.

Заключение

В этом разделе мы рассмотрели основы TypeScript и его практическое применение в проекте React-приложения медицинской клиники. Мы узнали, как использовать типы, интерфейсы, дженерики, условную типизацию, декораторы и асинхронные операции. Эти концепции помогают создавать более безопасный и надежный код, а также облегчают понимание и поддержку проекта.

Ссылки

  1. Официальная документация TypeScript - подробное руководство и справочник
  2. TypeScript Deep Dive - углубленное руководство по TypeScript
  3. TypeScript Playground - онлайн-среда для экспериментов с TypeScript
  4. Definitelyped - репозиторий с типами для JavaScript-библиотек
  5. TSConfig Reference - подробное описание всех опций tsconfig.json