Сложное приложение разрабатывается из простого приложения.По мере того, как в проект будет добавляться все больше и больше функций, код будет становиться все труднее и труднее контролировать.В этой статье в основном обсуждается, как организовать компоненты в больших проектах., чтобы сделать проект ремонтопригодным .
Каталог серий
- 01 Проверка типа
- 02 Организация компонентов
- 03 Управление стилем
- 04 Компонентное мышление
- 05 Управление статусом
содержание
- 1. Основные принципы проектирования компонентов
- 2. Базовые навыки
- 3. Классификация компонентов
- 4. Раздел каталога
- 5. Модули
- 6. Сплит
- 7. Пример разделения компонентов
- 8. Документация
- расширять
1. Основные принципы проектирования компонентов
Основной принцип
Принцип единой ответственности. Это происходит из объектно-ориентированного программирования. Нормативное определение: «класс должен иметь только одну причину для изменения», а на просторечии говорится, что «класс отвечает только за одну вещь». Независимо от того, какая парадигма программирования, пока поскольку это модульное программирование, применяется принцип единой ответственности.В React компоненты являются модулями.
Единая ответственность требует, чтобы компоненты были ограничены «соответствующей» степенью детализации. Эта степень детализации является более субъективным понятием, другими словами, «единая» является относительной концепцией. Лично я считаю, что единая ответственность не является стремлением к «минимизации» степени детализации ответственности. , Минимизация детализации является крайностью и может привести к большому количеству модулей, а дискретизация модулей также может сделать проект неуправляемым.Единая ответственность требует детализации, подходящей для повторного использования.
Часто компоненты, которые мы разрабатываем в начале, могут иметь несколько обязанностей, а позднее дублирование кода или границы модулей нарушаются (например, один модуль зависит от «деталей» другого модуля), и мы будем лениво извлекать повторно используемый код. все больше и больше рефакторинга и итераций, обязанности модулей могут становиться все более и более «одиночными» (😂 кто угодно, тоже может стать лапшой).
Конечно, опытные разработчики могут с самого начала рассматривать различные сценарии применения компонентов, а также могут наблюдать перекрывающиеся границы модулей.Для начинающихDon't repeat yourself
Принципы полезнее, не ленитесь/больше думайте/рефакторинг/удаляйте повторяющийся код, ваши способности будут постепенно улучшаться
Преимущества единой ответственности:
- Уменьшите сложность компонентов. Один компонент с одной обязанностью содержит меньше кода, прост для понимания и имеет высокую читабельность.
- Уменьшите сцепление с другими компонентами.Когда приходят изменения, можно уменьшить влияние на другие функции, чтобы не затрагивать весь организм
- Улучшение возможности повторного использования. Чем проще функция, тем выше возможность повторного использования, например, некоторых основных компонентов.
Характеристики высококачественных компонентов
Качественный компонент должен быть高内聚, 低耦合
из, эти два принципа или характеристики являются критерием для суждения о независимости компонентов.
Высокая сплоченность, требует, чтобы компонент имел четкие границы компонента, собирал близко связанный контент в одном компоненте и выполнял "специфические" функции. В отличие от традиционного внешнего программирования, компонент — этоавтономныйОн содержит логику/стиль/структуру и даже зависимые статические ресурсы.Это тоже делает компонент относительно самостоятельным индивидуумом.Конечно,эта независимость относительна.Чтобы максимизировать эту независимость,нужноединственная ответственностьРазделите компоненты на более мелкие компоненты, которые можно более гибко комбинировать и повторно использовать.
Хотя компонент является независимым, его необходимо объединить с другими компонентами для реализации приложения, что называется «ассоциацией».Низкая муфтаТребуется свести к минимуму эту ассоциацию, например, четкие границы модулей не должны иметь доступ к внутренним деталям других компонентов, интерфейсы компонентов сведены к минимуму, односторонний поток данных и т. д.
В последующем содержании статьи в основном обсуждается реализацияВысокая сплоченность/низкая связанностьОсновные меры
2. Базовые навыки
Эти техники исходят изreact-bits:
- Если компоненту не нужно состояние, используйтекомпоненты без сохранения состояния
- Сравнение производительности:Функции без состояния > Функции с состоянием > компоненты класса
- Минимизируйте реквизиты (интерфейс). Не передавайте больше реквизитов, чем требуется.
- Если внутри компонента много условного потока управления, это обычно означает, что компонент необходимо извлечь.
- Не оптимизируйте преждевременно. Просто требуйте, чтобы компоненты можно было повторно использовать для текущих нужд, и «появляйтесь»
3. Классификация компонентов
1️⃣компонент контейнераа такжеКомпоненты дисплеяотдельный
Разделение компонентов контейнера и компонентов представленияЭто важная идея разработки React, и она влияет на организацию и архитектуру проектов приложений React.Ниже резюмируется разница между ними:
компонент контейнера | Компоненты дисплея | |
---|---|---|
точка фокусировки | бизнес | UI |
источник данных | Государственный менеджер/Бэкенд | props |
форма компонента | компоненты более высокого порядка | общие компоненты |
-
Компоненты дисплеяЭто «компонент», который фокусируется только на представлении. Чтобы его можно было повторно использовать в нескольких местах, его не следует связывать с «бизнесом/функцией» или чрезмерно связывать.
antd
Этот тип библиотеки компонентов предоставляет общие компоненты, которые, очевидно, являются «компонентами дисплея».Ниже приведена типичная структура каталогов приложений, мы можем видетьКомпонент дисплея может иметь разную степень связи с бизнесом/функцией. Чем ниже степень связи с бизнесом, тем выше универсальность/возможность повторного использования.:
node_modules/antd/ 🔴 通用的组件库, 不能和任何项目的业务耦合 src/ components/ 🔴 项目通用的组件库, 可以被多个容器/页面组件共享 containers/ Foo/ components/ 🔴 容器/页面组件特有的组件库, 和一个业务/功能深度耦合. 以致于不能被其他容器组件共享 index.tsx Bar/ components/ index.tsx
Что касается компонентов дисплея, нам необходимо рассмотреть дизайн компонентов на основе стандарта «библиотеки сторонних компонентов», уменьшить связь с бизнесом, рассмотреть различные сценарии приложений и разработать общедоступный интерфейс.
-
компонент контейнераОсновное внимание уделяется бизнес-обработке. Компоненты-контейнеры обычно существуют в виде «компонентов более высокого порядка», которые обычно ① получают данные из внешних источников данных (менеджеры состояний, такие как избыточные или напрямую запрашивают данные сервера), и ② объединяютКомпоненты дисплеядля построения полного представления.
компонент контейнеракомбинируяКомпоненты дисплеяЧтобы построить полный вид, но два не обязательно являются простыми отношениями, которые включены.
容器组件和展示组件的分离
Это может принести пользу в первую очередьвозможность повторного использованияа такжеремонтопригодность:
- Повторное использование: Компоненты представления могут использоваться для нескольких различных источников данных (компоненты-контейнеры). Компоненты-контейнеры (бизнес-логика) также могут повторно использоваться для компонентов представления на разных «платформах».
- Лучшее разделение компонентов представления и контейнера для лучшего понимания приложения и пользовательского интерфейса, которые можно поддерживать независимо друг от друга.
- Дисплей в сборе становится легким (состояние/или местное состояние), его легче тестировать
понять большеPresentational and Container Components
2️⃣ Разделить логику и вид
容器组件和展示组件
Разделение по существу逻辑和视图
разделение. вReact Hooks
После появления компоненты-контейнеры могут быть заменены хуками, которые можно более естественно отделить от уровня представления, предоставляя чистый источник данных для уровня представления.
Извлеченную пост-бизнес-логику можно повторно использовать на разных «платформах отображения», таких как веб-версия и нативная версия:
Login/
useLogin.ts // 可复用的业务逻辑
index.web.tsx
index.tsx
использовано вышеuseLogin.tsx
Поддерживать бизнес-логику отдельно, ее можно переиспользовать кодом веб-платформы и нативной платформы.
не толькоБизнес-логика, выставкакомпонентная логикаЕго также можно разделить.Например, на картинке выше,FilePicker
а такжеImagePicker
Логика «загрузки файла» для двух компонентов является общей, и эта часть логики может быть извлечена для компонентов более высокого порядка или перехватчиков или даже контекста (поведение загрузки файла может быть настроено единообразно).
Основные способы разделения логики и представлений:
- hooks
- компоненты более высокого порядка
- Render Props
- Context
3️⃣ Компоненты с состоянием и без состояния
Компоненты без состояния не хранят состояние внутри, они полностью отображаются внешними реквизитами.Такие компоненты существуют в виде функциональных компонентов, таких как низкоуровневые/высокоуровневые презентационные компоненты с высоким уровнем повторного использования. Компоненты без состояния, естественно, являются «чистыми компонентами». Если сопоставление компонентов без состояния требует небольших затрат, вы можете использовать пакет React.memo, чтобы избежать повторного рендеринга.
4️⃣ Чистые компоненты и нечистые компоненты
«Чистый» из чистых компонентов исходит из функционального программирования.Для функции при одних и тех же входных данных она всегда возвращает один и тот же результат, процесс не имеет побочных эффектов и дополнительных зависимостей состояния.В соответствии с React, чистые компоненты относятся к реквизиту (строго говоря, есть еще состояние и контекст, которые также являются входом компонента), и вывод компонента не изменится, если не будет изменений.
По сравнению с выходной моделью компонентов React,CyclejsАбстракция ввода/вывода компонентов более тщательная и более «функциональная». Его компонент представляет собой обычную функцию только с «односторонним» вводом и выводом:
Функциональное программирование и компонентное программирование в некотором смысле одинаковы, они оба являются искусством «композиции». Большая функция может состоять из одной функции с несколькими обязанностями. То же самое верно и для компонентов.Мы разделяем большой компонент на подкомпоненты, чтобы иметь более детальный контроль над компонентами, поддерживать их чистоту и делать их обязанности более едиными и независимыми.Преимущества этого заключаются в возможности повторного использования, тестируемости, стабильности и предсказуемости.
Чистые компоненты также важны для оптимизации производительности в React., Если компонент является чистым компонентом, если «входные данные» не меняются, то компонент не нуждается в повторном рендеринге.Чем больше дерево компонентов, тем выше преимущества оптимизации производительности, обеспечиваемые чистыми компонентами.
Мы можем легко гарантировать чистоту низкоуровневого компонента, потому что он по своей сути прост, но для построения сложного дерева компонентов требуется немного усилий, поэтому возникает необходимость в «управлении состоянием».Эти менеджеры состояний обычно находятся в дереве компонентов.внешнийПоддерживайте одну или несколько библиотек состояний, а затем внедряйте локальное состояние в поддеревья посредством внедрения зависимостей.Поддерживайте чистоту дерева компонентов за счет принципа разделения представлений и логики..
Redux — типичное решение, которое можно рассматривать в мире Redux.Сложное дерево компонентов — это карта дерева состояний., пока дерево состояний (которое должно полагаться на неизменяемые данные для обеспечения предсказуемости состояния) не меняется, дерево компонентов не меняется Redux рекомендует поддерживать чистоту компонента и передавать состояние компонента в Redux и поддержка инструментов асинхронной обработки для обслуживания. Это абстрагирует все приложение в «односторонний поток данных», который представляет собой простое отношение «ввод/вывод».
Будь то Cyclejs или Redux, абстракция обходится дорого, например, код redux может быть многословным; сложное дерево состояний, если оно не будет хорошо организовано, все приложение станет трудным для понимания. На самом деле, не все сценарии можно гладко/элегантно выразить с помощью «управления данными» (см. эту статью).Нарушает ли Modal.confirm шаблон React?), такие как фокус текстового поля или модальные поля.
В будущем я напишу специальную статью для обзора и обобщения управления состоянием.
расширение:
5️⃣ По UI делится на布局组件
а также内容组件
- Компоненты макета используются для управления макетом страницы и предоставления заполнителей для компонентов содержимого. Заполните компоненты, передав реквизиты, например
Grid
,Layout
,HorizontalSplit
- Компоненты контента содержат некоторый контент, а не только макет. Компоненты контента обычно ограничиваются заполнителями компонентами макета. Например
Button
,Label
,Input
Например, на рисунке ниже List/List.Item — это компонент макета, а Input и Address — компоненты содержимого.
Извлечение макета из компонента содержимого и разделение макета и содержимого может сделать их более удобными в обслуживании.Например, изменения макета не повлияют на содержимое, а компонент содержимого можно применять с различными макетами; с другой стороны, компонент автономный связанный блок, изолированный, не должен влиять на его внешнее состояние, например, кнопка не должна изменять внешний макет, а также не должна влиять на глобальный стиль
6️⃣ Компоненты ввода данных с единым интерфейсом
Компоненты ввода данных или формы являются важным элементом разработки на стороне клиента.Для пользовательских компонентов формы, я думаю, должен поддерживаться согласованный API:
interface Props<T> {
value?: T;
onChange: (value?: T) => void;
}
Преимущества этого:
-
Близко к родным примитивам элементов формы. Пользовательские компоненты формы, как правило, не нужно инкапсулировать в объекты событий.
-
Почти все пользовательские формы в библиотеках компонентов используют этот API, что делает наши пользовательские компоненты совместимыми со сторонними библиотеками, такими какмеханизм проверки формы antd
-
Проще динамический рендеринг. Поскольку интерфейс согласован, динамический рендеринг или централизованная обработка могут быть легко выполнены, что уменьшает дублирование кода.
-
Проблема эхо. Государственное echo - один из компонентов функциональной формы, моей личной практики
value
Должен быть автономным:Например, для пользовательского селектора, который поддерживает поиск, параметры загружаются асинхронно из бэкэнда. Если значение сохраняет только идентификатор пользователя, имя пользователя не может отображаться во время эха. Согласно моей практике, структура значения должна быть:
{id: string, name: string}
, который решает проблему эха.Данные, необходимые для эха, передаются родительским узлом, а не поддерживаются самим компонентом. -
Все компоненты контролируются. В реальной разработке React очень мало сценариев неконтролируемых компонентов. Я думаю, что пользовательские компоненты могут игнорировать это требование и предоставлять только полностью контролируемые компоненты формы, чтобы компоненты не сохраняли состояние кеша сами по себе.
4. Раздел каталога
1️⃣ Базовая структура каталогов
Существует два популярных шаблона разделения структуры каталогов проекта:
-
Rails-style/by-type: Разделить на разные каталоги в зависимости от типа файла, например
components
,constants
,typings
,views
- Domain-style/by-feature: Создайте отдельную папку в соответствии с функцией или бизнесом, содержащую несколько типов файлов или каталогов.
Фактическая среда проекта, которую мы обычно используем,режим смешивания, вот типичная структура проекта React:
src/
components/ # 🔴 项目通用的‘展示组件’
Button/
index.tsx # 组件的入口, 导出组件
Groups.tsx # 子组件
loading.svg # 静态资源
style.css # 组件样式
...
index.ts # 到处所有组件
containers/ # 🔴 包含'容器组件'和'页面组件'
LoginPage/ # 页面组件, 例如登录
components/ # 页面级别展示组件,这些组件不能复用与其他页面组件。
Button.tsx # 组件未必是一个目录形式,对于一个简单组件可以是一个单文件形式. 但还是推荐使用目录,方便扩展
Panel.tsx
reducer.ts # redux reduces
useLogin.ts # (可选)放置'逻辑', 按照👆分离逻辑和视图的原则,将逻辑、状态处理抽取到hook文件
types.ts # typescript 类型声明
style.css
logo.png
message.ts
constants.ts
index.tsx
HomePage/
...
index.tsx # 🔴应用根组件
hooks/ # 🔴可复用的hook
useList.ts
usePromise.ts
...
index.tsx # 应用入口, 在这里使用ReactDOM对跟组件进行渲染
stores.ts # redux stores
contants.ts # 全局常量
использовать вышеDomain-style
стиль разделенLoginPage
а такжеHomePage
каталог, в котором собраны все файлы, связанные с этим бизнесом или страницей; также используется здесьRails-style
выкройка по файлуТип/ОтветственностьРазделите разные каталоги, такие какcomponents
, hooks
, containers
; вы найдете вLoginPage
Есть похожиеRails-Style
структуру, напримерcomponents
, но этообъемотличается, принадлежит толькоLoginPage
, не может использоваться другими Страницами
Интерфейсные проекты обычно разделяют компоненты в соответствии с маршрутизацией страниц. Эти компоненты пока называются «компонентами страниц». Эти компоненты связаны с бизнес-функциями, и каждая страница имеет определенную степень независимости.
Здесь компоненты страницы размещены вcontainers
, Как следует из названия, этот каталог изначально использовался для размещения компонентов контейнера. В реальных проектах «компоненты контейнера» и «компоненты страницы» обычно смешиваются вместе. На этом этапе, если вы хотите добиться чистого логического разделения, я лично думаю он должен быть извлечен, чтобы перехватить.Этот каталог также может называться представлениями, страницами... (как угодно), называть его контейнерами - это просто привычка (из Redux).
расширение:
2️⃣ Разделение каталога многостраничных приложений
Для больших приложений может быть несколько записей приложений, например, многие электронные приложения имеют несколько окон; например, многие приложения имеют интерфейсы фонового управления в дополнение к приложению.Я обычно организую многостраничные приложения следующим образом:
src/
components/ # 共享组件
containers/
Admin/ # 后台管理页面
components/ # 后台特定的组件库
LoginPage/
index.tsx
...
App/
components/ # App特定的组件库
LoginPage/ # App页面
index.tsx
stores.ts # redux stores
AnotherApp/ # 另外一个App页面
hooks/
...
app.tsx # 应用入口
anotherApp.tsx # 应用入口
admin.tsx # 后台入口
webpack поддерживает создание многостраничных приложений, я обычно называю файл записи приложения как*.page.tsx
, а затем автоматически сканировать соответствующие файлы как записи в src.
Используйте веб-пакетыSplitChunksPlugin
Можно автоматически извлекать общие модули для многостраничных приложений, что очень важно для многостраничных приложений с аналогичными функциями и более общим кодом.Это означает, что ресурсы оптимизируются вместе, а общие модули извлекаются, что выгодно для сокращения размер скомпилированных файлов и облегчение обмена Кэш браузера.
html-webpack-plugin
4.0 поддерживает вставку общих чанков, до этого вам нужно явно определить общие чанки через SplitChunksPlugin, а затем html-webpack-plugin явно вставит чанки, что расстраивает.
3️⃣ Разделение каталога многостраничных приложений: режим монорепозитория
Вышеупомянутым способом все страницы сгруппированы в рамках одного проекта с общими зависимостями и модулями npm, что может привести к некоторым проблемам:
- Невозможно разрешить разным страницам иметь разные версии зависимостей
- Для несвязанных приложений этот метод организации сделает код запутанным, например приложение и серверная часть, используемые ими технологический стек/библиотека компонентов/взаимодействия могут быть совершенно разными, и легко вызвать конфликты имен.
- Повышение производительности. Вы хотите создавать и поддерживать страницу по отдельности, а не все вместе.
Этот сценарий можно использоватьlernaилиyarn workspaceЭтот механизм монорепозитория изолирует многостраничные приложения в разных модулях npm, взяв в качестве примера рабочее пространство пряжи:
package.json
yarn.lock
node_modules/ # 所有依赖都会安装在这里, 方便yarn对依赖进行优化
share/ # 🔴 共享模块
hooks/
utils/
admin/ # 🔴 后台管理应用
components/
containers/
index.tsx
package.json # 声明自己的模块以及share模块的依赖
app/ # 🔴 后台管理应用
components/
containers/
index.tsx
package.json # 声明自己的模块以及share模块的依赖
расширение:
4️⃣ Кроссплатформенное приложение
React можно использовать для разработки нативных приложений с использованием ReactNative.react-native-web
Такое решение, API/функции/метод разработки Web и Native и даже требования к продукту могут сильно отличаться, и со временем может появиться большое количество неуправляемых кодов адаптации, кроме того, сам react-native-web тоже может стать точкой риска. Поэтому некоторым командам необходимо разрабатывать для разных платформ и вообще организовывать кроссплатформенные приложения по следующим стилям:
src/
components/
Button/
index.tsx # 🔴 ReactNative 组件
index.web.tsx # 🔴 web组件, 以web.tsx为后缀
loading.svg # 静态资源
style.css # 组件样式
...
index.ts
index.web.ts
containers/
LoginPage/
components/
....
useLogin.ts # 🔴 存放分离的逻辑,可以在React Native和Web组件中共享
index.web.tsx
index.tsx
HomePage/
...
index.tsx
hooks/
useList.ts
usePromise.ts
...
index.web.tsx # web应用入口
index.tsx # React Native 应用入口
доступно через вебпакresolve.extensions
для настройки приоритета завершения расширения.antd-mobileВот так это организовано.
5️⃣ Еще один способ кроссплатформенности: таро
Для отечественных разработчиков кроссплатформенность не так проста, как Native, у нас также есть множество небольших программ и небольших приложений. ТерминалфрагментацияДелайте фронтенд-разработку все более и более сложной задачей.
Taro родился таким образом Taro основан на стандартном синтаксисе React (DSL), в сочетании с идеей принципа компиляции, преобразует набор кода в целевой код различных терминалов и предоставляет унифицированный набор встроенных компонентов библиотека и SDK для сглаживания разницы между несколькими терминалами
Потому что Taro использует стандартный синтаксис и API React, что позволяет нам разрабатывать многотерминальные приложения в соответствии с исходными соглашениями и привычками разработки React и поддерживать только один набор кода.Но не забывайте, что абстракция обходится дорого.
Вы можете просмотреть официальную документацию Таропонять больше
FlutterЭто недавнее сравнение или кроссплатформенное решение, но оно не имеет отношения к теме этой статьи.
5. Модули
1️⃣ Создавайте строгие границы модулей
Следующая картинка представляет собой модуль импорта определенной страницы, что довольно запутанно, что приемлемо. Я также видел компоненты с тысячами строк, из которых на оператор импорта модуля приходится более 100 строк. Частично это может быть связано к функции автоматического импорта VsCode В результате (операторы импорта могут быть упорядочены и сгруппированы по правилам tslint), более серьезной причиной является отсутствие организации этих модулей.
Я чувствую, что должны быть созданы строгие границы модулей,Модуль имеет только один унифицированный «экспорт».. Например, сложный компонент:
ComplexPage/
components/
Foo.tsx
Bar.tsx
constants.ts
reducers.ts
style.css
types.ts
index.tsx # 出口
Это можно считать«Каталог» — это граница модуля. тыне следуетТаким образом импортируется модуль:
import ComplexPage from '../ComplexPage';
import Foo from '../ComplexPage/components/Foo';
import Foo from '../ComplexPage/components/Bar';
import { XXX } from '../ComplexPage/components/constants';
import { User, ComplexPageProps } from '../ComplexPage/components/type';
Модуль/каталог должен иметь файл «экспорта» для унифицированного управления экспортом модуля, ограничивая видимость модуля., Например, вышеуказанный модуль,components/Foo
,components/Bar
а такжеconstants.ts
Эти файлы на самом делеComplexPage
«Детали реализации» компонента — это детали реализации, которые внешние модули не должны разделять, но они не имеют механизма ограничения на уровне языка и могут полагаться только на соглашения спецификации.
Когда другие модули зависят от «деталей» модуля, это может быть сигналом рефакторинга: например, в зависимости от служебной функции модуля или объявления типа объекта, в это время он может быть повышен до родительского модуля, чтобы одноуровневые модули разделяли его.
во фронтенд проектеindex
Этот файл лучше всего подходит в качестве файла «экспорта». При импорте каталога средство поиска модулей будет искать индексный файл, который существует в каталоге. При разработке API модуля разработчикам необходимо учитывать различные методы использования модуля и используйте индекс видимости модуля управления файлами:
// 导入外部模块需要使用的类型
export * from './type';
export * from './constants';
export * from './reducers';
// 不暴露外部不需要关心的实现细节
// export * from './components/Foo'
// export * from './components/Bar'
// 模块的默认导出
export { ComplexPage as default } from './ComplexPage';
Операторы импорта теперь могут быть более краткими:
import ComplexPage, { ComplexPageProps, User, XXX } from '../ComplexPage';
Это правило также можно использовать для библиотек компонентов.До того, как функция встряхивания дерева веб-пакета была незрелой, мы все использовали различные уловки для ее достижения.按需导入
, напримерbabel-plugin-import
или прямой импорт подпути:
import TextField from '~/components/TextField';
import SelectField from '~/components/SelectField';
import RaisedButton from '~/components/RaisedButton';
теперь доступноNamed import
Импортируйте напрямую и позвольте веб-пакету оптимизировать его для вас:
import { TextField, SelectField, RaisedButton } from '~/components';
Но не во всех каталогах есть файлы экспорта, значит каталог не является границей модуля, Обычноutils/
, utils
просто пространство имен модуля,utils
Следующие файлы не связаны между собой или относятся к разным типам файлов:
utils/
common.ts
dom.ts
sdk.ts
Мы привыкли ссылаться на эти файлы напрямую, а не через файл ввода, что делает более понятным, какой тип импортируется:
import { hide } from './utils/dom'; // 通过文件名可以知道, 这可能是隐藏某个DOM元素
import { hide } from './utils/sdk'; // webview sdk 提供的的某个方法
Наконец, резюмируя:
В соответствии с принципом границы модуля (как показано выше):Модуль может получить доступ к родственным модулям (в той же области), предкам и одноуровневым модулям предков.Например:
- Бар может получить доступ к Foo, но больше не может получить доступ к его деталям, т.е. нет доступа
../Foo/types.ts
, но может получить доступ к его экспортному файлу../Foo
- src/types.ts не может получить доступ к containers/HomePage
- Страница входа и доступ к домашней странице
- LoginPage может получить доступ к utils/sdk
2️⃣Named export
vs default export
Эти два метода экспорта имеют свои собственные применимые сценарии, и мы не должны использовать здесь определенный метод экспорта.Давайте сначала посмотримВ чем преимущества именного экспорта:
-
Именование ОК
-
Упрощение рефакторинга Typescript
-
Удобное интеллектуальное напоминание и автоматическое распознавание импорта
-
легкий реэкспорт
// named export * from './named-export'; // default export { default as Foo } from './default-export';
-
-
Модуль поддерживает несколько
named export
посмотри сноваdefault export
Каковы преимущества?:
-
default export
Обычно это означает «сам модуль». Когда мы используем «импорт по умолчанию» для импорта модуля, разработчики, естественно, знают, какой объект импортируется по умолчанию.Например, react экспортирует объект React, LoginPage экспортирует страницу входа, somg.png импортирует изображение. Этот тип модуля всегда имеет определенный «тематический объект». Таким образом, импортируемое имя по умолчанию и имя модуля совпадают: последовательный (автоматический импорт Typescript основан на имени файла).
Конечно, «предметный объект» — это неявное понятие, вы можете ограничить его только спецификациями.
-
default export
Оператор импорта более краток. Напримерlazy(import('./MyPage'))
default export
Есть и некоторые недостатки:
- Трудно понять при взаимодействии с другими механизмами модулей (commonjs).Например, мы бы импортировали так
default export
:require('./xx').default
-
named import
Преимущество в том, чтоdefault export
Недостатки
Итак, подведем итог:
- Для «основного объекта» явные модули должны иметь экспорт по умолчанию, например компоненты страницы, классы
- Экспорт по умолчанию не следует использовать для модулей с неоднозначными «объектами тела», такими как библиотеки компонентов, утилиты (куда помещаются различные служебные методы), константы, константы
Следуя этому правилу, каталог компонентов можно организовать следующим образом:
components/
Foo/
Foo.tsx
types.ts
constants.ts
index.ts # 导出Foo组件
Bar/
Bar.tsx
index.tsx
index.ts # 导出所有组件
Для модуля Foo есть основной объект, который является компонентом Foo, поэтому здесь мы используемdefault export
Экспортированный компонент Foo, код:
// index.tsx
// 这三个文件全部使用named export导出
export * from './contants';
export * from './types';
export * from './Foo';
// 导入主体对象
export { Foo as default } from './Foo';
Теперь предположим, что компонент Bar зависит от Foo:
// components/Bar/Bar.tsx
import React from 'react';
// 导入Foo组件, 根据模块边界规则, 不能直接引用../Foo/Foo.tsx
import Foo from '../Foo';
export const Bar = () => {
return (
<div>
<Foo />
</div>
);
};
export default Bar;
дляcomponents
Для модуля все его подмодули равны, поэтому нет предметного объекта,default export
Здесь не применимо.components/index.ts
Код:
// components/index.ts
export * from './Foo';
export * from './Bar';
3️⃣ Избегайте циклических зависимостей
Циклические зависимости — признак плохой конструкции модуля, в это время вам нужно подумать о разделении и разработке файла модуля, например
// --- Foo.tsx ---
import Bar from './Bar';
export interface SomeType {}
export const Foo = () => {};
Foo.Bar = Bar;
// --- Bar.tsx ----
import { SomeType } from './Foo';
...
Компоненты Foo и Bar выше образуют простую циклическую зависимость, хотя это не вызывает никаких проблем во время выполнения Решение состоит в том, чтобы извлечь SomeType в отдельный файл:
// --- types.ts ---
export interface SomeType {}
// --- Foo.tsx ---
import Bar from './Bar';
import {SomeType} from './types'
export const Foo = () => {};
...
Foo.Bar = Bar;
// --- Bar.tsx ----
import {SomeType} from './types'
...
4️⃣ Относительный путь не должен превышать двух уровней
Когда проект становится все более и более сложным, каталог может становиться все глубже и глубже, и появится этот путь импорта:
import { hide } from '../../../utils/dom';
Во-первых, такого рода операторы импорта очень неэлегантны и плохо читаемы.Когда вы не знаете контекст каталога текущего файла, вы не знаете, где находится конкретный модуль, даже если вы знаете расположение текущего файла. файл, вам нужно следовать по пути импорта Поднимаясь вверх по дереву каталогов, вы можете найти конкретный модуль, поэтому такой относительный путь более античеловеческий.
Кроме того, такие пути импорта не удобны для переноса модулей (хотя Vscode поддерживает рефакторинг путей импорта при перемещении файлов), и при переносе файлов необходимо переписать эти относительные пути импорта.
Поэтому обычно рекомендуется, чтобы импорт относительного пути не превышал двух уровней, т.../
а также./
Вы можете попробовать поставитьПреобразование относительного пути в форму абсолютного пути, напримерwebpack
можно настроить вresolve.alias
свойства для реализации:
...
resolve: {
...
alias: {
// 可以直接使用~访问相对于src目录的模块
// 如 ~/components/Button
'~': context,
},
}
Теперь мы можем импортировать относительноsrc
модуль:
import { hide } from '~/utils/dom';
расширять
- Настраивается для машинописного текстаpathsопции;
- Для Babel вы можете использовать
babel-plugin-module-resolver
Плагин для преобразования в относительный путь
6. Сплит
1️⃣ Раздельный метод рендеринга
Когда структура JSX метода рендеринга очень сложна, первое, что нужно попробовать, — это разделить JSX, самый простой способ — разделить его на несколько вспомогательных методов рендеринга:
Конечно, этот метод только временно делает метод рендеринга менее сложным, он не разделяет сам компонент, и все входные данные и состояния по-прежнему собираются под одним компонентом, поэтому обычно разделение метода рендеринга — это только первый шаг рефакторинга: с Компоненты становятся все сложнее, а файл все длиннее и длиннее Автор вообще использует 300 строк как порог.Более 300 строк указывают на необходимость дальнейшего разделения этого компонента.
2️⃣ Разбить на составляющие
Если рендеринг компонента был разделен на несколько подрендеров в соответствии с описанным выше методом 👆, когда компонент становится раздутым, эти методы подрендеринга можно легко разделить на компоненты.Как правило, существует несколько способов извлечения компонентов:
- Разделение чистого рендеринга: методы субрендеринга, как правило, представляют собой чистый рендеринг, их можно напрямую извлечь каккомпоненты без сохранения состояния
public render() {
const { visible } = this.state
return (
<Modal
visible={visible}
title={this.getLocale('title')}
width={this.width}
maskClosable={false}
onOk={this.handleOk}
onCancel={this.handleCancel}
footer={<Footer {...}></Footer>}
>
<Body {...}></Body>
</Modal>
)
}
- Чисто логическое разделение: по
逻辑和视图分离
Принцип извлечения части логического управления в хуки или компоненты более высокого порядка - Разделение логики и рендеринга: извлеките связанные представления и логику, чтобы сформировать независимый компонент, который представляет собой более тщательное разделение и реализует принцип единой ответственности.
7. Пример разделения компонентов
Обычно мы анализируем и разделяем компоненты из схемы прототипа пользовательского интерфейса в официальном React.Thinking in reactТакже упоминается, что иерархия компонентов разделена по пользовательскому интерфейсу:Это связано с тем, что пользовательский интерфейс и модели данных, как правило, следуют одной и той же информационной архитектуре, а это означает, что работа по разделению пользовательского интерфейса на компоненты часто проста. Просто разделите его на компоненты, которые точно представляют часть вашей модели данных.". В дополнение к следованию некоторым принципам, упомянутым выше 👆, разделение компонентов также зависит от вашего опыта разработки.
В этом разделе описывается процесс разделения компонентов через простое приложение, это система служебной отчетности государственного ведомства, состоящая из четырех страниц:
1️⃣ Разделить страницы
Страница обычно является компонентной единицей верхнего уровня, разделить страницу очень просто, мы можем разделить ее на четыре страницы в соответствии со схемой-прототипом:ListPage
, CreatePage
, PreviewPage
, DetailPage
src/
containers/
ListPage/
CreatePage/
PreviewPage/
DetailPage/
index.tsx # 根组件, 一般在这里定义路由
2️⃣ Разделите основные компоненты пользовательского интерфейса
Первый взглядListPage
ListPage можно разделить на следующие компоненты в соответствии с пользовательским интерфейсом:
ScrollView # 滚动视图, 提供下拉刷新, 无限加载等功能
List # 列表容器, 布局组件
Item # 列表项, 布局组件, 提供header, body等占位符
props - header
Title # 渲染标题
props - after
Time # 渲染时间
props - body
Status # 渲染列表项的状态
посмотри сноваCreatePage
Это страница заполнения формы. Чтобы улучшить опыт заполнения формы, он разделен на несколько шагов; на каждом этапе есть несколько групп форм; структура каждой формы аналогична, с дисплеем метки слева и фактическим Формируйте компоненты справа, поэтому компоненты могут быть разделены так, по словам UI:
CreatePage
Steps # 步骤容器, 提供了步骤布局和步骤切换等功能
Step # 单一步骤容器
List # 表单分组
List.Item # 表单容器, 支持设置label
Input # 具体表单类型
Address
NumberInput
Select
FileUpload
Рекомендации по именованию компонентов: Для сборок типа коллекции, обычно обозначаемых единым комплексом, например, указанным выше Шаги/Шаг;Список/Элемент Эта форма также более распространена, например, Форма/Форма.Элемент, эта форма подходит в качестве подкаталога. -сборка в виде Вы можете узнать о том, как называются сторонние компоненты в библиотеке компонентов.
посмотри сноваPreviewPage
, PreviewPage — созданная страница предварительного просмотра данных, структура данных и структура страницы аналогичны CreatePage, Steps соответствует компоненту Preview, Step соответствует Preview.Item, Input соответствует Input.Preview:
3️⃣ Статус компонентов дизайна
Для ListPage статус относительно прост, здесь мы в основном обсуждаем статус CreatePage.Особенности CreatePage:
- Компонент формы использует контролируемый режим и не хранит состояние самой формы, кроме того, состояние между формами может быть связано
- Состояние должно быть общим между CreatePage и PreviewPage.
- Требуется проверка формы
- черновик сохранить
Из-за необходимости совместного использования данных в CreatePage и PreviewPage состояние формы должно быть извлечено и передано в родительский.В фактической разработке этого проекта мой подход заключается в создании компонента Context FormStore, а подчиненные компоненты используют это контекст для хранения данных единообразно. Кроме того, я решил использовать метод конфигурации для динамического отображения этих форм. Общая структура выглядит следующим образом:
// CreatePage/index.tsx
<FormStore defaultValue={draft} onChange={saveDraft}>
<Switch>
<Route path="/create/preview" component={Preview} />
<Route path="/create" component={Create} />
</Switch>
</FormStore>
// CreatePage/Create.tsx
<Steps>
{steps.map(i =>
<Step key={i.name}>
<FormRenderer forms={i.forms} /> {/* forms为表单配置, 根据配置的表单类型渲染表单组件, 从FormStore的获取和存储值 */}
</Step>
)}
</Steps>
8. Документация
Рекомендуется документация компонентовStorybook, который является компонентомPlayground
, имеет следующие характеристики
- Пример интерактивного компонента
- Документация, которую можно использовать для отображения компонентов.Поддерживает генерацию свойств и уценку.
- Может использоваться для тестирования компонентов Поддерживает структурное тестирование компонентов, тестирование взаимодействия, визуальное тестирование, тестирование доступности или ручное тестирование
- Богатая экология плагинов
Реагировать примерИз-за недостатка места Storybook не будет раскрывать детали, и заинтересованные читатели могут обратиться к официальной документации.