Создание приложения для управления списком клиентов с помощью React и TypeScript: основные компоненты

TypeScript внес много улучшений в структурирование и написание кода приложений JavaScript , особенно веб-приложений. Так называемый расширенный набор JavaScript, TypeScript ведет себя идентично JavaScript, но предоставляет дополнительные функции, цель которых – упростить разработку более крупных и сложных программ, уменьшить количество ошибок или полностью устранить их. TypeScript становится все популярнее и уже используется крупными компаниями (такими как Google – для веб-фреймворка Angular). Бэкенд фреймворк Nest.js также был разработан с помощью TypeScript.

Одним из способов повышения производительности труда разработчика является возможность максимально быстро внедрять новые функции, не беспокоясь о том, что существующее приложение перестанет работать. Для этого многие опытные разработчики пишут статически типизированный код. Статически типизированные языки программирования, такие как TypeScript, привязывают к каждой переменной тип данных (типы – строки, целые числа, логические данные и т. д.). Одним из основных преимуществ языка программирования со статической типизацией является то, что проверка типов завершается во время компиляции, поэтому разработчики могут увидеть ошибки в своем коде на самой ранней стадии.

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

В этом мануале вы создадите приложение для управления списком клиентов с отдельным интерфейсом REST API и внешним интерфейсом, созданным с помощью React и TypeScript. Для бэкенда мы используем условный REST API по имени json-server. Вы будете использовать его для быстрой настройки бэкенда CRUD (Create, Read, Update, Delete). Следовательно, вы можете сосредоточиться на обработке внешней логики приложения, используя React и TypeScript.

Требования

1: Установка TypeScript и создание приложения React

Для начала нужно глобально установить пакет TypeScript с помощью Node Package Manager (npm). После этого вы также установите React и его зависимости и убедитесь, что ваше приложение React работает, запустив сервер разработки.

Для начала откройте терминал и выполните следующую команду для установки TypeScript:

npm install -g typescript

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

tsc -v

Вы увидите текущую версию программы, установленную на вашем компьютере:

Version 3.4.5

Затем установите приложение React с помощью инструмента create-react-app (чтобы установить приложение с помощью одной команды). Используйте команду npx (инструмент запуска пакетов), она поставляется с npm 5.2+. Инструмент create-react-app имеет встроенную поддержку TypeScript, без какой-либо дополнительной настройки. Выполните следующую команду, чтобы создать и установить новое приложение React по имени typescript-react-app:

npx create-react-app typescript-react-app --typescript

Эта команда создаст новое приложение React по имени typescript-react-app. Флаг —typescript установит .tsx как тип файла по умолчанию для компонентов React.

Прежде чем завершить это, приложению потребуется перейти с одного порта на другой. Для этого вам необходимо установить библиотеку маршрутизации React Router и соответствующие определения TypeScript. Используйте yarn для установки библиотеки и других пакетов этого проекта. Инструмент yarn быстрее, особенно при установке зависимостей для приложения React. Перейдите новую папку проекта и затем установите React Router с помощью следующей команды:

cd typescript-react-app
yarn add react-router-dom

Теперь у вас есть пакет React Router, который обеспечит маршрутизацию в вашем проекте. Затем выполните следующую команду, чтобы установить определения TypeScript для React Router:

yarn add @types/react-router-dom

Теперь нужно установить axios, HTTP-клиент для браузеров, чтобы упростить процесс выполнения HTTP-запросов от различных компонентов приложения, которые вы создадите:

yarn add axios

После завершения установки запустите сервер разработки с помощью команды:

yarn start

Ваше приложение будет работать на http://localhost:3000.

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

2: Создание JSON-сервера

На этом этапе вы создадите фиктивный сервер, к которому ваше приложение React может быстро подключиться, чтобы использовать ресурсы. Важно отметить, что этот внутренний сервис не подходит для среды производства. Вы можете использовать Nest.js, Express или любую другую серверную технологию для создания RESTful API в производстве. Инструмент json-server полезен только в тех ситуациях, когда нужно создать прототип и «макет» бэкенд сервера.

Вы можете использовать npm или yarn для установки json-сервера на компьютер. Это сделает его доступным из любого каталога проекта. Откройте новое окно терминала и выполните эту команду в каталоге проекта, чтобы установить json-server:

yarn global add json-server

