TypeScript — это надмножество типов JS, поддерживающее дженерики, типы, пространства имен, перечисления и другие функции, восполняющие недостатки JS при разработке крупномасштабных приложений. Так какие искры могут столкнуться при совместном использовании TypeScript и React? Далее давайте рассмотрим возможность написания компонентов React в TypeScript 2.8+.
предисловие
В последние годы голос переднего плана для TypeScript становится все выше и выше, и TypeScript в новом проекте Райана Даля Deno также стал обязательным навыком. Чжиху часто видит такие вещи, как «Поскольку я использовал TypeScript, я не больше не хотите его использовать. JavaScript больше нет», «Пока вы используете ES6, доступ к TypeScript можно получить почти без барьеров», «TypeScript может заменить JS в любом сценарии» и подобные ответы, и постепенно пытайтесь использовать его в команда с менталитетом слушать других, что лучше использовать его самостоятельно TypeScript используется в некоторых низкоуровневых поддерживаемых проектах.
Опыт программирования с использованием TypeScript действительно классный, когда вы печатаете на клавиатуре.
В то время следующая серия подсказок действительно полна счастья на экране, качество кода и эффективность значительно улучшились, и я больше не хочу использовать JavaScript.
При использовании только TypeScript не так много ям, но все же есть много ям при использовании в сочетании с некоторыми фреймворками. Например, при использовании таких фреймворков, как React и Vue, комбинация с TypeScript станет серьезным препятствием. Вам нужно проверить Определение некоторых сложных типов в файле объявлений d.ts. В этой статье в основном рассказывается о некоторых проблемах с определением типов, которые часто возникают при объединении с React.Рекомендуется прочитать эту статью, чтобы иметь определенное представление о TypeScript, потому что базовые знания некоторых TypeScript не будут слишком подробно объясняться в этой статье. статья.
Напишите свой первый компонент TSX
import React from 'react'
import ReactDOM from 'react-dom'
const App = () => {
return (
<div>Hello world</div>
)
}
ReactDOM.render(<App />, document.getElementById('root')
Следующая ошибка возникает при запуске вышеуказанного кода
-
Cannot find module 'react'
-
Cannot find module 'react-dom'
Причина ошибки связана сReact
иReact-dom
Не разработано с TS, поэтому TS не знаетReact
,React-dom
Тип модуля и то, что модуль экспортирует. В настоящее время необходимо ввести файл объявления .d.ts. К счастью, файлы объявлений этих общих модулей были выпущены в сообществе.DefinitelyTyped.
УстановитьReact
,React-dom
файл определения типа
Установить с помощью пряжи
yarn add @types/react
yarn add @types/react-dom
Установить с помощью нпм
npm i @types/react -s
npm i @types/react-dom -s
Разработка компонентов с отслеживанием состояния
Мы определяем компонент приложения с отслеживанием состояния,props
,state
следующее.
Props
props | тип | Это обязательно |
---|---|---|
color |
string | да |
size |
string | нет |
State
props | тип |
---|---|
count |
string |
Используя TSX, мы можем написать
import * as React from 'react'
interface IProps {
color: string,
size?: string,
}
interface IState {
count: number,
}
class App extends React.Component<IProps, IState> {
public state = {
count: 1,
}
public render () {
return (
<div>Hello world</div>
)
}
}
TypeScript может анализировать JSX, в полной мере использовать свою собственную функцию статической проверки и использовать дженерики дляProps
,State
определение типа. использовать после определенияthis.state
иthis.props
Вы получаете лучший интеллект в редакторе, и выполняется проверка типов.
Итак, как реализован общий тип компонента?Мы можем обратиться к файлу определения типа React.node_modules/@types/react/index.d.ts
.
можно увидеть здесьComponent
Этот общий класс,P
представлятьProps
тип,S
представлятьState
тип.
class Component<P, S> {
readonly props: Readonly<{ children?: ReactNode }> & Readonly<P>;
state: Readonly<S>;
}
Общий класс Component получаетP
,S
После этих двух универсальных переменных свойство только для чтенияprops
Тип объявлен как перекрестный типReadonly<{ children?: ReactNode }> & Readonly<P>;
заставь его поддерживатьchildren
и что мы декларируемcolor
,size
.
Псевдонимы типов через дженерикиReadonly
будетprops
Все свойства установлены как свойства только для чтения.
Исходный код реализации только для чтенияnode_modules/typescript/lib/lib.es5.d.ts
.
так какprops
свойство установлено только для чтения, поэтомуthis.props.size = 'sm'
При обновлении ТС чекер выдаст ошибку,Error:(23, 16) TS2540: Cannot assign to 'size' because it is a constant or a read-only property
Запретить прямые обновленияstate
Реагироватьstate
Обновление необходимо использоватьsetState
метод, но мы часто злоупотребляем, напрямуюstate
свойства обновляются.
this.state.count = 2
В разработке иногда случайно пишется приведенный выше код, и после выполненияstate
Обновления нет, в это время мы будем очень сумасшедшими, думая, где я ошибаюсь?
Теперь с помощью TypeScript мы можем сделать это, добавивstate
,а такжеstate
Для следующих свойств установлен тип только для чтения, что предотвращает прямые обновления.state
.
import * as React from 'react'
interface IProps {
color: string,
size?: string,
}
interface IState {
count: number,
}
class App extends React.PureComponent<IProps, IState> {
public readonly state: Readonly<IState> = {
count: 1,
}
public render () {
return (
<div>Hello world</div>
)
}
public componentDidMount () {
this.state.count = 2
}
}
export default App
На данный момент мы напрямую модифицируемstate
значение, TypeScript сразу сообщит нам об ошибке,Error:(23, 16) TS2540: Cannot assign to 'count' because it is a constant or a read-only property.
.
Разработка компонентов без сохранения состояния
Props
props | тип | Это обязательно |
---|---|---|
children |
ReactNode | нет |
onClick |
function | да |
SFC
тип
В файле объявления React уже определенSFC
Тип, используя этот тип, мы можем избежать повторения определенияchildren
,propTypes
,contextTypes
,defaultProps
,displayName
тип.
Реализовать исходный кодnode_modules/@types/react/index.d.ts
.
type SFC<P = {}> = StatelessComponent<P>;
interface StatelessComponent<P = {}> {
(props: P & { children?: ReactNode }, context?: any): ReactElement<any> | null;
propTypes?: ValidationMap<P>;
contextTypes?: ValidationMap<any>;
defaultProps?: Partial<P>;
displayName?: string;
}
использоватьSFC
Займитесь разработкой компонентов без сохранения состояния.
import { SFC } from 'react'
import { MouseEvent } from 'react'
import * as React from 'react'
interface IProps {
onClick (event: MouseEvent<HTMLDivElement>): void,
}
const Button: SFC<IProps> = ({onClick, children}) => {
return (
<div onClick={onClick}>
{ children }
</div>
)
}
export default Button
обработка событий
Мы часто используем обработчики событий при регистрации событийevent
объект события, например, при использовании событий мыши мы передаемclientX
,clientY
чтобы получить координаты указателя.
Каждый может думать непосредственноevent
Установить какany
type, но тогда мы теряем смысл статической проверки нашего кода.
function handleEvent (event: any) {
console.log(event.clientY)
}
Только представьте, когда мы регистрируемTouch
событие, затем ошибочно переданное в обработчик событияevent
объект, чтобы получить егоclientY
стоимость имущества, здесь мы поставилиevent
Установить какany
type, который заставляет TypeScript компилироваться без сообщения об ошибке, когда мы передаемevent.clientY
Возникла проблема при доступе, т.к.Touch
событиеevent
объект неclientY
это свойство.
пройти черезinterface
правильноevent
Писать объявление типа объекта — пустая трата времени, к счастью, файл объявления React предоставляетEvent
Объявление типа объекта.
Тип объекта события события
Общие типы объектов события Event:
-
ClipboardEvent<T = Element>
объект события буфера обмена -
DragEvent<T = Element>
перетащить объект события -
ChangeEvent<T = Element>
Изменить объект события -
KeyboardEvent<T = Element>
объект события клавиатуры -
MouseEvent<T = Element>
Объект события мыши -
TouchEvent<T = Element>
Коснитесь объекта события -
WheelEvent<T = Element>
объект события колесо -
AnimationEvent<T = Element>
объект события анимации -
TransitionEvent<T = Element>
объект события перехода
Пример:
import { MouseEvent } from 'react'
interface IProps {
onClick (event: MouseEvent<HTMLDivElement>): void,
}
MouseEvent
Введите исходный код реализацииnode_modules/@types/react/index.d.ts
.
interface SyntheticEvent<T = Element> {
bubbles: boolean;
/**
* A reference to the element on which the event listener is registered.
*/
currentTarget: EventTarget & T;
cancelable: boolean;
defaultPrevented: boolean;
eventPhase: number;
isTrusted: boolean;
nativeEvent: Event;
preventDefault(): void;
isDefaultPrevented(): boolean;
stopPropagation(): void;
isPropagationStopped(): boolean;
persist(): void;
// If you thought this should be `EventTarget & T`, see https://github.com/DefinitelyTyped/DefinitelyTyped/pull/12239
/**
* A reference to the element from which the event was originally dispatched.
* This might be a child element to the element on which the event listener is registered.
*
* @see currentTarget
*/
target: EventTarget;
timeStamp: number;
type: string;
}
interface MouseEvent<T = Element> extends SyntheticEvent<T> {
altKey: boolean;
button: number;
buttons: number;
clientX: number;
clientY: number;
ctrlKey: boolean;
/**
* See [DOM Level 3 Events spec](https://www.w3.org/TR/uievents-key/#keys-modifier). for a list of valid (case-sensitive) arguments to this method.
*/
getModifierState(key: string): boolean;
metaKey: boolean;
nativeEvent: NativeMouseEvent;
pageX: number;
pageY: number;
relatedTarget: EventTarget;
screenX: number;
screenY: number;
shiftKey: boolean;
}
EventTarget
Введите исходный код реализацииnode_modules/typescript/lib/lib.dom.d.ts
.
interface EventTarget {
addEventListener(type: string, listener: EventListenerOrEventListenerObject | null, options?: boolean | AddEventListenerOptions): void;
dispatchEvent(evt: Event): boolean;
removeEventListener(type: string, listener?: EventListenerOrEventListenerObject | null, options?: EventListenerOptions | boolean): void;
}
Из исходного кода мы видимMouseEvent<T = Element>
наследоватьSyntheticEvent<T>
, и черезT
получитьDOM
тип элемента,currentTarget
Тип определяетсяEventTarget & T
Композиция крестового типа.
тип обработчика событий
Когда мы определяем обработчик событий, есть ли более удобный способ определить тип его функции? Ответ предоставляется с использованием файла декларации ReactEventHandler
Псевдонимы типов через разные событияEventHandler
Псевдоним типа для определения типа функции обработчика событий.
EventHandler
Введите исходный код реализацииnode_modules/@types/react/index.d.ts
.
type EventHandler<E extends SyntheticEvent<any>> = { bivarianceHack(event: E): void }["bivarianceHack"];
type ReactEventHandler<T = Element> = EventHandler<SyntheticEvent<T>>;
type ClipboardEventHandler<T = Element> = EventHandler<ClipboardEvent<T>>;
type DragEventHandler<T = Element> = EventHandler<DragEvent<T>>;
type FocusEventHandler<T = Element> = EventHandler<FocusEvent<T>>;
type FormEventHandler<T = Element> = EventHandler<FormEvent<T>>;
type ChangeEventHandler<T = Element> = EventHandler<ChangeEvent<T>>;
type KeyboardEventHandler<T = Element> = EventHandler<KeyboardEvent<T>>;
type MouseEventHandler<T = Element> = EventHandler<MouseEvent<T>>;
type TouchEventHandler<T = Element> = EventHandler<TouchEvent<T>>;
type PointerEventHandler<T = Element> = EventHandler<PointerEvent<T>>;
type UIEventHandler<T = Element> = EventHandler<UIEvent<T>>;
type WheelEventHandler<T = Element> = EventHandler<WheelEvent<T>>;
type AnimationEventHandler<T = Element> = EventHandler<AnimationEvent<T>>;
type TransitionEventHandler<T = Element> = EventHandler<TransitionEvent<T>>;
EventHandler
перениматьE
, который представляет обработчик события вevent
Тип объекта.
bivarianceHack
Определения типов для функций обработчиков событий, которые получаютevent
объект, а его тип — полученная универсальная переменнаяE
тип, возвращаемое значениеvoid
.
Пример:
interface IProps {
onClick : MouseEventHandler<HTMLDivElement>,
}
Тип обещания
Мы часто используем его при выполнении асинхронных операций.async
функция, когда функция вызываетсяreturn
ОдинPromise
объект, вы можете использоватьthen
метод для добавления функции обратного вызова.
Promise<T>
является универсальным типом,T
Общие переменные используются для определения использованияthen
Тип параметра первой функции обратного вызова (onfulfilled), полученной методом.
Пример:
interface IResponse<T> {
message: string,
result: T,
success: boolean,
}
async function getResponse (): Promise<IResponse<number[]>> {
return {
message: '获取成功',
result: [1, 2, 3],
success: true,
}
}
getResponse()
.then(response => {
console.log(response.result)
})
Мы сначала объявляемIResponse
Общий интерфейс используется для определенияresponse
тип, черезT
общая переменная для определенияresult
тип.
Затем объявите асинхронную функциюgetResponse
и определите тип возвращаемого значения функции какPromise<IResponse<number[]>>
.
последний звонокgetResponse
метод вернетpromise
тип, черезthen
Звоните, в это времяthen
Параметр первой функции обратного вызова, полученной методомresponse
имеет тип,{ message: string, result: number[], success: boolean}
.
Promise<T>
Реализовать исходный кодnode_modules/typescript/lib/lib.es5.d.ts
.
interface Promise<T> {
/**
* Attaches callbacks for the resolution and/or rejection of the Promise.
* @param onfulfilled The callback to execute when the Promise is resolved.
* @param onrejected The callback to execute when the Promise is rejected.
* @returns A Promise for the completion of which ever callback is executed.
*/
then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<TResult1 | TResult2>;
/**
* Attaches a callback for only the rejection of the Promise.
* @param onrejected The callback to execute when the Promise is rejected.
* @returns A Promise for the completion of the callback.
*/
catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): Promise<T | TResult>;
}
Советы по универсальным инструментам
typeof
Как правило, мы сначала определяем тип, а затем назначаем его для использования, но используемtypeof
Мы можем изменить порядок использования.
const options = {
a: 1
}
type Options = typeof options
Используйте типы строковых литералов, чтобы ограничить строковые параметры фиксированными значениями.
ограничениеprops.color
Значение может быть только строкойred
,blue
,yellow
.
interface IProps {
color: 'red' | 'blue' | 'yellow',
}
Используйте тип числового литерала, чтобы ограничить значение числового параметра фиксированным значением.
ограничениеprops.index
Значение может быть только числом0
,1
,2
.
interface IProps {
index: 0 | 1 | 2,
}
использоватьPartial
положить всеprops
свойства становятся необязательными
Partial
Реализовать исходный кодnode_modules/typescript/lib/lib.es5.d.ts
type Partial<T> = { [P in keyof T]?: T[P] };
Приведенный выше код означаетkeyof T
получитьT
все имена свойств, затемin
пройти, присвоить значенияP
, НаконецT[P]
Получить значение соответствующего атрибута, средний?
Используется для установки в качестве необязательного значения.
еслиprops
Все значения свойств являются необязательными, и мы можем использоватьPartial
Это достигается.
import { MouseEvent } from 'react'
import * as React from 'react'
interface IProps {
color: 'red' | 'blue' | 'yellow',
onClick (event: MouseEvent<HTMLDivElement>): void,
}
const Button: SFC<Partial<IProps>> = ({onClick, children, color}) => {
return (
<div onClick={onClick}>
{ children }
</div>
)
использоватьRequired
положить всеprops
атрибуты установлены на обязательные
Required
Реализовать исходный кодnode_modules/typescript/lib/lib.es5.d.ts
.
type Required<T> = { [P in keyof T]-?: T[P] };
Увидев это, маленькие друзья могут немного смутиться,-?
что он на самом деле делает-?
Функция состоит в том, чтобы поместить необязательные атрибуты?
Удалите, чтобы сделать этот атрибут обязательным параметром, соответствующим+?
, роль-?
Вместо этого сделайте свойства необязательными.
Тип условия
TypeScript 2.8 представил условные типы, которые могут выносить суждения о типах на основе характеристик других типов.
T extends U ? X : Y
оригинальный
interface Id { id: number, /* other fields */ }
interface Name { name: string, /* other fields */ }
declare function createLabel(id: number): Id;
declare function createLabel(name: string): Name;
declare function createLabel(name: string | number): Id | Name;
Использовать тип условия
type IdOrName<T extends number | string> = T extends number ? Id : Name;
declare function createLabel<T extends number | string>(idOrName: T): T extends number ? Id : Name;
Exclude<T,U>
отT
исключить те, которые могут быть назначеныU
тип.
Exclude
Реализовать исходный кодnode_modules/typescript/lib/lib.es5.d.ts
.
type Exclude<T, U> = T extends U ? never : T;
Пример:
type T = Exclude<1|2|3|4|5, 3|4> // T = 1|2|5
В настоящее времяT
Значение типа может быть только1
,2
,5
, при использовании другого значения TS выдаст сообщение об ошибке.
Error:(8, 5) TS2322: Type '3' is not assignable to type '1 | 2 | 5'.
Extract<T,U>
отT
извлеките те, которые могут быть назначеныU
тип.
Извлечь исходный код реализацииnode_modules/typescript/lib/lib.es5.d.ts
.
type Extract<T, U> = T extends U ? T : never;
Пример:
type T = Extract<1|2|3|4|5, 3|4> // T = 3|4
В настоящее время значение типа T может быть только3
,4
, при использовании других значений TS выдаст сообщение об ошибке:
Error:(8, 5) TS2322: Type '5' is not assignable to type '3 | 4'.
Pick<T,K>
отT
снять сериюK
характеристики.
Pick
Реализовать исходный кодnode_modules/typescript/lib/lib.es5.d.ts
.
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
Пример:
Если теперь у нас есть тип, которыйname
,age
,sex
свойства, поддерживаемые только тогда, когда мы хотим сгенерировать новый типname
,age
может быть так:
interface Person {
name: string,
age: number,
sex: string,
}
let person: Pick<Person, 'name' | 'age'> = {
name: '小王',
age: 21,
}
Record<K,T>
будетK
Значения всех свойств в конвертируются вT
тип.
Record
Реализовать исходный кодnode_modules/typescript/lib/lib.es5.d.ts
.
type Record<K extends keyof any, T> = {
[P in K]: T;
};
Пример:
будетname
,age
Все свойства установлены наstring
тип.
let person: Record<'name' | 'age', string> = {
name: '小王',
age: '12',
}
Пропустить (нет встроенного)
от объектаT
исключатьkey
даK
характеристики.
Так как встроенного в TS нет, нам нужно использоватьPick
иExclude
реализовать.
type Omit<T, K> = Pick<T, Exclude<keyof T, K>>
Пример:
исключатьname
Атрибуты.
interface Person {
name: string,
age: number,
sex: string,
}
let person: Omit<Person, 'name'> = {
age: 1,
sex: '男'
}
NonNullable <T>
исключатьT
заnull
,undefined
.
NonNullable
Реализовать исходный кодnode_modules/typescript/lib/lib.es5.d.ts
.
type NonNullable<T> = T extends null | undefined ? never : T;
Пример:
type T = NonNullable<string | string[] | null | undefined>; // string | string[]
ReturnType<T>
получить функциюT
Тип возвращаемого значения. .
ReturnType
Реализовать исходный кодnode_modules/typescript/lib/lib.es5.d.ts
.
type ReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => infer R ? R : any;
infer R
Это эквивалентно объявлению переменной, которая получает тип возвращаемого значения входящей функции.
Пример:
type T1 = ReturnType<() => string>; // string
type T2 = ReturnType<(s: string) => void>; // void