Резюме использования TypeScript в React

внешний интерфейс исходный код TypeScript React.js
Резюме использования TypeScript в React

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Установить какanytype, но тогда мы теряем смысл статической проверки нашего кода.


function handleEvent (event: any) {
  console.log(event.clientY)
}

Только представьте, когда мы регистрируемTouchсобытие, затем ошибочно переданное в обработчик событияeventобъект, чтобы получить егоclientYстоимость имущества, здесь мы поставилиeventУстановить какanytype, который заставляет 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