Где же держать бизнес логику приложения?  "Та чего уж там париться - прямо в компонентах." - скажут некоторые. И в некоторых ситуациях это правильное и удобное решение. Но что если мы все-таки хотим чего-то большего? Например, масштабируемости, несколько юзер интерфейсов, лёгкой смены дизайна. Тогда логично разделить приложение на два слоя. Слой бизнес логики и слой представления.  Как написано на сайте reactjs.org, React - это библиотека для создания UI.  А для модели и бизнес логики написано несколько других замечательных библиотек. Так уж получилось, что проекты, на которых я работал, использовали redux. Поэтому дальше речь пойдет про то, как построить бизнес логику в большом react/redux приложении, и чтобы потом не закрывать рукой глаза при виде того огромного количества редюсеров, экшенов и так далее и тому подобное. Чтобы не запутаться в своем коде, я привык все делить на разные слои. Вы тоже это делаете, когда стили храните в одном файле, контейнер - в другом, компонент - в третьем, константы в четвертом, переводы в пятом, стейтлесс компоненты - в шестом, а ещё редюсеры, экшены, селекторы,  Что? Не храните в разных файлах? Не, ну пусть даже вы назвали это duck подходом и запихнули все в один файл, вы все равно имеете в виду что он состоит из разных слоев. Деление на слои помогает ориентироваться в коде. Вы уже не просто смотрите 125-ю строчку кода вывода таблицы X, а слой контейнера таблицы X. Деление на слои помогает заменять отдельные слои. Например, у вас есть стейтлесс компонент. Пока редакс не готов, вы можете временно подключить к стейтфулл компоненту, а потом стейтфулл компонент заменить на контейнер подключенный к редаксу. Деление на слои помогает разделить работу между разработчиками. Итак, деление на слои - это хорошо. Как правило когда мы выводим пользователю какою-то страницу с десятком компонентов, то все эти компоненты не сами по себе, они взаимодействуют друг с другом ради одного общего дела. Многие делают ошибку - инкапсулируют логику в отдельные соответствующие компоненты. Например, представим страницу с таблицей и фильтрацией и Вы все-таки расположили логику фильтрации в компоненте FilterComponent. И вдруг понадобилось добавить панель с кнопкой (BottomPanel) сброса фильтра в другой части UI. Часто я вижу такое решение: Оставляют FilterComponent с его состоянием и добавляют еще BottomPanel в которой дублируется тоже состояние, и потом пытаются эти два состояния синхронизировать. И первое время все работает как положено. Но постепенно появляются другие связанные компоненты и контроль над ситуацией теряется. Из такого подхода вырастает очень не тривиальный и сложный в поддержке код. Правильнее будет вынести эту логику в корневой компонент, в котором все взаимодействующие компоненты рендерятся. Тогда поток данных будет предсказуем - пропсы от родителя к дочерним компонентам, события от дочернего компонента к родителю. Размещать бизнес логику в корневом компоненте - это хорошо. До появления хуков общую логику компонентов выносили либо в HOC либо описывали прямо в корневом для этой логики компоненте . С появлением хуков все стало наглядней. Хуки отличный претендент для слоя бизнеса логики и переиспользования общей логики в разных компонентах. Они очень удобные и если бы не парочку нюансов, можно было бы ими ограничится. Хуки для бизнес логики - это хорошо Первое, чего не хватает при работе с хуками и то что даёт редакс - это общее состояние всего приложения. Общее состояние - это отдельный слой, а как мы выяснили в самом начале статьи - чем больше разных слоев, тем лучше. Редакс предоставляет инструменты для дебага общего состояния. Общее состояние позволяет передвигаться по истории, что открывает дополнительные возможности при тестировании. Можно сохранить состояние и потом восстанавливать для нескольких веток теста. Можно пролистать все виды интерфейса пользователя просто меняя состояние. Общее состояние приложения в одном месте - это хорошо. Т.к. у нас redux, то часть логики приложения попала в редюсеры. Но с редюсерами одна проблема - они синхронные. Нельзя в редюсере сделать запрос на сервер и результат вернуть в редакс. Чтобы добавить асинхронности, разработали несколько расширений для редакса используя специальному механизм - middleware. Этот механизм позволяет выполнить асинхронные действия вместо/до/после реального диспатча экшена. Я рассматривал два варианта Thunk и Saga и выбрал Thunk. Saga отпала по простой причине - мне не нравятся генераторы. Но принципиально Thunk и Saga ничем не отличаются. Они оба добавляют в поток данных асинхронные действия, при этом поток данных так и остается однонаправленным: Пользователь вызвал событие в UI. Выполнился редюсер (изменился стейт) либо вызвался экшен thunk или Saga. Экшен  thunk или Saga сделал асинхронные действия и вызвал один или несколько экшенов редакса что ведет обратно к пункту 1. Но есть кардинально другой подход - Акторы. Акторы подписываются на изменение состояния и  делают асинхронные действия в ответ на изменения какого-то значения в состоянии. Поток данных становится таким:  Пользователь вызвал событие в UI  Вызвался экшен redux и поменялось состояние стора.  Актор подписанный на изменение этого состояния сделал асинхронные действия и вызвал один или несколько экшенов редакса. На первый взгляд бросается в глаза недостаток акторов в том, что перед асинхронным действием надо задиспатчить дополнительный экшен и изменить состояние стора. Иначе актор не поймет что надо делать. В thunk ничего не мешает сразу начать асинхронные действия. Но те кто делал реальный проект на thunk, а не просто смотрел примеры в документации, знают, что нельзя просто так начать асинхронное действие. Пользователю надо сообщить что программа начала что то делать. И как правило в экшен thunk-а добавляют dispatch({type: ‘START’}) перед асинхронным действием и dispatch({type: ‘END’}) после. Таким образом с точки зрения частоты смены состояния между мидлварой и актором нет никакой разницы. Как ни крути, при появлении асинхронности будет как минимум два изменения состояния: 1. Чтобы сообщить что что-то начало грузится/обрабатываться 2. Непосредственно сохранение результата в редакс Тут все просто. Синхронную логику мы заносим в Redux, асинхронную - в Thunk. Важно отметить, основное что делала бизнес логика в thunk actions - это сравнение предыдущего состояния с новым и в зависимости от результата вызывала другие action. Я ничего не имею против Thunk и Saga.  Но любая дополнительная библиотека увеличивает базу знаний исходного кода. Для каждой библиотеки каждому разработчику нужно изучить документацию, почитать лучшие практики и получить опыт использования. Поэтому мой минималистичный дух считает что чем меньше подходов используется в приложении тем лучше.   Чем меньше подходов используется при разработке тем лучше.  Признаюсь, сначала я пошел по сложному пути. Я начал выдумывать свои подходы к организации бизнес логики. Начал выдумывать некие модули-акторы чтобы в них хранить бизнесс логику. Немаловажное значение имел мой опыт с организаци бизнес логики в thunk. Я подписался на состояние редакса, При изменении состояния, я проверял что поменялось и вызывал dispatch(action). Основной проблемой было придумать как сделать композицию из нескольких акторов и чтобы одни акторы можно было вставлять в другие, и еще передавать параметры от одного актора другому. И когда я решил вынести логику сравнения предыдущего состояния с новым в отдельные хелперные функции, я начал подозревать, что я делаю что-то очень знакомое, что-то, что уже есть готовое, что уже установлено у меня в npm модулях. Я внезапно пришел к тому что уже есть в библиотеке React. Мои модули-акторы - это то что в реакте называется компонентом. Одни компоненты могут вставлятся в другие по условию - это то что мне нужно было для акторов. Есть передача пропсов от одного компонента в другой, есть хуки которые в момент изменения состояния могут задиспатчить экшен в редакс. Все есть. Единственное что мне не нравилось - это определения. Я пытался вынести бизнес логику из react-компонентов, так как это слой UI, а в результате в проекте у меня опять могли получиться везде компоненты: и UI на react-компонентах и акторы на react-компонентах. Мне хотелось четко разделить эти слои, поэтому я сделал две функции: ghost - аналог createElement ghosts - аналог Fragment Да, я прям вот так просто присвоил: В переводе с английского означает “призрак”. Мне показалось подходящим названием для противопоставления видимым реакт компонентам. С одной стороны - это отдельное понятие для слоя бизнес логики, не связанное с UI, а с другой стороны включает в себя весь опыт реакта. Вот так выглядит использование: Никакого jsx и можно спокойно пользоваться привычными хуками. Естественно, я создал себе npm модуль react-ghost с этими двумя строчками и уже применил его на нескольких проектах. Результат меня радует, мне получилось с минимальным усложнением провести четкую границу между бизнесс логикой и UI. Но людям в команде все же приходится объяснять что это, зачем это нужно и что вместо thunk достаточно использовать useEffect. Собственно, поэтому и захотелось поделиться этим подходом и узнать ваше мнение.