Лучшие методы использования Typescript с React

1656605535 luchshie metody ispolzovaniya typescript s react

Кристофер Диггинс

NWzcNzdUlOZrJ8FguMaiBqh5RT7LWNB3a79u
Типичная теория для победы!

Существует множество инструментов и пособий, которые помогут разработчикам начать писать простые программы React с помощью TypeScript. Однако наилучшие методы использования TypeScript в большей программе React менее ясны.

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

В Clemex мы разрабатываем приложения для вычислительной микроскопии. Недавно мы перенесли интерфейс React для одного из наших приложений JavaScript на TypeScript. В общем, мы очень довольны конечным результатом. Консенсус состоит в том, что нашу кодовую базу теперь легче понять и поддерживать.

Однако наш переход не обошелся без некоторых проблем. В этой статье рассказывается о некоторых проблемах, с которыми мы столкнулись, и о том, как мы их преодолели.

Проблемы в первую очередь связаны с пониманием сигнатур типов React API, особенно компонентов высшего порядка. Как мы можем правильно устранить ошибки типа, сохранив преимущества TypeScript?

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

Поиск типов для библиотеки

TypeScript может легко импортировать любую библиотеку JavaScript. Но без объявления типов для импортируемых значений и функций мы не получаем всех преимуществ от использования TypeScript.

К счастью, TypeScript позволяет легко определять инструкции типов для библиотек JavaScript в виде файлов объявления типов.

Только несколько проектов сегодня предлагают определение типа TypeScript непосредственно с проектом. Однако для многих библиотек обычно можно найти обновленный файл определения типа в @types пространство имен организации.

Например, если вы заглянете в шаблон TypeScript React package.jsonвы можете увидеть, что мы используем следующие файлы определения типов:

"@types/jest": "^22.0.1","@types/node": "^9.3.0","@types/react": "^16.0.34","@types/react-dom": "^16.0.3","@types/redux-logger": "^3.0.5"

Единственным недостатком использования внешних деклараций типов является то, что отслеживать ошибки, возникающие из-за несоответствия версий или незначительные ошибки в самих файлах объявления типов, может быть немного раздражает. Файлы объявления типа не всегда поддерживаются авторами оригинальной библиотеки.

Проверка свойств и полей состояния во время компиляции

Одним из главных преимуществ использования TypeScript в программе React является то, что компилятор (и IDE, если настроен правильно) может проверить все необходимые свойства, предоставленные компоненту.

Он также может проверить, имеют ли они правильный тип. Это заменяет необходимость проверки при выполнении, как это предусмотрено prop-types библиотека.

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

import * as React from ‘react’;
export interface CounterDisplayProps {  value: number;  label: string;}
export class CounterDisplay extends React.PureComponent<CounterDisplayProps> {   render(): React.ReactNode {   return (     <div>       The value of {this.props.label} is {this.props.value}      </div>    );}

Компоненты как классы или функции

С помощью React можно определить новый компонент двумя способами: как функцию или как класс. Типы этих двух видов компонентов:

  1. Классы компонентов :: React.ComponentClass<;P>
  2. Функциональные компоненты без состояния (SFC) ::React.StatelessComponent<;P>

Классы компонентов

Тип класса – это новая концепция для разработчиков с фоном C++/C#/Java. Класс имеет особый тип, отдельный от типа экземпляра класса. Он определяется в терминах функции конструктора. Понимание этого является ключом к пониманию подписей типов и некоторых ошибок типа, которые могут возникнуть.

А ComponentClass является типом функции-конструктора, возвращающей объект, являющийся экземпляром a Component. Если описать некоторые детали, суть ComponentClass определение типа:

interface ComponentClass<P = {}> {  new (props: P, context?: any): Component<P, ComponentState>;}

Компоненты без состояния (SFC)

А StatelessComponent (также известен как SFC) – это функция, принимающая объект свойств, необязательный список дочерних компонентов и необязательный объект контекста. Он возвращает или a ReactElement или null.

Несмотря на то, что может подсказать название, а StatelessComponent не имеет отношения к a Component типа.

Упрощенный вариант определения типа a StatelessComponent и SFC псевдоним:

interface StatelessComponent<P = {}> {  (props: P & { children?: ReactNode }, context?: any):   ReactElement<any> | null;}
type SFC<P = {}> = StatelessComponent<P>;

До React 16 SFC были достаточно медленными. По-видимому, это улучшилось с React 16. Тем не менее, из-за стремления к последовательному стилю кодирования в нашей базе кода, мы продолжаем определять компоненты как классы.

Чистые и нечистые компоненты

Есть два разных типа Component: чистый и нечистый.

Термин «чистый» имеет очень специфическое значение во фреймворке React, не связанное с термином в информатике.

А PureComponent является компонентом, обеспечивающим реализацию по умолчаниюshouldComponentUpdate функция (которая делает неглубокое сравнение this.stateи this.props).

Вопреки распространенному заблуждению, а StatelessComponent не чист, а а PureComponent может иметь государство.

Компоненты с определением состояния могут (и должны) происходить от React.PureComponent

Как указано выше, компонент React с состоянием все еще можно считать a Pure component в соответствии с народным языком React. На самом деле, это хорошая идея получать компоненты, имеющие внутреннее состояние React.PureComponent.

Ниже приведено на основе популярного пособия Петра Витка из TypeScript, но с такими небольшими изменениями:

