[Перевод] Основы программирования на примере исходного кода React
Структуры данных
Linked List
Широко используется в кодовой базе, особенно для управления единицами работы, обновлениями и эффектами. Например, структура данных Fiber сама по себе формирует древовидную структуру посредством свойств child
, sibling
и return
. Очереди обновлений для компонентов классов и функций также реализованы в виде связанных списков, что позволяет эффективно обрабатывать обновления.
Почему React использует связанные списки
(1) Эффективное обновление и сверка
Динамическая природа пользовательских интерфейсов. Обновления пользовательского интерфейса в React часто включают добавление, удаление и изменение порядка элементов. Связанные списки хорошо подходят для таких динамических операций, поскольку позволяют эффективно вставлять и удалять узлы без необходимости перемещать элементы, как в массиве. Это крайне важно для алгоритма согласования React, который сводит к минимуму манипуляции с DOM для достижения оптимальной производительности.
Вставка и удаление в постоянное время. Связанные списки обеспечивают постоянную сложность времени (O (1)) для вставок и удалений в начале или конце списка. Такая эффективность полезна при управлении очередями обновлений и списками эффектов, где новые обновления или эффекты обычно добавляются или удаляются из начала или хвоста.
(2) Эффективность памяти
Уменьшенное выделение памяти. Связанные списки динамически выделяют память по мере необходимости, добавляя новые узлы только при необходимости. Это контрастирует с массивами, которые часто требуют предварительного выделения фиксированного объема памяти, даже если не все слоты используются. Для больших и динамических пользовательских интерфейсов связанные списки могут быть более эффективными с точки зрения использования памяти.
Гибкое использование памяти. В виртуальном DOM React каждый fiber узел (единица работы) представлен структурой связанного списка. Такая гибкость позволяет библиотеке выделять и освобождать память для отдельных fiber узлов по мере их входа и выхода из процесса согласования, оптимизируя использование памяти.
(3) Парадигма функционального программирования
Неизменяемость. React поддерживает неизменяемость, при которой структуры данных не изменяются напрямую, а заменяются новыми версиями. Связанные списки естественным образом подходят для этой парадигмы, поскольку обновление списка предполагает создание нового списка с желаемыми изменениями, оставляя исходный список нетронутым.
Рекурсия. Связанные списки хорошо подходят для рекурсивных алгоритмов, которые распространены в функциональном программировании. Алгоритм согласования React и некоторые другие внутренние процессы используют рекурсию для эффективного перемещения по дереву fiber узлов и манипулирования им.
Stack
Используется для управления контекстом, обработчиками приостановки и другой контекстной информацией на этапах рендеринга и фиксации. Модуль ReactFiberStack.js предоставляет такие функции, как push
, pop
и createCursor
для работы со стеками.
Map
Используется для управления различными сопоставлениями, например картой записей в ReactCacheOld.js, которая связывает ресурсы с соответствующими записями, и карту _chunks
в ReactFlightClient.js для хранения фрагментов ответа.
Set
Используется для хранения уникальных значений, например, набора _updatedFibers
в ReactFiberTracingMarkerComponent.js, который отслеживает единицы работы, обновляемые во время перехода.
Heap (Min Heap)
пакет scheduler, используемый React для управления задачами, использует структуру данных минимальной кучи для определения приоритета задач на основе времени их истечения.
Trees
Сам виртуальный DOM представляет собой древовидную структуру, представленную узлами «Fiber». Кроме того, ReactFiberTreeContext.js управляет отдельной древовидной структурой для генерации уникальных идентификаторов во время гидратации.
Алгоритмы
Reconciliation Algorithm
Основной алгоритм React, отвечающий за эффективное обновление пользовательского интерфейса путем сравнения текущего и следующего виртуальных деревьев DOM и внесения минимальных изменений в реальный DOM. Это включает в себя обход деревьев, сравнение узлов и вычисление минимальных операций сравнения. Реализовано в различных файлах, таких как ReactChildFiber.js и ReactFiberBeginWork.js.
LRU Cache
Модуль LRU.js реализует LRU (Вытеснение давно неиспользуемых) кэш для хранения и управления кэшированными данными. Этот алгоритм помогает оптимизировать производительность за счет повторного использования ранее вычисленных значений.
Scheduler Algorithm
Пакет scheduler использует приоритетную очередь на основе минимальной кучи для планирования и определения приоритетов задач на основе времени их истечения и уровней приоритета.
String Encoding/Decoding
Используется в ReactFlightClient.js и других. модули для эффективной передачи данных между сервером и клиентом.
Context Propagation
Алгоритмы эффективного распространения изменений контекста через дерево компонентов, особенно с введением ленивого распространения контекста.
Hydration Algorithm
Используется для эффективного «присоединения» обработчиков к существующей разметке HTML во время первоначального рендеринга вместо создания DOM с нуля.
Error Handling
Алгоритмы обработки ошибок и резервного варианта Suspense, обход дерева единиц работы в поисках ближайшей подходящей границы ошибки.
Шаблоны проектирования
Module Pattern
Код организован в модули, каждый из которых имеет свою ответственность и API. Это способствует повторному использованию кода, удобству сопровождения и разделению задач. Например, ReactChildren.js предоставляет функции для работы с дочерними элементами, а ReactContext.js занимается созданием контекста и управлением им.
Factory Pattern
createElement и cloneElement функции действуют как фабрики для создания и клонирования элементов React. Это обеспечивает центральную точку для создания элементов и обеспечивает согласованность их структуры.
Observer Pattern
Хук useSyncExternalStore реализует шаблон наблюдателя, при котором компоненты подписываются на внешнее хранилище и повторно отрисовывают его при обновлении хранилища. Это обеспечивает эффективное управление состоянием и синхронизацию с внешними источниками данных.
Provider Pattern
Контекстный API, реализованный в ReactContext.js, использует шаблон провайдера. Поставщик контекста делает значение доступным для своих компонентов-потомков, позволяя передавать данные вниз по дереву компонентов без явной передачи реквизитов через каждый уровень.
Decorator Pattern
Компоненты высшего порядка (HOC
) и некоторые хуки представляют этот шаблон. HOC: forwardRef
, memo
. Хуки: useMemo
, useCallback
. HOC и хуки обогащают компоненты и функции дополнительным поведением, не изменяя их основную реализацию. Это способствует гибкости и повторному использованию кода.
SOLID принципы
Принцип единой ответственности (SRP)
Каждый модуль и функция имеют четко определенную ответственность. Например, ReactFiberBeginWork.js фокусируется на начальной фазе процесса согласования, а ReactFiberCompleteWork.js обрабатывает полную фазу.
Принцип открытости/закрытости (OCP)
Библиотека открыта для расширения, но закрыта для модификации. Это видно по использованию хуков, которые позволяют добавлять новую функциональность без изменения основных компонентов React.
Принцип подстановки Лисков (LSP)
Подтипы компонентов (например, PureComponent и memo компоненты) можно использовать везде, где ожидается Component
, обеспечение последовательного поведения.
Принцип разделения интерфейсов (ISP)
Библиотека предоставляет несколько меньших, сфокусированных интерфейсов (например, Dispatcher), а не большие монолитные. Это уменьшает связанность и упрощает использование определенных частей библиотеки без зависимости от ненужной функциональности.
Принцип инверсии зависимостей (DIP)
Библиотека опирается на абстракции (например, ReactFiberReconciler использует HostConfig интерфейс) вместо конкретных реализаций, что позволяет использовать разные средства визуализации с одной и той же базовой логикой.