Недавно друг нашей команды Койке поделился "Выпущен BetterScroll 2.0: продолжайте совершенствоваться, идите с вамиЭта статья попала во внутреннюю группу команды и увиделаплагинБрат Абао внезапно заинтересовался архитектурным проектом, потому что раньше брат Абао также делился соответствующей информацией с командой. Теперь, когда вы заинтересовались, я решил начать изучение исходного кода BetterScroll 2.0.
В этой статье основное внимание будет уделено следующемуплагинАрхитектура BetterScroll 2.0 расширена, но прежде чем анализировать архитектуру плагинов BetterScroll 2.0, давайте кратко рассмотрим ее.BetterScroll.
1. Введение в BetterScroll
BetterScrollЭто плагин, ориентированный на удовлетворение потребностей различных сценариев прокрутки на мобильном терминале (ПК уже поддерживается). По своей сути он заимствован изiscrollЕго дизайн API в основном совместим с iscroll, и на основе iscroll некоторые функции были расширены, а производительность оптимизирована.
Выпущен BetterScroll 1.030Несколько версий, ежемесячные загрузки npm510 000, общее количество звезд12600+. Так зачем обновляться до 2.0?
Первоначальное намерение сделать версию v2 связано с потребностью сообщества:
- Может ли BetterScroll поддерживать загрузку по требованию?
Источник:Выпущен BetterScroll 2.0: продолжайте совершенствоваться, идите с вами
Для поддержки загрузки плагинов по запросу BetterScroll 2.0 используетплагинархитектурный дизайн. Являясь самой маленькой единицей прокрутки, CoreScroll предоставляет богатыйсобытиеа такжекрюк, остальные функции расширены различными плагинами, что сделает BetterScroll более гибким в использовании и адаптации к различным сценариям.
Ниже представлена общая схема архитектуры BetterScroll 2.0:
(Источник изображения:Наггетс.Талант/пост/686808…
Проект организован в монорепозиториях с использованиемlernaДля управления несколькими пакетами каждый компонент является независимым пакетом npm:
Как и проигрыватель арбузов, BetterScroll 2.0 также используетплагинИдея дизайна CoreScroll заключается в наименьшем блоке прокрутки, а остальные функции расширяются за счет плагинов. Например, общие функции загрузки и обновления раскрывающегося списка в длинных списках, в BetterScroll 2.0 эти функции передаются черезpull-up
иpull-down
Эти два плагина реализованы.
Одним из преимуществ подключаемых модулей является то, что они могут поддерживать загрузку по запросу.Кроме того, разделение независимых функций на независимые подключаемые модули сделает ядро системы более стабильным и в определенной степени надежным. Хорошо, краткое введение в BetterScroll, давайте перейдем к теме и проанализируем некоторые вещи, которые стоит изучить в этом проекте.
2. Опыт разработки
2.1 Улучшенные умные советы
BetterScroll 2.0 разработан с использованием TypeScript.Чтобы разработчики могли получать более интеллектуальные подсказки при использовании BetterScroll, команда BetterScroll в полной мере использует функцию автоматического слияния интерфейсов TypeScript, чтобы разработчики могли иметь соответствующие параметры при использовании подключаемого модуля. Подсказки и bs (экземпляр BetterScroll) могут иметь соответствующие подсказки методов.
2.1.1 Советы по параметрам смарт-плагина
2.1.2 Советы по использованию экземпляра Smart BetterScroll
Далее, чтобы лучше понять идею дизайна BetterScroll, давайте кратко представим архитектуру плагина.
3. Введение в архитектуру подключаемых модулей
3.1 Концепция подключаемой архитектуры
Архитектура подключаемых модулей — это функционально-ориентированная и расширяемая архитектура, которая обычно используется для реализации приложений, основанных на продуктах. Шаблон архитектуры подключаемых модулей позволяет добавлять дополнительные функции приложения в виде подключаемых модулей к основному приложению, обеспечивая расширяемость, функциональное разделение и изоляцию.
Подключаемый архитектурный шаблон включает в себя два типа архитектурных компонентов:Базовая система и подключаемые модули. Логика приложения разделена на независимые подключаемые модули и базовые системы, что обеспечивает масштабируемость, гибкость, функциональную изоляцию и настраиваемые функции логики обработки.
Функции базовой системы на рисунке относительно стабильны и не будут постоянно модифицироваться из-за расширения бизнес-функций, в то время как подключаемые модули могут постоянно корректироваться или расширяться в соответствии с потребностями реальных бизнес-функций.Суть архитектуры подключаемых модулей заключается в том, чтобы инкапсулировать части, которые, возможно, необходимо будет постоянно изменять в подключаемых модулях, чтобы достичь цели быстрого и гибкого расширения, не влияя на стабильность всей системы.
Базовая система подключаемой архитектуры обычно обеспечивает минимальный набор функций, необходимых для работы системы. Подключаемые модули — это автономные модули, которые содержат определенную обработку, дополнительные функции и пользовательский код для улучшения или расширения дополнительных бизнес-возможностей базовой системы.Обычно подключаемые модули являются независимыми, а некоторые подключаемые модули зависят от нескольких других подключаемых модулей. Важно свести к минимуму взаимодействие между плагинами, чтобы избежать проблем с зависимостями.
3.2 Преимущества подключаемой архитектуры
- Высокая гибкость. Общая гибкость — это способность быстро реагировать на изменения в окружающей среде. Из-за низкой связи между плагинами изменения обычно изолированы и могут быть реализованы быстро. В целом, базовая система стабильна и быстра, обладает некоторой надежностью и требует незначительных модификаций.
- Тестируемость: Плагины можно тестировать независимо друг от друга и легко создавать макеты для демонстрации или создания прототипов новых функций без изменения базовой системы.
- Высокая производительность. Хотя сама по себе архитектура подключаемых модулей не обеспечивает высокой производительности приложения, производительность приложения, построенного с использованием архитектуры подключаемых модулей, обычно высока, поскольку ненужные функции можно настраивать или урезать.
После введения базовых знаний, связанных с архитектурой подключаемых модулей, давайте проанализируем, как BetterScroll 2.0 проектирует архитектуру подключаемых модулей.
В-четвертых, реализация архитектуры подключаемого модуля BetterScroll.
Для проектирования базовой системы подключаемых модулей необходимо учитывать три ключевых момента: управление подключаемыми модулями, подключение подключаемых модулей и связь между подключаемыми модулями. Ниже мы сосредоточимся на этих трех ключевых моментах, чтобы постепенно проанализировать, как BetterScroll 2.0 реализует архитектуру плагинов.
4.1 Управление плагинами
Для унифицированного управления встроенными подключаемыми модулями разработчикам также удобно разрабатывать собственные подключаемые модули, отвечающие спецификациям в соответствии с потребностями бизнеса. BetterScroll 2.0 предусматривает унифицированную спецификацию разработки подключаемых модулей.Плагин для BetterScroll 2.0 должен быть классом со следующими свойствами:
1. Статическое свойство pluginName;
2. Реализовать интерфейс PluginAPI (в том и только в том случае, если метод подключаемого модуля необходимо делегировать в bs);
3. Первый параметр конструктора — экземпляр BetterScrollbs
, вы можете передать bsсобытиеиликрюкввести собственную логику.
Чтобы интуитивно понять приведенные выше спецификации разработки, мы возьмем в качестве примера встроенный плагин PullUp, чтобы увидеть, как он реализует приведенные выше спецификации. Плагин PullUp расширяет возможности загрузки подтягиваний для BetterScroll.
Как следует из названия, статическийpluginName
Атрибут представляет имя подключаемого модуля, а интерфейс PluginAPI представляет интерфейс API, предоставляемый экземпляром подключаемого модуля. Через интерфейс PluginAPI можно узнать, что он поддерживает 4 метода:
- FinishPullUp(): void: завершить поведение загрузки при подтягивании;
- openPullUp(config?: PullUpLoadOptions): void: Динамическое включение функции подтягивания;
- closePullUp(): void: закрыть функцию загрузки подтягивания;
- autoPullUpLoad(): void: Автоматически выполнять подтягивающую загрузку.
Плагин внедряет экземпляр BetterScroll через конструкторbs
, то мы можем передать bsсобытиеиликрюкввести собственную логику. Так зачем внедрять экземпляр bs? Как использовать экземпляр bs? Здесь мы сначала вспомним эти вопросы, а потом разберем их.
4.2 Штекерное соединение
Базовая система должна знать, какие плагины доступны в данный момент, как их загружать и когда их загружать. Распространенной реализацией является механизм реестра плагинов. Базовая система предоставляет реестр подключаемых модулей (который может быть файлом конфигурации, кодом или базой данных).Реестр подключаемых модулей содержит информацию о каждом подключаемом модуле, включая его имя, местоположение и время загрузки (загрузка при запуске). , или загружается по запросу) и т. д.
Здесь мы возьмем вышеупомянутый плагин PullUp в качестве примера, чтобы увидеть, как зарегистрироваться и использовать плагин. Сначала вам нужно установить плагин PullUp с помощью следующей команды:
$ npm install @better-scroll/pull-up --save
После успешной установки плагина pullup вам необходимо пройтиBScroll.use
способ регистрации плагина:
import BScroll from '@better-scroll/core'
import Pullup from '@better-scroll/pull-up'
BScroll.use(Pullup)
Затем при создании экземпляра BetterScroll вам необходимо передать элементы конфигурации плагина PullUp.
new BScroll('.bs-wrapper', {
pullUpLoad: true
})
Теперь мы знаем, что поBScroll.use
Метод может регистрировать плагины, так какая обработка выполняется внутри метода? Чтобы ответить на этот вопрос, давайте взглянем на соответствующий исходный код:
// better-scroll/packages/core/src/BScroll.ts
export const BScroll = (createBScroll as unknown) as BScrollFactory
createBScroll.use = BScrollConstructor.use
существуетBScroll.ts
файл,BScroll.use
метод указывает наBScrollConstructor.use
Статический метод, реализация этого метода выглядит следующим образом:
export class BScrollConstructor<O = {}> extends EventEmitter {
static plugins: PluginItem[] = []
static pluginsMap: PluginsMap = {}
static use(ctor: PluginCtor) {
const name = ctor.pluginName
const installed = BScrollConstructor.plugins.some(
(plugin) => ctor === plugin.ctor
)
// 省略部分代码
if (installed) return BScrollConstructor
BScrollConstructor.pluginsMap[name] = true
BScrollConstructor.plugins.push({
name,
applyOrder: ctor.applyOrder,
ctor,
})
return BScrollConstructor
}
}
Наблюдая за приведенным выше кодом, мы можем видеть, чтоuse
Метод принимает один параметр, тип параметраPluginCtor
, используемый для описания характеристик конструктора плагинов.PluginCtor
Конкретное объявление типа выглядит так:
interface PluginCtor {
pluginName: string
applyOrder?: ApplyOrder
new (scroll: BScroll): any
}
когда мы звонимBScroll.use(Pullup)
методом сначала будет получено имя текущего подключаемого модуля, а затем будет определено, был ли установлен текущий подключаемый модуль. Если он был установлен, он напрямую вернет объект BScrollConstructor, в противном случае плагин будет зарегистрирован. То есть сохранить информацию о текущем плагине в объекты pluginsMap({}) и plugins([]) соответственно:
В дополнение к звонкуuse
После статического метода он вернетBScrollConstructor
объект, это для поддержки цепочки:
BScroll.use(MouseWheel)
.use(ObserveDom)
.use(PullDownRefresh)
.use(PullUpLoad)
Теперь мы знаемBScroll.use
Как зарегистрировать плагин внутри метода, регистрация плагина — это только первый шаг, чтобы использовать зарегистрированный плагин, нам также нужно передать элементы конфигурации плагина при создании экземпляра BetterScroll, чтобы инициализировать плагин. Для подключаемого модуля PullUp мы инициализируем подключаемый модуль следующими способами.
new BScroll('.bs-wrapper', {
pullUpLoad: true
})
Итак, чтобы понять, как подключаемый модуль подключается к базовой системе и инициализирует подключаемый модуль, нам необходимо проанализировать его.BScroll
Конструктор:
// packages/core/src/BScroll.ts
export const BScroll = (createBScroll as unknown) as BScrollFactory
export function createBScroll<O = {}>(
el: ElementParam,
options?: Options & O
): BScrollConstructor & UnionToIntersection<ExtractAPI<O>> {
const bs = new BScrollConstructor(el, options)
return (bs as unknown) as BScrollConstructor &
UnionToIntersection<ExtractAPI<O>>
}
существуетcreateBScroll
Фабричный метод пройдетnew
вызов ключевого словаBScrollConstructor
конструктор для создания экземпляра BetterScroll. Поэтому следующим направлением является анализBScrollConstructor
Конструктор:
// packages/core/src/BScroll.ts
export class BScrollConstructor<O = {}> extends EventEmitter {
constructor(el: ElementParam, options?: Options & O) {
const wrapper = getElement(el)
// 省略部分代码
this.plugins = {}
this.hooks = new EventEmitter([...])
this.init(wrapper)
}
private init(wrapper: MountedBScrollHTMLElement) {
this.wrapper = wrapper
// 省略部分代码
this.applyPlugins()
}
}
Читая исходный код BScrollConstructor, мы обнаружили, что внутри конструктора BScrollConstructor будет вызыватьсяinit
метод для инициализации, в то время как вinit
Далее метод вызоветapplyPlugins
способ применения зарегистрированного плагина:
// packages/core/src/BScroll.ts
export class BScrollConstructor<O = {}> extends EventEmitter {
private applyPlugins() {
const options = this.options
BScrollConstructor.plugins
.sort((a, b) => {
const applyOrderMap = {
[ApplyOrder.Pre]: -1,
[ApplyOrder.Post]: 1,
}
const aOrder = a.applyOrder ? applyOrderMap[a.applyOrder] : 0
const bOrder = b.applyOrder ? applyOrderMap[b.applyOrder] : 0
return aOrder - bOrder
})
.forEach((item: PluginItem) => {
const ctor = item.ctor
// 当启用指定插件的时候且插件构造函数的类型是函数的话,再创建对应的插件
if (options[item.name] && typeof ctor === 'function') {
this.plugins[item.name] = new ctor(this)
}
})
}
}
существуетapplyPlugins
Метод будет отсортирован в соответствии с порядком, установленным плагином, а затем использованbs
Экземпляр вызывается как параметр конструктора плагина для создания плагина и сохранения экземпляра плагина вbs
Внутри свойства plugins({}) экземпляра.
Пока мы представили управление плагинами и подключение плагинов, давайте представим последний ключевой момент — связь плагинов.
4.3 Связь с плагином
Подключаемая связь относится к связи между подключаемыми модулями. Несмотря на то, что при проектировании подключаемые модули полностью отделены друг от друга, в реальном бизнес-процессе неизбежно будет бизнес-процесс, требующий взаимодействия нескольких подключаемых модулей, что требует взаимодействия между двумя подключаемыми модулями;Поскольку между плагинами нет прямой связи, связь должна проходить через базовую систему, поэтому базовая система должна обеспечивать механизм связи плагинов..
Эта ситуация аналогична ситуации с компьютером.ЦП компьютера, жесткий диск, память и сетевая карта представляют собой независимо разработанные конфигурации.Однако во время работы компьютера должна быть связь между ЦП и памятью, памятью и жестким диском. , Компьютер обеспечивает их через шину на материнской плате Функции связи между компонентами.
Аналогичным образом, для систем с подключаемой архитектурой базовая система обычно предоставляет подключаемый механизм связи в виде шины событий. Когда дело доходит до автобуса мероприятия, некоторые друзья могут быть незнакомы. Но если он используетсямодель публикации-подпискиЕсли это так, это должно быть легко понять. Здесь брат Абао не собирается вводить модель публикации-подписки, а только использует изображение для обзора модели.
Для BetterScroll его ядром являетсяBScrollConstructor
класс, который наследуетEventEmitter
Диспетчер событий:
// packages/core/src/BScroll.ts
export class BScrollConstructor<O = {}> extends EventEmitter {
constructor(el: ElementParam, options?: Options & O) {
this.hooks = new EventEmitter([
'refresh',
'enable',
'disable',
'destroy',
'beforeInitialScrollTo',
'contentChanged',
])
this.init(wrapper)
}
}
Класс EventEmitter предоставляется внутри BetterScroll, а его экземпляр будет обеспечивать функцию шины событий извне, а диаграмма класса UML, соответствующая этому классу, выглядит следующим образом:
На данный момент мы можем ответить на первый вопрос, оставленный ранее: «Так зачем внедрять экземпляры bs?». Поскольку экземпляр bs (BScrollConstructor) также по сути является диспетчером событий, при создании подключаемого модуля экземпляр bs внедряется, чтобы подключаемые модули могли обмениваться данными через единый диспетчер событий.
Мы уже знаем ответ на первый вопрос, поэтому давайте рассмотрим второй вопрос: «Как использовать инстанс bs?». Чтобы ответить на этот вопрос, мы продолжим использовать подключаемый модуль PullUp в качестве примера, чтобы увидеть, как подключаемый модуль использует экземпляр bs для внутреннего обмена сообщениями.
export default class PullUp implements PluginAPI {
static pluginName = 'pullUpLoad'
constructor(public scroll: BScroll) {
this.init()
}
}
В конструкторе PullUp экземпляр bs будет сохранен во внутреннем экземпляре PullUp.scroll
properties, а затем в подключаемом модуле PullUp передача событий может выполняться через внедренный экземпляр bs. Например, внутренние события плагина диспетчеризации, в плагине PullUp, когда расстояние прокрутки до низа меньшеthreshold
значение, триггер один разpullingUp
событие:
private checkPullUp(pos: { x: number; y: number }) {
const { threshold } = this.options
if (...) {
this.pulling = true
// 省略部分代码
this.scroll.trigger(PULL_UP_HOOKS_NAME) // 'pullingUp'
}
}
Узнав, как использовать экземпляр bs для отправки событий, давайте посмотрим, как использовать его внутри подключаемого модуля для прослушивания событий, представляющих интерес для подключаемого модуля.
// packages/pull-up/src/index.ts
export default class PullUp implements PluginAPI {
static pluginName = 'pullUpLoad'
constructor(public scroll: BScroll) {
this.init()
}
private init() {
this.handleBScroll()
this.handleOptions(this.scroll.options.pullUpLoad)
this.handleHooks()
this.watch()
}
}
будет вызываться в конструкторе PullUpinit
метод инициализации плагина, в то время как вinit
Внутри метода будут вызываться разные методы для выполнения разных операций инициализации.handleHooks
метод, который реализуется следующим образом:
private handleHooks() {
this.hooksFn = []
// 省略部分代码
this.registerHooks(
this.scroll.hooks,
this.scroll.hooks.eventTypes.contentChanged,
() => {
this.finishPullUp()
}
)
}
очевидно вhandleHooks
Внутри метода будут сделаны дальнейшие вызовыregisterHooks
способ зарегистрировать хук:
private registerHooks(hooks: EventEmitter, name: string, handler: Function) {
hooks.on(name, handler, this)
this.hooksFn.push([hooks, name, handler])
}
НаблюдаяregisterHooks
Сигнатура метода показывает, что он поддерживает 3 параметра, первый параметрEventEmitter
объект, а два других параметра представляют имя события и обработчик события соответственно. существуетregisterHooks
метод, он просто проходитhooks
объект для прослушивания указанного события.
Такthis.scroll.hooks
Когда создается объект? существуетBScrollConstructor
В конструкторе мы нашли ответ.
// packages/core/src/BScroll.ts
export class BScrollConstructor<O = {}> extends EventEmitter {
constructor(el: ElementParam, options?: Options & O) {
// 省略部分代码
this.hooks = new EventEmitter([
'refresh',
'enable',
'disable',
'destroy',
'beforeInitialScrollTo',
'contentChanged',
])
}
}
Это понятноthis.hooks
такжеEventEmitter
объект, поэтому вы можете использовать его для обработки событий. Хорошо, сначала здесь будет представлено содержание плагина связи.Подытожим содержание этой части картинкой:
Познакомившись с реализацией архитектуры плагинов BetterScroll, давайте кратко поговорим об инженерных аспектах проекта BetterScroll.
5. Инженерные аспекты
С точки зрения инженерии, BetterScroll использует некоторые распространенные в отрасли решения:
- lerna: Lerna — это инструмент управления проектами JavaScript, содержащими несколько пакетов.
- prettier: Prettier в переводе с китайского означает красивый, красивый и является популярным инструментом форматирования кода.
- tslint: TSLint — это расширяемый инструмент статического анализа для проверки кода TypeScript на удобочитаемость, ремонтопригодность и функциональные ошибки.
- commitizen & cz-conventional-changelog: используется, чтобы помочь нам сгенерировать сообщение фиксации, соответствующее спецификации.
- husky: husky может предотвратить фиксацию, отправку, слияние и т. д. нестандартного кода.
- jest: Jest — это среда тестирования JavaScript, поддерживаемая Facebook.
- coveralls: для получения отчетов о покрытии для Coveralls.io и добавляет удобную кнопку покрытия в файл README.
- vuepress: Генератор статических сайтов на базе Vue, который создает документацию для BetterScroll 2.0.
Поскольку эта статья не посвящена разработке, в приведенном выше A Baoge просто перечислены библиотеки с открытым исходным кодом, которые BetterScroll использует в разработке. Если вас также интересует проект BetterScroll, вы можете ознакомиться с проектомpackage.json
файл и сосредоточиться на проектеnpm scriptsКонфигурация. Конечно, проекту BetterScroll еще есть чему поучиться, остальное откроет для себя каждый, а заинтересованные друзья приглашаются к общению и обсуждению с братом Абао.