Затем создайте файл JSON, который будет содержать данные для API REST. Для объектов, указанных в этом файле, конечная точка CRUD сгенерируется автоматически. Для начала создайте новую папку server и затем перейдите в нее:

mkdir server
cd server

Теперь используйте nano для создания нового файла db.json:

nano db.json

Добавьте в файл следующее содержимое:

{
"customers": [
{
"id": 1,
"first_name": "Customer_1",
"last_name": "Customer_11",
"email": "customer1@mail.com",
"phone": "00000000000",
"address": "Customer_1 Address",
"description": "Customer_1 description"
},
{
"id": 2,
"first_name": "Customer_2",
"last_name": "Customer_2",
"email": "customer2@mail.com",
"phone": "00000000000",
"address": "Customer_2 Adress",
"description": "Customer_2 Description"
}
]
}

Структура JSON состоит из объекта customer, которому присвоены два набора данных. Каждый клиент состоит из семи свойств: id, description, first_name, last_name, email, phone и address.

Сохраните и закройте файл.

По умолчанию json-server работает по порту 3000 – это тот же порт, на котором работает приложение React. Чтобы избежать конфликта, вы можете изменить порт по умолчанию для json-сервера. Для этого перейдите в корневой каталог приложения:

cd ~/typescript-react-app

Откройте приложение в текстовом редакторе и создайте новый файл по имени json-server.json:

nano json-server.json

Теперь вставьте следующее, чтобы обновить номер порта:

{
"port": 5000
}

Этот файл будет действовать как конфигурационный файл для json-server, благодаря чему сервер всегда будет работать по правильному порту.

Сохраните и закройте файл.

Чтобы запустить сервер, используйте следующую команду:

json-server --watch server/db.json

Это запустит json-server на порт 5000. Если вы перейдете по адресу http://localhost:5000/customers  в браузере, вы увидите сервер, показывающий ваш список клиентов.

Чтобы упростить процесс запуска json-сервера, вы можете обновить package.json, добавив новое свойство server для объекта scripts, как показано здесь:

{
...
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"server": "json-server --watch server/db.json"
},
...
}

Сохраните и закройте файл.

Теперь для запуска сервера json-server вам нужно только запустить yarn server из терминала.

Вы создали простой REST API, который можно использовать в качестве внутреннего сервера для этого приложения. Вы также создали JSON объект клиента, который будет использоваться в качестве данных по умолчанию для REST API. А еще вы настроили альтернативный порт для внутреннего сервера json-server. Далее мы создадим повторно используемые компоненты приложения.

3: Создание повторно используемых компонентов

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

Для начала вернитесь в терминал, где запущено приложение React, и остановите сервер разработки, нажав CTRL + C. Затем перейдите в папку ./src/:

cd ./src/

Затем создайте внутри нее новую папку components  и перейдите в эту папку:

mkdir components
cd components

В этой папке создайте папку customer и перейдите в нее:

mkdir customer
cd customer

Теперь создайте два новых файла Create.tsx и Edit.tsx:

touch Create.tsx Edit.tsx

Эти файлы представляют собой повторно используемые компоненты React, которые будут визуализировать формы и содержать всю бизнес-логику для создания и редактирования сведений о клиенте.

Откройте файл Create.tsx в текстовом редакторе и добавьте следующий код:

import * as React from 'react';
import axios from 'axios';
import { RouteComponentProps, withRouter } from 'react-router-dom';
export interface IValues {
first_name: string,
last_name: string,
email: string,
hone: string,
address: string,
description: string,
}
export interface IFormState {
[key: string]: any;
values: IValues[];
submitSuccess: boolean;
loading: boolean;
}

Этот код импортирует React, axios и другие компоненты, необходимые для маршрутизации, из пакета React Router. После этого создаются два новых интерфейса по имени IValues ​​и IFormState. Интерфейсы TypeScript помогают задать определенный тип значений, которые следует передать объекту, и обеспечивают согласованность во всем приложении. Это уменьшает вероятность возникновения ошибки в программе.

Далее вы создадите компонент Create, который расширяет React.Component. Добавьте следующий код в файл Create.tsx сразу после интерфейса IFormState:

...
class Create extends React.Component<RouteComponentProps, IFormState> {
constructor(props: RouteComponentProps) {
super(props);
this.state = {
first_name: '',
last_name: '',
email: '',
phone: '',
address: '',
description: '',
values: [],
loading: false,
submitSuccess: false,
}
}
}
export default withRouter(Create)

Этот код определяет компонент React в Typescript. В этом случае компонент класса Create принимает props (сокращение от «properties») типа RouteComponentProps и использует состояние типа IFormState. Затем внутри конструктора вы инициализировали объект state и определили все переменные, которые будут представлять визуализированные значения клиента.

Затем добавьте эти методы в компонент класса Create сразу после конструктора. Эти методы нужно использовать для обработки клиентских форм и всех изменений в полях ввода:

...
values: [],
loading: false,
submitSuccess: false,
}
}
private processFormSubmission = (e: React.FormEvent<HTMLFormElement>): void => {

e.preventDefault();


this.setState({ loading: true });


const formData = {


first_name: this.state.first_name,


last_name: this.state.last_name,


email: this.state.email,


phone: this.state.phone,


address: this.state.address,


description: this.state.description,


}


this.setState({ submitSuccess: true, values: [...this.state.values, formData], loading: false });


axios.post(`http://localhost:5000/customers`, formData).then(data => [


setTimeout(() => {


this.props.history.push('/');


}, 1500)


]);


}


private handleInputChanges = (e: React.FormEvent<HTMLInputElement>) => {


e.preventDefault();


this.setState({


[e.currentTarget.name]: e.currentTarget.value,


})


}

...
export default withRouter(Create)
...

Метод processFormSubmission() получает сведения о клиенте из состояния приложения и передает их в базу данных с помощью axios. Метод handleInputChanges() использует React.FormEvent для получения значений всех полей ввода и вызывает this.setState() для обновления состояния приложения.

Затем добавьте метод render() в компонент Create сразу после метода handleInputchanges(). Этот метод render() отобразит форму для создания нового клиента в приложении:

...
public render() {
const { submitSuccess, loading } = this.state;
return (
<div>
<div className={"col-md-12 form-wrapper"}>
<h2> Create Post </h2>
{!submitSuccess && (
<div className="alert alert-info" role="alert">
Fill the form below to create a new post
</div>
)}
{submitSuccess && (
<div className="alert alert-info" role="alert">
The form was successfully submitted!
</div>
)}
<form id={"create-post-form"} onSubmit={this.processFormSubmission} noValidate={true}>
<div className="form-group col-md-12">
<label htmlFor="first_name"> First Name </label>
<input type="text" id="first_name" onChange={(e) => this.handleInputChanges(e)} name="first_name" className="form-control" placeholder="Enter customer's first name" />
</div>
<div className="form-group col-md-12">
<label htmlFor="last_name"> Last Name </label>
<input type="text" id="last_name" onChange={(e) => this.handleInputChanges(e)} name="last_name" className="form-control" placeholder="Enter customer's last name" />
</div>
<div className="form-group col-md-12">
<label htmlFor="email"> Email </label>
<input type="email" id="email" onChange={(e) => this.handleInputChanges(e)} name="email" className="form-control" placeholder="Enter customer's email address" />
</div>
<div className="form-group col-md-12">
<label htmlFor="phone"> Phone </label>
<input type="text" id="phone" onChange={(e) => this.handleInputChanges(e)} name="phone" className="form-control" placeholder="Enter customer's phone number" />
</div>
<div className="form-group col-md-12">
<label htmlFor="address"> Address </label>
<input type="text" id="address" onChange={(e) => this.handleInputChanges(e)} name="address" className="form-control" placeholder="Enter customer's address" />
</div>
<div className="form-group col-md-12">
<label htmlFor="description"> Description </label>
<input type="text" id="description" onChange={(e) => this.handleInputChanges(e)} name="description" className="form-control" placeholder="Enter Description" />
</div>
<div className="form-group col-md-4 pull-right">
<button className="btn btn-success" type="submit">
Create Customer
</button>
{loading &&
<span className="fa fa-circle-o-notch fa-spin" />
}
</div>
</form>
</div>
</div>
)
}
...

Вы создали форму с полями для хранения значений first_name, last_name, email, phone, address и description клиента. Каждое из полей ввода имеет метод handleInputChanges(), который выполняется при каждом нажатии клавиши, обновляя состояние React значением, полученным из ввода. Кроме того, в зависимости от состояния приложения логическая переменная submitSuccess будет выводить то или иное сообщение (приложение будет отображать его до и после создания нового клиента).

Вы можете увидеть полный код этого файла в этом репозитории GitHub.

Сохраните и закройте Create.tsx.

Теперь, когда вы добавили соответствующую логику в файл компонента Create приложения, вы можете добавить компонент Edit.

Откройте файл Edit.tsx в папке customer и добавьте следующий код для импорта React, axios, а также определения интерфейсов TypeScript:

import * as React from 'react';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import axios from 'axios';

export interface IValues {
[key: string]: any;
}
export interface IFormState {
id: number,
customer: any;
values: IValues[];
submitSuccess: boolean;
loading: boolean;
}

Аналогично компоненту Create вы импортируете необходимые модули и создаете интерфейсы IValues и IFormState. Интерфейс IValues определяет тип данных для значений из полей ввода, а IFormState объявляет ожидаемый тип для объекта приложения.

Затем создайте компонент класса EditCustomer сразу после блока интерфейса IFormState, как показано здесь:

...
class EditCustomer extends React.Component<RouteComponentProps<any>, IFormState> {
constructor(props: RouteComponentProps) {
super(props);
this.state = {
id: this.props.match.params.id,
customer: {},
values: [],
loading: false,
submitSuccess: false,
}
}
}
export default withRouter(EditCustomer)

Этот компонент принимает RouteComponentProps <any> и интерфейс IFormState в качестве параметра. Мы используем <any> как добавление к RouteComponentProps, потому что всякий раз, когда React Router анализирует параметры пути, он не выполняет никакого преобразования типов, чтобы определить, являются ли данные числами или строками. Поскольку вы ожидаете параметр для uniqueId клиента, безопаснее использовать any.

Теперь добавьте следующие методы:

...
public componentDidMount(): void {

axios.get(`http://localhost:5000/customers/${this.state.id}`).then(data => {


this.setState({ customer: data.data });


})


}


private processFormSubmission = async (e: React.FormEvent<HTMLFormElement>): Promise<void> => {


e.preventDefault();


this.setState({ loading: true });


axios.patch(`http://localhost:5000/customers/${this.state.id}`, this.state.values).then(data => {


this.setState({ submitSuccess: true, loading: false })


setTimeout(() => {


this.props.history.push('/');


}, 1500)


})


}


private setValues = (values: IValues) => {


this.setState({ values: { ...this.state.values, ...values } });


}


private handleInputChanges = (e: React.FormEvent<HTMLInputElement>) => {


e.preventDefault();


this.setValues({ [e.currentTarget.id]: e.currentTarget.value })


}

...
}
export default withRouter(EditCustomer)

Сначала идет метод componentDidMount(), он является методом жизненного цикла, который вызывается при создании компонента. Метод берет id, полученный из параметра маршрута, чтобы идентифицировать конкретного клиента в качестве параметра, использует его для извлечения их данных из БД, а затем заполняет форму. Кроме того, здесь есть методы для обработки отправки формы и изменений, внесенных в поля ввода.

Теперь добавьте метод render() для компонента Edit:

...
public render() {
const { submitSuccess, loading } = this.state;
return (
<div className="App">
{this.state.customer &&
<div>
< h1 > Customer List Management App</h1>
<p> Built with React.js and TypeScript </p>

<div>
<div className={"col-md-12 form-wrapper"}>
<h2> Edit Customer </h2>
{submitSuccess && (
<div className="alert alert-info" role="alert">
Customer's details has been edited successfully </div>
)}
<form id={"create-post-form"} onSubmit={this.processFormSubmission} noValidate={true}>
<div className="form-group col-md-12">
<label htmlFor="first_name"> First Name </label>
<input type="text" id="first_name" defaultValue={this.state.customer.first_name} onChange={(e) => this.handleInputChanges(e)} name="first_name" className="form-control" placeholder="Enter customer's first name" />
</div>
<div className="form-group col-md-12">
<label htmlFor="last_name"> Last Name </label>
<input type="text" id="last_name" defaultValue={this.state.customer.last_name} onChange={(e) => this.handleInputChanges(e)} name="last_name" className="form-control" placeholder="Enter customer's last name" />
</div>
<div className="form-group col-md-12">
<label htmlFor="email"> Email </label>
<input type="email" id="email" defaultValue={this.state.customer.email} onChange={(e) => this.handleInputChanges(e)} name="email" className="form-control" placeholder="Enter customer's email address" />
</div>
<div className="form-group col-md-12">
<label htmlFor="phone"> Phone </label>
<input type="text" id="phone" defaultValue={this.state.customer.phone} onChange={(e) => this.handleInputChanges(e)} name="phone" className="form-control" placeholder="Enter customer's phone number" />
</div>
<div className="form-group col-md-12">
<label htmlFor="address"> Address </label>
<input type="text" id="address" defaultValue={this.state.customer.address} onChange={(e) => this.handleInputChanges(e)} name="address" className="form-control" placeholder="Enter customer's address" />
</div>
<div className="form-group col-md-12">
<label htmlFor="description"> Description </label>
<input type="text" id="description" defaultValue={this.state.customer.description} onChange={(e) => this.handleInputChanges(e)} name="description" className="form-control" placeholder="Enter Description" />
</div>
<div className="form-group col-md-4 pull-right">
<button className="btn btn-success" type="submit">
Edit Customer </button>
{loading &&
<span className="fa fa-circle-o-notch fa-spin" />
}
</div>
</form>
</div>
</div>
</div>
}
</div>
)
}
...

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