  1. The setState функция использует обратный вызов для обновления состояния на основе предыдущего состояния в соответствии с документацией React.
  2. Мы следуем из React.PureComponent поскольку он не переопределяет функции жизненного цикла
  3. The State Тип определяется как класс, потому он может иметь инициализатор.
  4. Мы не назначаем свойства локальным переменным в функции визуализации, поскольку это нарушает принцип DRY и добавляет ненужные строки кода.
import * as React from ‘react’;export interface StatefulCounterProps {  label: string;}
// By making state a class we can define default values.class StatefulCounterState {  readonly count: number = 0;};
// A stateful counter can be a React.PureComponentexport class StatefulCounter  extends React.PureComponent<StatefulCounterProps, StatefulCounterState>{  // Define  readonly state = new State();
  // Callbacks should be defined as readonly fields initialized with arrow functions, so you don’t have to bind them  // Note that setting the state based on previous state is done using a callback.  readonly handleIncrement = () => {    this.setState((prevState) => {       count: prevState.count + 1 } as StatefulCounterState);  }
  // We explicitly include the return type  render(): React.ReactNode {    return (      <div>        <span>{this.props.label}: {this.props.count} </span>        <button type=”button” onClick={this.handleIncrement}>           {`Increment`}        </button>      </div>     );  }}

Функциональные компоненты React без состояния не являются чистыми компонентами

Несмотря на распространенную ошибку, функциональные компоненты без состояния (SFC) не являются чистыми компонентами, что означает, что они отображаются каждый раз, независимо от того, изменились свойства или нет.

Введение компонентов высшего порядка

Многие библиотеки, используемые с приложениями React, обеспечивают функции, принимающие определение компонента и возвращающие новое определение компонента. Эти называются Компоненты высшего порядка (или HOCс сокращенно).

Компонент высшего порядка может возвращать a StatelessComponent или а ComponentClass зависимо от того, как это определяется.

Путаница экспорта по умолчанию

Распространенным шаблоном в приложениях JavaScript React является определение компонента с определенным названием (скажем MyComponent) и храните его локальным для модуля. Затем экспортируйте по умолчанию результат обертывания его одним или несколькими HOC.

Анонимный компонент импортируется в программу как MyComponent. Это вводит в заблуждение, поскольку программист повторно использует одно и то же имя для двух очень разных вещей!

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

В нашей команде мы оказали полезным предоставить имена для обоих определенных компонентов, хранящихся локально для файла (например MyComponentBase) и явно назвать константу с экспортируемым компонентом (например export const MyComponent = injectIntl(MyComponentBase);).

Кроме того, это более четко, это позволяет избежать проблемы псевдонимов определения, что облегчает понимание и рефакторинг кода.

HOC, вводящие свойства

Большинство HOC вводят в ваши компоненты свойства, которые не должны предоставляться потребителем вашего компонента. Некоторые примеры, которые мы используем в нашей программе, включают в себя:

