Написание простого приложения React-Redux с помощью Typescript — часть 3

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

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

Давайте использовать Bootstrap для стилизации, потому что все используют Bootstrap, верно?

yarn add bootstrap reactstrap
yarn add --dev @types/reactstrap

Добавьте пакет Bootstrap вместе с Reactstrap, удобной библиотекой компонентов пользовательского интерфейса, вместе с соответствующей информацией о типе. Затем обновите верхний файл Index.tsx, включив в него стили Bootstrap.

# src/index.tsx
import * as React from 'react'
import * as ReactDOM from 'react-dom'
import App from './App'
import 'bootstrap/dist/css/bootstrap.min.css'
import './index.css'
import registerServiceWorker from './registerServiceWorker'
ReactDOM.render(<App />, document.getElementById('root') 
as HTMLElement)
registerServiceWorker()

Докажем, что у нас есть хранилище и мы можем его использовать.

Обычно, когда у вас есть компонент, связанный с магазином, вы называете его «контейнером». Кто-то создает контейнер, который просто подключает компонент к хранилищу, кто-то складывает все это в один файл. А пока, чтобы не усложнять задачу, мы создадим контейнер, который предоставляет макет, логику и подключается к хранилищу в одном файле.

# src/containers/ContactList.tsx
import * as React from 'react'
import { connect } from 'react-redux'
import { Container } from 'reactstrap'
import IContact from '../models/IContact'
import IAppState from '../store/IAppState'
import { getContacts } from '../store/contacts/actions'
interface IContactListProps {
  contacts: IContact[]
  getContacts: typeof getContacts
}
class ContactList extends React.Component<IContactListProps, object> {
  public componentDidMount() {
    this.props.getContacts()
  }
  public render() {
    const { contacts } = this.props
    return (
      <Container>
        <h1>Contact List</h1>
        {contacts.map(this.renderContactResult)}
      </Container>
    )
  }
  private renderContactResult(contact: IContact) {
    return (
      <div key={contact.id}>{contact.name}</div>
    )
  }
}
function mapStateToProps(state: IAppState) {
  return {
    contacts: state.contacts.contacts,
  }
}
export default connect(
  mapStateToProps,
  { getContacts }
)(ContactList)

Он немного длинный, но выглядит как обычный контейнер React Redux. Единственное отличие состоит в том, что функции указывают типы для своих параметров, а сам компонент требует информацию о типе для свойств и состояния.

При написании компонента на Typescript вам необходимо указать информацию о типе для его свойств и любого состояния, которое он содержит, это немного похоже на PropTypes, но вместо работы во время выполнения оно используется во время сборки и помогает вашей IDE обеспечить лучший интеллект. Укажите информацию о типе, а затем используйте ее в объявлении класса, сначала интерфейс реквизита, а затем интерфейс состояния. Второй параметр в этом примере установлен на «объект», потому что нет информации о состоянии, и в конечном итоге это будет пустой объект.

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

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

Теперь мы можем видеть список контактов, давайте добавим экран сведений о контакте. Будучи SPA (одностраничным приложением), нам нужно добавить React Router, чтобы пользователь мог перемещаться по разным частям приложения.

yarn add react-router
yarn add --dev @types/react-router-dom

Если вы добавите только react-router, компилятор Typescript сообщит вам об отсутствии информации о типе и предложит имя добавляемой библиотеки типов.

# src/containers/ContactDetails.tsx
import * as React from 'react'
import { connect } from 'react-redux'
import { Container } from 'reactstrap'
import IContact from '../models/IContact'
import IAppState from '../store/IAppState'
import { clearCurrentContact, getContact } from '../store/contacts/actions'
interface IContactDetailsProps {
  contact: IContact | null
  getContact: typeof getContact
  clearCurrentContact: typeof clearCurrentContact
  match: any
}
class ContactDetails extends React.Component<IContactDetailsProps, object> {
  public componentDidMount() {
    const contactId = this.props.match.params.contactId
    this.props.getContact(contactId)
  }
public componentWillUnmount() {
    this.props.clearCurrentContact()
  }
public render() {
    if (!this.props.contact) {
      return null
    }
const contact = this.props.contact
return (
      <Container>
        <h1>Contact Details: {contact.name}</h1>
        <dl>
          <dt>email</dt>
          <dd>{contact.email}</dd>
        </dl>
      </Container>
    )
  }
}
function mapStateToProps(state: IAppState) {
  return {
    contact: state.contacts.currentContact,
  }
}
export default connect(
  mapStateToProps,
  { getContact, clearCurrentContact }
)(ContactDetails)

Как и в случае с компонентом ContactList, мы указываем информацию о типе для свойств контейнера. Свойство match исходит от маршрутизатора React, и я признаю, что меня не слишком беспокоила информация о типе, поэтому было проще обойти проверку типа для этого свойства и сказать, что это может быть «любой» тип. Позже я вернусь и посмотрю на маршрутизатор React и выясню, есть ли для него информация о типе, но я не уверен, что это принесет какую-то пользу.

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

Итак, теперь нам нужно разрешить контакту видеть эту страницу по ссылке. Сначала давайте настроим маршрутизатор React

# /src/App.tsx
import * as React from 'react'
import { Provider } from 'react-redux'
import { BrowserRouter, Route, Switch } from 'react-router-dom'
import ContactDetails from './containers/ContactDetails'
import ContactList from './containers/ContactList'
import { createAppStore } from './store'
const appStore = createAppStore()
class App extends React.Component {
  public render() {
    return (
      <React.Fragment>
        <Provider store={appStore}>
          <BrowserRouter>
            <React.Fragment>
              <Switch>
                <Route 
                  path="/" 
                  component={ContactList} 
                  exact={true}
                />
                <Route
                  path="/details/:contactId"
                  component={ContactDetails}
                  exact={true}
                />
              </Switch>
            </React.Fragment>
          </BrowserRouter>
        </Provider>
      </React.Fragment>
    )
  }
}
export default App

В компоненте ContactList теперь вы можете указать ему связать экран сведений. Добавьте импорт для функции React Router Link и обновите строку, отображающую контакт, на:

<Link to={`/details/${contact.id}`}>{contact.name}</Link>

Запустите все это, и теперь вы можете просмотреть список контактов (добавить еще немного поддельных данных) и выбрать контакт, чтобы увидеть его детали.

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

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