460 | {todos.filter((t) => !t.done).length} items left
461 |
462 | )}
463 |
464 | );
465 | }
466 | ```
467 |
468 |
469 |
470 | Чаще всего модули очень быстро перерастают этот этап. Но иногда нет!
471 |
472 | **С файлом до 400 строк работать приемлемо.**
473 |
474 | При этом скорость разработки и рефакторинга такого модуля выше, чем модуля из 20 файлов по 20 строк
475 |
476 | Не пренебрегайте этим подходом, особенно на этапе прототипирования.
477 |
478 | ### Этап 2: Flat module
479 |
480 | Вот ваш файл стал больше 400 строк, и стало неудобно. Что выделяем `components/hooks/ui/model`?
481 |
482 | **Нет ещё рано!**
483 |
484 | Нет ничего хуже папки, в которой один файл.
485 |
486 | Второй этап эволюции, это создание плоской структуры разнородных модулей
487 |
488 | Вот пример фичи `todo-list` на втором этапе
489 |
490 |
491 |
492 | - todo-list/
493 | - todo-list-page.tsx
494 | - use-todo-list.tsx
495 | - api.tsx
496 | - use-intersection-observer.tsx
497 | - index.ts
498 |
499 |
500 |
501 | Здесь мы просто разделили код на функции. А потом вынесли их в отдельные файлы.
502 | Файлы называем в соответствии с содержимым. Избегаем названия `hooks` `components`
503 |
504 |
508 |
509 | Это число выведено эмпирически и связано с концепцией [**кошелёка миллера**](https://ru.wikipedia.org/wiki/%D0%9C%D0%B0%D0%B3%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5_%D1%87%D0%B8%D1%81%D0%BB%D0%BE_%D1%81%D0%B5%D0%BC%D1%8C_%D0%BF%D0%BB%D1%8E%D1%81-%D0%BC%D0%B8%D0%BD%D1%83%D1%81_%D0%B4%D0%B2%D0%B0)
510 |
511 | #### Public api
512 |
513 | На этом этапе уже не понятно, какой код предназначен для внешнего использования, а какой должен оставаться внутри.
514 |
515 | Поэтому мы добавляем `public-api`.
516 |
517 | Это `index.ts` файл, в котором вы реэкспортируем (иногда со сменой названия) доступные извне элементы.
518 |
519 | ```tsx
520 | export { TodoListPage } from "./todo-list-page";
521 | ```
522 |
523 |
524 | Для тех у кого next.js
525 |
526 | Если в index файлах нет сайд эффектов, то в vite tree-shaking работает корректно
527 |
528 | С next.js же встречались проблемы. Поэтому там часто используется другой подход с public-api.
529 |
530 | Все приватные файлы начинаются с \_, а публичные без. Таким образом не обязательно создавать только один public файл
531 |
532 |
533 |
534 | - todo-list/
535 | - todo-list-page.tsx
536 | - \_use-todo-list.tsx
537 | - \_api.tsx
538 | - \_use-intersection-observer.tsx
539 |
540 |
541 |
542 |
543 |
544 | #### Explicit dependencies (экспериментально)
545 |
546 | При взаимодействии модулей друг с другом, часто крайне сложно проследить все зависимости модуля от других модулей.
547 |
548 | Поэтому все зависимости, которые используются внутри, можно реэкспортировать через специальный `deps.ts` файл.
549 |
550 | _Особенно это важно для `services` и `features`, если разрешены кросс импорты (из фичи фичу, из сервиса сервис)_
551 |
552 | Пример:
553 |
554 |
555 |
556 | - features/
557 | - settings/
558 | - auth/
559 | - todo-list/
560 | - index.ts
561 | - deps.ts
562 | - todo-list-page.tsx
563 | - use-todo-list.tsx
564 | - create-todo-form.tsx
565 |
566 |
567 |
568 | deps.tsx
569 |
570 | ```tsx
571 | export { useSession } from "@/features/auth";
572 | export { useSettings } from "@/features/settings";
573 | ```
574 |
575 | todo-list-page.tsx
576 |
577 | ```tsx
578 | import { useSession, useSettings } from "./deps";
579 | ```
580 |
581 | #### Sub modules
582 |
583 | По сути, когда мы сделали такое разделение, мы получили модуль, который состоит из нескольких однофайловых модулей.
584 |
585 | Но они не обязаны быть однофайловыми. **Любой дочерний модуль может быть на любом этапе эволюции**
586 |
587 |
588 |
589 | - todo-list/
590 | - index.ts
591 | - todo-list-page.tsx
592 | - use-todo-list.tsx
593 | - create-todo-form/
594 | - index.ts
595 | - create-todo-form.tsx
596 | - use-create-todo.tsx
597 |
598 |
599 |
600 |
601 |
602 | #### Опасности вложенности
603 |
604 | Благодаря подмодулям, на втором уровне можно оставаться очень долго.
605 |
606 | Но глубокая вложенность не так хороша, как кажется. Глубокие древовидные структуры сложны для понимания.
607 | Намного комфортнее читаются **однородные списки** (слой features как раз пример однородного списка)
608 |
609 | Поэтому чаще всего для преодоления ограничения в ~6 элементов подмодулям лучше предпочитать создание `групп`
610 |
611 | ### Этап 3: Grouped module
612 |
613 | Как было сказано выше, если для разнородных папок комфортно < 6 элементов.
614 | То для однородных папок это количество резко возрастает (до 20 элементов. Сильно зависиот от однородности)
615 |
616 | Поэтому мы можем разделить подмодули на группы, таким образом сильно увеличив допустимый размер модуля
617 |
618 | #### Что такое группа.
619 |
620 | Группа это объединение нескольких модулей на основании общего признака:
621 |
622 | Примеры групп:
623 |
624 | - components
625 | - hooks
626 | - services
627 | - features
628 | - ui
629 | - model
630 | - lib
631 | - api
632 |
633 |
643 |
644 | Кстати, все слои это тоже группы.😉
645 |
646 | #### Стандартные группы
647 |
648 | Существуют достаточно удачные группы, которые хорошо себя показали:
649 |
650 | ##### Группа: `ui`
651 |
652 | По сути это объединение всех компонентов, реже хуков, которые отвечают целиком и полностью за отображение. И не несут в себе сложной логики
653 |
654 | Примеры:
655 |
656 | - todo-list-page.tsx
657 | - todo-card.tsx
658 | - todo-form.tsx
659 | - use-render-arrows.tsx
660 |
661 | ##### Группа: `model`
662 |
663 | Группа, в которой лежит основная работа с данными.
664 |
665 | Если вы на чистом react, здесь лежат хуки, которые манипулируют данными в отрыве от отображения.
666 |
667 | Если у вас стейт менеджер, то здесь будет лежать вся логика работы со стейт менеджером
668 |
669 | Примеры:
670 |
671 | - use-todo-list.ts
672 | - todo-list.slice.ts
673 | - todo-list-store.ts
674 | - todo-item.ts
675 |
676 | ##### Группа: `lib`
677 |
678 | Группа, в которой находится инфраструктурный код.
679 | Это код чаще всего предоставляет более удобные обёртки над браузерным api и библиотеками.
680 | Или просто упрощает рутинные задачи
681 |
682 | Примеры:
683 |
684 | - use-mutation-observer.ts
685 | - date.ts
686 |
687 | ##### Группа: `api`
688 |
689 | Группа для кода работы с api и типами контрактов.
690 |
691 | ##### Группа: `domain`
692 |
693 | Если логика в `model` становится очень сложной.
694 |
695 | То код, описывающий самые важные бизнес процессы:
696 |
697 | - Расчёт скидки
698 | - Вычисление отпуска
699 | - Получение прогресса
700 | - Расчёт координат при перемещении элемента по карте
701 |
702 | Можно вынести в виде `чистых функций` в группу `domain`
703 |
704 | Также в `domain` находятся все типы, над которыми эти чистые функции проводят манипуляции
705 |
706 | Примеры:
707 |
708 | - map-node.ts
709 | - get-intersections.ts
710 | - compute-next-lesson.ts
711 |
712 | ##### Группа: `view-model`
713 |
714 | В некоторых кейсах модуль содержит большое количество логики, которая обрабатывает пользовательский ввод
715 |
716 | Обычно это происходит, если реализуется `dnd` или анимации
717 |
718 | В таком случае этот код можно вынести в отдельную группу `view-model`
719 |
720 | Пример:
721 |
722 | - use-dnd.tsx
723 | - use-animation.tsx
724 |
725 | #### Вложенные группы
726 |
727 | Внутри группы вы можете группировать модули и по другим признакам.
728 |
729 | Это работает точно так же, как с подмодулями. Только не забывайте, что излишняя глубина это неудобно
730 |
731 | Пример:
732 |
733 |
734 |
735 | - ui/
736 | - fields/
737 | - file-field.tsx
738 | - text-field.tsx
739 | - select-field.tsx
740 | - date-field.tsx
741 | - create-user-form.tsx
742 | - update-user-form.tsx
743 |
744 |
745 |
746 | #### Пример модуля с группами
747 |
748 |
749 |
750 | - todo-list/
751 | - index.ts
752 | - api.ts
753 | - model/
754 | - todo-item.ts
755 | - use-create-todo.ts
756 | - use-update-todo.ts
757 | - ui/
758 | - pages/
759 | - todo-list-page.tsx
760 | - todo-details-page.tsx
761 | - fields/
762 | - file-field.tsx
763 | - text-field.tsx
764 | - select-field.tsx
765 | - date-field.tsx
766 | - create-todo-form.tsx
767 | - todo-details-form.tsx
768 | - todo-item.module.css
769 | - todo-item.tsx
770 |
771 |
772 |
773 | #### Группы и подмодули
774 |
775 | Появление групп не отменяет подмодули. Иногда чем разделять код на группы, лучше разбить весь код на несколько подмодулей
776 |
777 |
778 |
779 | - todo-list/
780 | - index.ts
781 | - api.ts
782 | - ui/
783 | - file-field.tsx
784 | - text-field.tsx
785 | - select-field.tsx
786 | - date-field.tsx
787 | - todo-list/
788 | - index.ts
789 | - todo-list-page.tsx
790 | - create-todo-form.tsx
791 | - use-create-todo.ts
792 | - todo-item.tsx
793 | - todo-details/
794 | - index.ts
795 | - todo-details-page.tsx
796 | - todo-details-form.tsx
797 | - use-update-todo.ts
798 |
799 |
800 |
801 | #### Когда переходить на следующий этап?
802 |
803 | На этом этапе можно разрабатывать модули любого размера.
804 |
805 | Но здесь есть важная проблема - **связи между подмодулями хаотичны, и в них может быть очень сложно разобраться**
806 |
807 | Если вы столкнулись с такой проблемой, вам поможет следующий этап:
808 |
809 | ### Этап 4: Module with compose
810 |
811 | Это сложный, но при этом крайне мощный паттерн борьбы с хаотическими зависимостями.
812 |
813 | Его ближайщий аналог это DIP принцип из SOLID.
814 |
815 | Основная суть этого паттерна, чтобы **убрать зависимости между подмодулями** в `model` и `ui`
816 |
817 | Для этого используются инструменты слабой связанности.
818 | (для `ui` слоты и рендер-пропсы, для `model` события, DI, или простая связь через параметры)
819 |
820 | После того как мы получили **набор независимых элементов**,
821 | всё это объединяется в специальных компонентах `медиаторах`
822 |
823 | Для них я обычно создаю отдельную группу `compose`
824 | Связи между компонентами в `compose` разрешены
825 |
826 | Пример:
827 |
828 |
829 |
830 | - todo-list/
831 | - index.ts
832 | - api.ts
833 | - compose/
834 | - todo-list-page.tsx
835 | - todo-details-page.tsx
836 | - create-todo-form.tsx
837 | - todo-details-form.tsx
838 | - domain/
839 | - todo-item.ts
840 | - model/
841 | - use-todo-list.ts
842 | - use-delete-todo.ts
843 | - use-create-todo.ts
844 | - use-update-todo.ts
845 | - ui/
846 | - fields/
847 | - file-field.tsx
848 | - text-field.tsx
849 | - select-field.tsx
850 | - date-field.tsx
851 | - todo-page-layout.tsx
852 | - todo-item.tsx
853 | - common-fields.tsx
854 | - update-button.tsx
855 | - delete-button.tsx
856 |
857 |
858 |
859 | Тогда `todo-list-page.tsx` выглядел бы как-то так
860 |
861 | ```tsx
862 | export function TodoListPage() {
863 | const todoList = useTodoList();
864 | const deleteTodo = useDeleteTodo(todoList);
865 | const createTodo = useCreateTodo(todoList);
866 | const updateTodo = useUpdateTodo(todoList);
867 |
868 | return (
869 | }
871 | todos={todoList.list.map((item) => (
872 |
873 |
874 |
875 |
876 | ))}
877 | />
878 | );
879 | }
880 | ```
881 |
882 |
883 | Где можно почитать подробнее
884 |
885 | Документация ещё активно разрабатывается. Позже об этом паттерне появится отдельная статья.
886 | Сейчас вы можете изучить следующие материаллы:
887 |
888 | - [О том как автор пришёл к такому подходу](https://www.youtube.com/watch?v=VFipNg6sVMU)
889 | - [Версия этого паттерна в gof](https://refactoring.guru/ru/design-patterns/mediator)
890 | - [Версия этого паттерна в grasp](https://hackernoon.com/grasp-principles-part-3-polymorphism-pure-fabrication-indirection-protected-variations)
891 | - [Версия этого паттерна в solid](https://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%B8%D0%BD%D1%86%D0%B8%D0%BF_%D0%B8%D0%BD%D0%B2%D0%B5%D1%80%D1%81%D0%B8%D0%B8_%D0%B7%D0%B0%D0%B2%D0%B8%D1%81%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D0%B5%D0%B9х)
892 | - [Если вы в сообществе, паттерны 8 - 12](https://evocomm.space/course/react-patterns)
893 |
894 |
895 |
896 | #### Ограничения этапа 4
897 |
898 | Самые главные здесь ограничения технические. Не все инструменты поддерживают слабую связанность.
899 | И не во всех ситуациях, это даёт нужную производительность
900 |
901 | Но в большей части ситуаций, такой подход помогает сильно уменьшить сложность модуля!
902 |
903 | ### Выводы по эволюции модулей
904 |
905 | Может быть вы уже запутались во всех вариантах модулей здесь представленных.
906 |
907 | _Это нормально, подобная гибкость чуть усложняет вход._
908 |
909 | При более глубоком рассмотрении оказывается, что это всё вариации вокруг трёх понятий: `модуль` `группа` и `public-api`.
910 |
911 | **На самом деле, этот подход одновременно и стандартизирует подходы. И предоставляет гибкость**
912 |
913 | По мере усложнения ваших модулей, вы можете строить удобный и поддерживаемый код из кирпичиков `модулей` и `групп`
914 |
915 | При этом не тратить время на постоянный оверхед от неудобных ритуалов и паттернов.
916 |
917 |
921 |
922 | ## Что дальше?
923 |
924 | Пользуясь советами из этого гайда, вы уже сейчас можете начать использовать ED.
925 |
926 | - По любым вопросам пишите в чат архитектуры в [tg](https://t.me/+VugvWY1dtdRhM2Uy).
927 | - Смотрите примеры разработки на ED в [youtube](https://www.youtube.com/playlist?list=PLMlifxDLpB1DXcNcXK4Wl8jtC2SOyWETr)
928 |
929 | Если хотите понять, а как это всё вообще работает. Приходите читать [продвинутую часть документации](/deep-dive)
930 |
931 | Сейчас эта часть только начинает развиваться, но со временем там будет появляться всё больше статей, раскрывающих концепции и паттерны
932 |
--------------------------------------------------------------------------------