Недавно я начал думать об управлении состоянием для приложений React. Я пришел к некоторым интересным выводам, и в этом посте я покажу вам, что то, что мы называем управлением состоянием, на самом деле не является управлением состоянием.
Переводчик: Alibaba Cloud Front-end — Yeshu
Оригинальная ссылка:managing-state-in-javascript-with-state-machines-stent
Мы избегаем говорить о том, что есть (слон в комнате)
Давайте рассмотрим простой пример. Представьте, что это компонент формы, который отображает имя пользователя, пароль и кнопку. Пользователь нажимает кнопку «Отправить» после заполнения формы. Если все пойдет хорошо, мы завершили вход в систему, и необходимо отобразить приветственное сообщение и некоторые ссылки:
Мы предполагаем, что этот компонент имеет два состояния представления. Одно из них — состояние «не вошел в систему», а другое — состояние после входа пользователя в систему. Итак, начиная с управления этими двумя состояниями, мы используем логический флаг для описания состояния пользователя.
var isLoggedIn;
isLoggedIn = false; // 展示表单
isLoggedIn = true; // 展示欢迎信息和链接
Но этого недостаточно. Если HTTP-запрос, запущенный после того, как мы нажали кнопку отправки, требует некоторого времени для ответа, мы не можем оставить форму на экране и нам нужно больше элементов пользовательского интерфейса, чтобы показать такое промежуточное состояние, поэтому мы должны ввести еще один компонент в компонент. условие.
Теперь у нас есть третье состояние отображения, использующее только одноisLoggedIn
Переменные больше не могут быть разрешены. К сожалению, мы не можем установить значение переменной вfalse-ish
,Это неtrue
И это неfalse
. Конечно, мы можем ввести другую переменную, такую какisInProgress
. Как только мы отправим запрос, мы установим значение этой переменной вtrue
. Эта переменная сообщит нам, что запрос выполняется, и пользователь должен увидеть состояние отображения загрузки.
var isLoggedIn;
var isInProgress;
// 展示表单
isLoggedIn = false;
isInProgress = false;
// 请求过程中
isLoggedIn = false;
isInProgress = true;
// 展示欢迎信息和链接
isLoggedIn = true;
isInProgress = false;
Превосходно! Мы используем две переменные и должны помнить значения переменных для трех случаев. Похоже, мы решили проблему. Но другая проблема заключается в том, что мы поддерживаем слишком много состояний. Если нам нужно отобразить сообщение о том, что запрос выполнен успешно, или когда все пройдет успешно, нам нужно сообщить пользователю: «Ага, вы успешно вошли в систему», и через две секунды сообщение скроется с шикарной анимацией, а потом отображается окончательный интерфейс.Как сделать?
Теперь все становится немного сложнее. у нас есть
isLoggedIn
а такжеisInProgress
, но видимо только их использовать недостаточно.isInProgress
После выполнения запросаfalse
, но его значение по умолчанию также равноfalse
. Я думаю, нам нужна третья переменная -isSuccessful
.
var isLoggedIn, isInProgress, isSuccessful;
// 展示表单
isLoggedIn = false;
isInProgress = false;
isSuccessful = false;
// 请求过程中
isLoggedIn = false;
isInProgress = true;
isSuccessful = false;
// 展示成功状态
isLoggedIn = true;
isInProgress = false;
isSuccessful = true;
// 展示欢迎信息和链接
isLoggedIn = true;
isInProgress = false;
isSuccessful = false;
Наше простое управление состоянием шаг за шагом превратилось в огромную паутину условий if-else, которые было трудно понять и поддерживать.
if (isInProgress) {
// 请求过程中
} else if (isLoggedIn) {
if (isSuccessful) {
// 展示请求成功信息
} else {
// 展示欢迎信息和链接
}
} else {
// 等待输入,展示表单
}
У нас есть еще один вопрос, чтобы усугубить эту ситуацию: что мы будем делать, если запрос не будет выполнен? Нам нужно отобразить сообщение об ошибке и ссылку на повторную попытку, если нажать «Повторить», мы повторим процесс запроса.
Теперь наш код не имеет оспоминания. У нас много сцен, которые необходимо выполнить, просто полагаясь на введение новых переменных недопустимо. Давайте подумаем о том, можно ли быть решено лучшим методом именования и может также понадобиться ввести новое условное утверждение.
isInProgress
Используется только в процессе запроса. Мы по-прежнему обеспокоены процессом после завершения запроса.
isLoggedIn
Это немного вводящее в заблуждение значение, поскольку мы устанавливаем его, как только запрос завершается.true
. И если запрос пойдет не так, пользователь фактически не вошел в систему. Поэтому мы переименовали его вisRequestFinished
. Хотя это выглядит лучше, это означает лишь то, что мы получили ответ от сервера, и его нельзя использовать для определения, является ли ответ ошибкой или нет.
isSuccessful
является подходящей переменной-кандидатом для конечного состояния. Если запрос пойдет не так, мы можем установить его наfalse
, но подождите, его значение по умолчанию такжеfalse
. Поэтому его также нельзя использовать в качестве переменной, представляющей состояние ошибки.
Нам нужна четвертая переменная,isFailed
как насчет этого?
var isRequestFinished, isInProgress, isSuccessful, isFailed;
if (isInProgress) {
// 请求过程中
} else if (isRequestFinished) {
if (isSuccessful) {
// 展示请求成功信息
} else if (isFailed) {
// 展示请求失败信息和重试链接
} else {
// 展示欢迎信息和链接
}
} else {
// 等待输入,展示表单
}
Эти четыре переменные описывают, казалось бы, простой, но не такой уж простой процесс, включающий множество пограничных случаев. По мере дальнейшей итерации проекта в конечном итоге может быть определено больше переменных, поскольку комбинация существующих переменных не может соответствовать новым требованиям. Вот почему так сложно создавать пользовательские интерфейсы.
Нам нужен лучший способ управления состоянием. Возможно, можно было бы использовать более современную и популярную концепцию.
Как насчет Flux или Redux?
В последнее время я размышлял об архитектуре Flux и месте библиотеки Redux в управлении состоянием. Несмотря на то, что эти инструменты связаны с управлением состоянием, по своей сути они не решают проблемы такого типа.
Flux — это платформа Facebook для создания клиентских веб-приложений. Он дополняет способ организации компонентов представления React односторонним потоком данных.
Redux — это контейнер с предсказуемым состоянием для создания приложений JavaScript.
Это «односторонний поток данных» и «контейнеры состояний», а не «управление состоянием». Концепции, лежащие в основе Flux и Redux, очень практичны и игривы. Я думаю, что это правильный способ создания пользовательских интерфейсов. Односторонний поток данных делает данные предсказуемыми и улучшает интерфейсную разработку. Неизменная природа редюсеров в Redux обеспечивает способ передачи данных, который уменьшает количество ошибок.
На мой взгляд, эти шаблоны больше применимы к управлению данными и управлению потоками данных. Они предоставляют сложный API для обмена информацией, которая изменяет данные нашего приложения, но они не решают наших проблем управления состоянием. Это также связано с тем, что эти вопросы тесно связаны с проектом, а контекст вопроса зависит от того, что мы делаем.
Конечно, мы можем решить это с помощью библиотеки, например обработки HTTP-запросов, но нам все равно нужно написать собственный код для реализации другой связанной бизнес-логики. Вопрос в том, как организовать этот код таким образом, чтобы не требовалось переписывать все приложение каждые два года.
Несколько месяцев назад я начал искать шаблон, который мог бы решить проблему управления состоянием, и наконец открыл для себя концепцию конечного автомата. На самом деле, мы все время строили конечные автоматы, просто не знали об этом.
Что такое государственная машина?
Математическое определение конечного автомата — это вычислительная модель, мое понимание таково: конечный автомат — это коробка, в которой хранится ваше состояние и изменения состояния. Вот несколько различных типов конечных автоматов, в нашем случае подходит конечный автомат. Как и его название, конечный автомат содержит ограниченное количество состояний. Он принимает ввод и определяет следующее состояние на основе этого ввода и текущего состояния, а выходов может быть несколько. Когда конечный автомат меняет состояние, мы называем это переходом в новое состояние.
Фактический конечный автомат
Чтобы использовать конечный автомат, нам более или менее необходимо определить две вещи — состояние и возможные методы перехода. Попробуем реализовать указанные выше требования к форме.
В этой таблице мы можем ясно видеть все состояния и их возможные выходы. Мы также определяем следующее состояние, если ввод передается в конечный автомат. Написание такой таблицы может принести большую пользу вашему циклу разработки, поскольку она ответит на следующие вопросы:
- Каковы все возможные состояния пользовательского интерфейса?
- Что происходит между каждым государством?
- Если изменение состояния, в чем результат?
Эти три проблемы могут решить очень много сложных проблем. Представьте, что у вас есть анимация, когда мы меняем представление контента, когда анимация запускается, пользовательский интерфейс все еще находится в предыдущем состоянии, и пользователь все еще может взаимодействовать. Например, пользователь очень быстро дважды нажимает кнопку отправки. Если конечный автомат неприменим, нам нужно использовать оператор if, чтобы предотвратить выполнение кода, пометив переменную. Но если мы вернемся к форме выше, мы увидим, что состояние загрузки не принимает ввод из состояния отправки. Поэтому, если мы переведем конечный автомат в состояние загрузки после первого нажатия кнопки, мы в безопасном положении. Даже если ввод/действие Submit отправлено, конечный автомат проигнорирует его и, конечно же, не сделает еще один запрос к серверной части.
У меня работает шаблон конечного автомата. Есть три причины, по которым я использую конечный автомат в своем приложении:
- Шаблон конечного автомата устраняет множество возможных ошибок и странную чистоту, потому что он не позволяет пользовательскому интерфейсу переходить в состояние, о котором мы не знаем.
- Конечный автомат не принимает входные данные, которые не определены как текущее состояние. Это устраняет некоторую отказоустойчивость, которую мы делаем с другим кодом.
- Конечные автоматы заставляют разработчиков мыслить декларативно. Потому что большая часть нашей логики должна быть определена заранее.
Реализация конечных автоматов в JavaScript
Теперь, когда мы знаем, что такое конечный автомат, давайте реализуем его и решим проблему нашего запуска. Используйте некоторые вложенные атрибуты для определения простого объектного слова.
const machine = {
currentState: 'login form',
states: {
'login form': {
submit: 'loading'
},
'loading': {
success: 'profile',
failure: 'error'
},
'profile': {
viewProfile: 'profile',
logout: 'login form'
},
'error': {
tryAgain: 'loading'
}
}
}
Объект конечного автомата, использующий содержимое нашей приведенной выше таблицы, определяет состояние. Например, когда мыlogin form
государство, мы используемsubmit
в качестве входных данных и должен начинаться сloading
Статус заканчивается. Теперь нам нужна функция, которая принимает входные данные.
const input = function (name) {
const state = machine.currentState;
if (machine.states[state][name]) {
machine.currentState = machine.states[state][name];
}
console.log(`${ state } + ${ name } --> ${ machine.currentState }`);
}
Мы получаем текущее состояние и проверяем, действителен ли предоставленный ввод, если он проходит, мы меняем текущее состояние или, другими словами, переводим конечный автомат в новое состояние. Мы предоставляем вывод журнала для ввода, текущего состояния и нового состояния (если есть). Вот как использовать наш конечный автомат:
input('tryAgain');
// login form + tryAgain --> login form
input('submit');
// login form + submit --> loading
input('submit');
// loading + submit --> loading
input('failure');
// loading + failure --> error
input('submit');
// error + submit --> error
input('tryAgain');
// error + tryAgain --> loading
input('success');
// loading + success --> profile
input('viewProfile');
// profile + viewProfile --> profile
input('logout');
// profile + logout --> login form
Обратите внимание, что мы пытаемся передатьlogin form
Отправлено, когда статусtryAgain
состояние, чтобы сломать конечный автомат или повторно отправить запросы на фиксацию. В этих сценариях текущее состояние не изменяется, и конечный автомат игнорирует эти входные данные.
последние слова
Я не знаю, применима ли концепция конечного автомата к вашему собственному сценарию, но для меня она отлично работает. Я просто изменил способ управления состоянием. Рекомендую попробовать, оно того однозначно стоит.
PS: Рекламируйте, как всегда: Alibaba Cloud запрашивает инженеров по интерфейсу, базу в Пекине или Ханчжоу, обращайтесь: xiaoming.dxm@alibaba-inc.com