  • Из material-ui: withStyles
  • Из redux-формы: reduxForm
  • С react-intl: injectIntl
  • Из react-redux: connect

Внутренние, внешние и введенные свойства

Чтобы лучше понять связь между компонентом, возвращаемым из функции HOC, и компонентом, как он определен, попробуйте эту полезную ментальную модель:

Подумайте о свойствах, которые ожидается предоставить клиенту компонента как внешние свойстваи все свойства, видимые для определения компонента (например, свойства, используемые в функции визуализации), как внутренние свойства. Разница между этими двумя наборами свойств заключается в введенные свойства.

Оператор пересечения типа

В TypeScript мы можем комбинировать типы так, как мы хотим для свойств, используя оператор уровня типа, который называется оператором пересечения (&). Оператор пересечения объединяет поля одного типа с полями другого типа.

interface LabelProp {  label: string;}
interface ValueProp {  value: number;}
// Has both a label field and a value fieldtype LabeledValueProp = LabelProp & ValueProp;

Для тех, кто знаком с теорией множеств, вам может быть интересно, почему это не считается оператором объединения. Это потому, что он является пересечением множеств всех возможных значений, удовлетворяющих двум ограничениям типа.

Определение свойств для завернутого компонента

Определяя компонент, обернутый компонентом высшего порядка, мы должны придать внутренние свойства базовому типу (например React.PureComponent<;P)).

Однако мы не хотим определять все это в одном экспортированном интерфейсе, поскольку эти свойства не относятся к клиенту компонента: им нужны только внешние свойства.

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

interface MyProperties {  value: number;}
class MyComponentBase extends React.PureComponent<MyProperties & InjectedIntlProps> {  // Now has intl as a property  // ...}
export const MyComponent = injectIntl(MyComponentBase); // Has the type React.Component<MyProperties>;

Функция подключения React-Redux

The connect функция библиотеки React-Redux используется для получения свойств, необходимых компоненту из хранилища Redux, а также для отображения некоторых обратных вызовов диспетчеру (инициирующего действия, запускающие обновление хранилища).

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

  1. mapStateToProps
  2. mapDispatchToProps

Обе эти функции предоставляют собственное подмножество внутренних свойств для определения компонента.

Однако тип подписи connect является частным случаем через способ написания типа. Он может определить тип для вводимых свойств, а также вывести тип для оставшихся свойств.

Это оставляет нам два варианта:

  1. Мы можем разделить интерфейс на внутренние свойства mapStateToProps а другой для mapDispatchToProps.
  2. Мы можем позволить системе типов определить тип для нас.

В нашем случае нам пришлось конвертировать примерно 50 подключен компоненты от JavaScript к TypeScript.

Они уже имели формальные интерфейсы, сгенерированные из оригинального определения PropTypes (благодаря инструменту с открытым кодом, который мы использовали от Lyft).

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

В конце концов, используя connect правильно позволил клиентам правильно определить типы. Мы довольны, но можем пересмотреть выбор.

Помогает функции React-Redux подключать к выводу о типах

Кажется, что механизм вывода типа TypeScript иногда требует деликатного прикосновения. The connect функция, похоже, является одним из следующих случаев. Теперь, надеюсь, это не случай программирования культа карго, но вот шаги, которые мы предпринимаем, чтобы убедиться, что компилятор сможет определить тип.

  • Мы не предоставляем тип для mapStateToProps или mapDispatchToProps функции, мы просто позволяем компилятору сделать их вывод.
  • Мы определяем оба mapStateToProps и mapDispatchToProps как функции стрелки, предназначенные для const переменные.
  • Мы используем connect как внешний компонент высшего порядка
  • Мы не объединяем несколько компонентов высшего порядка с помощью a compose функция.

Свойства, подключенные к магазину в mapStateToProps и mapDispatchToProps нельзя объявлять как необязательное, иначе вы можете получить ошибки типа в выводимом типе.

Заключительные слова

В конце концов, мы обнаружили, что использование TypeScript облегчило понимание наших программ. Это помогло углубить наше понимание React и архитектуры нашего собственного приложения.

Правильное использование TypeScript в контексте дополнительных библиотек, предназначенных для расширения React, требовало дополнительных усилий, но оно того стоит.

Если вы только начинаете работу с TypeScript в React, вам будут полезны следующие руководства:

После этого рекомендую прочесть следующие статьи:

Благодарности

Большое спасибо членам команды Clemex за их сотрудничество над этой статьей, совместную работу, чтобы выяснить, как использовать TypeScript в лучших возможностях в программах React, и разработку проекта TypeScript React Template с открытым исходным кодом на GitHub.

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

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