Использование Typescript с React – руководство для новичков

Друзья, в преддверии выходных хотим поделиться с вами еще одной интересной публикацией, которую хотим приурочить к запуску новой группы по курсу «Разработчик JavaScript».

Потратив последние несколько месяцев на разработку приложений на React и библиотек с использованием Typescript, я решил поделиться некоторыми вещами, которые узнал за это время. В этом руководстве я расскажу вам про шаблоны, которые я использую для Typescript и React в 80% случаев.

Стоит ли изучать Typescript для разработки приложений на React? Стоит, еще как стоит! Для себя я осознал на практике, что строгая типизация приводит к написанию гораздо более надежного кода, быстрой разработке, особенно в крупных проектах. Сначала вы, вероятно, будете разочарованы, но по мере работы вы обнаружите, что хотя бы минимальный шаблон действительно будет очень кстати.

И если вы застряли на чем-то, помните, что вы всегда можете типизировать что- нибудь как any. Any – ваш новый друг. А теперь перейдем непосредственно к примерам.

Ваш базовый компонент react с typescript

Как же выглядит стандартный компонент react на typescript? Давайте сравним его с компонентом react в javascript.

import React from 'react' import PropTypes from 'prop-types'  export function StandardComponent({ children, title = 'Dr.' }) {   return (     <div>       {title}: {children}     </div>   ) }  StandardComponent.propTypes = {   title: PropTypes.string,   children: PropTypes.node.isRequired, }

А теперь версия на typescript:

import * as React from 'react'  export interface StandardComponentProps {   title?: string   children: React.ReactNode }  export function StandardComponent({   children,   title = 'Dr.', }: StandardComponentProps) {   return (     <div>       {title}: {children}     </div>   ) }

Очень похоже, не так ли? Мы заменили propTypes на интерфейс typescript.

Заголовок prop остается необязательным, в то время как все еще требуется prop наследника. Мы экспортировали наш интерфейс на случай, если другому компоненту понадобится на него ссылка.

Расширение стандартных атрибутов HTML

Если мы хотим, чтобы родительский компонент мог обеспечивать дополнительные типизированные атрибуты div, такие как aria-hidden, style или className, мы можем определить их в interface или же расширить встроенный интерфейс. В приведенном ниже примере, мы говорим, что наш компонент принимает любые стандартные свойства div в дополнение к заголовку и наследникам.

import * as React from 'react'  export interface SpreadingExampleProps   extends React.HTMLAttributes<HTMLDivElement> {   title?: string   children: React.ReactNode }  export function SpreadingExample({   children,   title = 'Dr.',   ...other }: SpreadingExampleProps) {   return (     <div {...other}>       {title}: {children}     </div>   ) } 

Обработка событий

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