Вы можете проверить код этого файла в этом репозитории GitHub.

Сохраните и выйдите из Edit.tsx.

Чтобы просмотреть полный список клиентов, созданных в приложении, создайте новый компонент в папке ./src/components и назовите его Home.tsx:

cd ./src/components
nano Home.tsx

Добавьте следующий код:

import * as React from 'react';
import { Link, RouteComponentProps } from 'react-router-dom';
import axios from 'axios';
interface IState {
customers: any[];
}

export default class Home extends React.Component<RouteComponentProps, IState> {
constructor(props: RouteComponentProps) {
super(props);
this.state = { customers: [] }
}
public componentDidMount(): void {
axios.get(`http://localhost:5000/customers`).then(data => {
this.setState({ customers: data.data })
})
}
public deleteCustomer(id: number) {
axios.delete(`http://localhost:5000/customers/${id}`).then(data => {
const index = this.state.customers.findIndex(customer => customer.id === id);
this.state.customers.splice(index, 1);
this.props.history.push('/');
})
}
}

Здесь вы импортировали React, axios и другие необходимые компоненты из React Router. Вы создали два новых метода в компоненте Home:

  • componentDidMount(): приложение вызывает этот метод сразу после монтирования компонента. В его обязанности входит получение списка клиентов и обновление домашней страницы (чтоб отобразить список).
  • deleteCustomer(): этот метод примет id в качестве параметра и удалит информацию о клиенте, который определен этим id, из базы данных.

Теперь добавьте метод render(), чтобы отобразить таблицу со списком клиентов в компоненте Home:

...
public render() {
const customers = this.state.customers;
return (
<div>
{customers.length === 0 && (
<div className="text-center">
<h2>No customer found at the moment</h2>
</div>
)}
<div className="container">
<div className="row">
<table className="table table-bordered">
<thead className="thead-light">
<tr>
<th scope="col">Firstname</th>
<th scope="col">Lastname</th>
<th scope="col">Email</th>
<th scope="col">Phone</th>
<th scope="col">Address</th>
<th scope="col">Description</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
{customers && customers.map(customer =>
<tr key={customer.id}>
<td>{customer.first_name}</td>
<td>{customer.last_name}</td>
<td>{customer.email}</td>
<td>{customer.phone}</td>
<td>{customer.address}</td>
<td>{customer.description}</td>
<td>
<div className="d-flex justify-content-between align-items-center">
<div className="btn-group" style={{ marginBottom: "20px" }}>
<Link to={`edit/${customer.id}`} className="btn btn-sm btn-outline-secondary">Edit Customer </Link>
<button className="btn btn-sm btn-outline-secondary" onClick={() => this.deleteCustomer(customer.id)}>Delete Customer</button>
</div>
</div>
</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
</div>
)
}
...

В этом блоке кода списки клиентов извлекаются из приложения в виде массива, просматриваются и отображаются в HTML-таблице. Также тут добавляется параметр customer.id, который используется для идентификации и удаления из списка сведений о конкретном клиенте.

Сохраните и выйдите из Home.tsx.

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

Теперь все необходимые повторно используемые компоненты приложения готовы. Пора обновить компонент приложения и добавить ссылки на все остальные компоненты, которые вы создали. Об этом мы расскажем вам здесь.

Tags: , , , ,