Описание состояний интерфейса с помощью TypeScript
На ревью я часто вижу код, который описывает состояния пользовательского интерфейса. Этот код содержит массу условий.
Для примера давайте рассмотрим код вывода списка пользователей.
<ng-container *ngIf="isLoading && !error">
Loading...
</ng-container>
<ul *ngIf="users && users.length && !error">
<li *ngFor="let user of users"></li>
</ul>
<ng-container *ngIf="!error && !loading && users && !users.length">
Nothing found
</ng-container>
<ng-container *ngIf="!isLoading && error">
</ng-container>
Из-за того, что данный код содержит массу различных флагов и их комбинаций, его тяжело читать и поддерживать.
Я предпочитаю другой подход. Давайте попробуем порефакторить этот код.
Я читал про теорию конечных автоматов. Конечный автомат принимает конечный набор состояний, и в один момент времени он находится в одном из этих состояний.
Давайте выделим состояния, в которых может находиться список пользователей.
- Загрузка — необходимо показать прелоадер
- Пользователи загружены — необходимо вывести список
- Сервер вернул ошибку — необходимо вывести текст ошибки
- Список пользователей пуст (пользователи не найдены) — необходимо вывести соответствующее сообщение
Давайте зафиксируем указанные выше состояния в виде типа с применением discriminating union.
type State =
| { status: 'loading' }
| { status: 'success', data: IUser[] }
| { status: 'failed', error: Error }
| { status: 'not-founded' };
Сделаем тип State универсальным с помощью дженериков.
type State<TSuccessData> =
| { status: 'loading' }
| { status: 'success', data: TSuccessData }
| { status: 'failed', error: Error }
| { status: 'not-founded' };
type UsersListState = State<IUser[]>;
Вот и все. Теперь можно переписать логику отображения.
<ng-container *ngIf="state.status === 'loading'">
Loading...
</ng-container>
<ul *ngIf="state.status === 'success'">
<li *ngFor="let user of state.data"></li>
</ul>
<ng-container *ngIf="state.status === 'not-found'">
Nothing found
</ng-container>
<ng-container *ngIf="state.status === 'failed'">
</ng-container>
Глядя на такой код, можно сразу понять какое состояние описывается. Этот код сам себя документирует. Кроме того, благодаря этому подходу, вы получаете отличные подсказки в вашей IDE.