export interface EventHandlerProps {   onClick: (e: React.MouseEvent) => void }  export function EventHandler({ onClick }: EventHandlerProps) {   // handle focus events in a separate function   function onFocus(e: React.FocusEvent) {     console.log('Focused!', e.currentTarget)   }    return (     <button       onClick={onClick}       onFocus={onFocus}       onKeyDown={e => {         // When using an inline function, the appropriate argument signature         // is provided for us       }}     >       Click me!     </button>   ) }

Не знаете, какую сигнатуру аргумента использовать? В редакторе наведите курсором на соответствующее свойство обработчика событий.

Использование дженериков с компонентами react

Это более продвинутая функция, но она действительно мощная. Как правило, вы определяете типы данных в компонентах react конкретными атрибутами. Предположим, вашему компоненту требуется объект profile.

interface ProfileType {   name: string   image: string   age: number | null }  interface ProfilesProps {   profiles: Array<ProfileType> }  function Profiles(props: ProfilesProps) {   // render a set of profiles } 

А теперь давайте представим, что у вас есть компонент, который может принимать массив любого типа. Дженерики похожи на отправку посылки по почте. Курьер (наш компонент) не должен знать содержимое посылки, которую вы отправляете, но отправитель (родительский компонент) ожидает, что получатель получит содержимое, которое он отправил.

Мы реализуем это так:

interface GenericsExampleProps<T> {   children: (item: T) => React.ReactNode   items: Array<T> }  export function GenericsExample<T>({   items,   children, }: GenericsExampleProps<T>) {   return (     <div>       {items.map(item => {         return children(item)       })}     </div>   ) } 

Немного странный пример… тем не менее он демонстрирует суть. Компонент принимает массив элементов любого типа, проходит по нему и вызывает функцию children как рендер функцию с элементом массива. Когда наш родительский компонент предоставляет колбэк рендера как наследника, элемент будет типизирован правильно!

Не поняли? Это нормально. Я сам еще не разобрался с дженериками до конца, но вам вряд ли понадобится понимать их досконально. Однако, чем больше вы будете работать с typescript, тем больше в этом будет смысла.

Типизация хуков (hooks)

Хуки в основном работают из коробки. Двумя исключениями могут быть только useRef и useReducer. Пример ниже показывает, как мы можем типизировать ссылки (refs).

import * as React from 'react'  interface HooksExampleProps {}  export function HooksExample(props: HooksExampleProps) {   const [count, setCount] = React.useState(0)   const ref = React.useRef<HTMLDivElement | null>(null)    // start our timer   React.useEffect(     () => {       const timer = setInterval(() => {         setCount(count + 1)       }, 1000)        return () => clearTimeout(timer)     },     [count]   )    // measure our element   React.useEffect(     () => {       if (ref.current) {         console.log(ref.current.getBoundingClientRect())       }     },     [ref]   )    return <div ref={ref}>Count: {count}</div> } 

Наше состояние автоматически типизируется, но мы вручную типизировали ref, чтобы показать, что он будет иметь значение null или содержать элемент div. Когда мы обращаемся к ref в функции useEffect, нам нужно убедиться, что он не равен null.

Типизация редуктора

С редуктором немного сложнее, но если он правильно типизирован, то это замечательно.

// Yeah, I don't understand this either. But it gives us nice typing // for our reducer actions. type Action<K, V = void> = V extends void ? { type: K } : { type: K } & V  // our search response type interface Response {   id: number   title: string }  // reducer actions. These are what you'll "dispatch" export type ActionType =   | Action<'QUERY', { value: string }>   | Action<'SEARCH', { value: Array<Response> }>  // the form that our reducer state takes interface StateType {   searchResponse: Array<Response>   query: string }  // our default state const initialState: StateType = {   searchResponse: [],   query: '', }  // the actual reducer function reducer(state: StateType, action: ActionType) {   switch (action.type) {     case 'QUERY':       return {         ...state,         query: action.value,       }      case 'SEARCH':       return {         ...state,         searchResponse: action.value,       }   } }  interface ReducerExampleProps {   query: string }  export function ReducerExample({ query }: ReducerExampleProps) {   const [state, dispatch] = React.useReducer(reducer, initialState)    React.useEffect(     () => {       if (query) {         // emulate async query         setTimeout(() => {           dispatch({             type: 'SEARCH',             value: [{ id: 1, title: 'Hello world' }],           })         }, 1000)       }     },     [query]   )    return state.searchResponse.map(response => (     <div key={response.id}>{response.title}</div>   )) }

Использование typeof и keyof чтобы типизировать варианты компонента

Предположим, что нам нужна кнопка, которая может иметь различный внешний вид, каждый из которых определен в объекте с набором ключей и стилей, например:

const styles = {   primary: {     color: 'blue',   },   danger: {     color: 'red',   }, }

Наш компонент кнопки должен принимать свойство type, которое может быть
любым ключом из объекта styles (например, «primary» или «danger»). Мы можем типизировать его достаточно просто:

const styles = {   primary: {     color: 'blue',   },   danger: {     color: 'red',   }, }  // creates a reusable type from the styles object type StylesType = typeof styles  // ButtonType = any key in styles export type ButtonType = keyof StylesType  interface ButtonProps {   type: ButtonType }  export function Button({ type = 'primary' }: ButtonProps) {   return <button style={styles[type]}>My styled button</button> }

Эти примеры помогут вам пройти 80% пути. Если вы застряли, то очень часто стоит
просто взглянуть на существующие примеры с открытым исходным кодом.

Sancho UI — это набор компонентов react,
построенный с помощью typescript и emotion.
Blueprint — это еще один набор компонентов
react, построенный на typescript.

Ну и по устоявшейся традиции ждем ваши комментарии.

FavoriteLoadingДобавить в избранное
Posted in Без рубрики

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *