Хабрахабр

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

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

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

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

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

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

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

import React from 'react'
import PropTypes from 'prop-types' export function StandardComponent() { 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.

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

Теги
Показать больше

Похожие статьи

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

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

Кнопка «Наверх»
Закрыть