Перейти к содержимому

Обзор общего ядра

@lh-ui/share/core/shared — общий алгоритмический слой всей семьи lh-ui: 12 не имеющих зависимостей TypeScript-модулей, которые сводят в единую реализацию логику, нужную каждому компоненту, но которую каждый порт легко пишет заново (и обязательно с расхождениями в деталях):

позиционирование плавающих слоёв, виртуальная прокрутка, клавиатурная навигация, ловушка фокуса, построение aria-атрибутов, примитивы дат, санитизация чисел, проброс стилей… Эти алгоритмы не принадлежат ни одному фреймворку: на входе обычные данные, на выходе обычные данные, никакого реактивного состояния и никакого глобального состояния DOM (несколько функций читают DOM, но за SSR-предохранителями).

Один и тот же компонент в lh-ui-plus, lh-ui-vue2, lh-ui-react и lh-ui-uniapp использует одни и те же computePosition и getNextListIndex. Исправили один баг — исправлено сразу на четырёх концах; добавили граничный случай — он сразу появился на всех четырёх.

Модуль Ответственность Ключевые экспорты
sx props sx → инлайн-стили с фиксированным приоритетом «одна сторона > пара осей > сокращение» sxToStyle / mergeSx / LhSxProps
popper Движок позиционирования плавающих слоёв: 12 размещений, переворот по главной оси, кламп по поперечной оси, смещение стрелки computePosition / parsePlacement / LhPlacement
virtual Диапазон виртуальной прокрутки фиксированной высоты (virtual-list и table разделяют один алгоритм) computeRange / computeWindow / LH_VIRTUAL_OVERSCAN
keyboard Клавиатурное взаимодействие: определение клавиш, навигация по списку / сетке / range, type-ahead getNextListIndex / getNextGridCell / LH_KEYS
focus-trap Управление фокусом: перечисление фокусируемых элементов, ловушка фокуса Tab, реестр владельцев inert trapTabKey / setInertOutside / focusFirst
a11y Атомарные построители aria-атрибутов (единая семантика «пропускать при ложном значении») getPopupTriggerAria / ariaExpanded / getDecorativeAria
date Примитивы дат: количество дней в месяце, сравнение, определение заблокированных, форматирование токенами фиксированной ширины formatDate / parseDate / isDateDisabled
format Интерполяция шаблонов текстов (плейсхолдеры {key}) formatMessage
delay Нормализация задержки показа/скрытия (общая для tooltip / popover / popconfirm) resolveDelay
pt Слияние пробрасываемых props PT (поверхностное слияние class / style / attrs) mergePt / LhPtAttrs
sanitize Санитизация чисел: NaN / Infinity перехватываются в одной точке входа sanitizeNumber / clamp / safePrecision
types Общие union-типы: размер / язык / тип / позиция LhComponentSize / LhComponentType

Сводный вход @lh-ui/share/core/shared реэкспортирует все 12 модулей через export *; можно также импортировать модуль точечно — @lh-ui/share/core/shared/popper, — чтобы сборщик унёс только то, что используется.

Функции слоя core не хранят состояние компонентов. Возьмём popper: computePosition(trigger, popper, viewport, options) принимает три обычных rect и возвращает { top, left, placement, arrowOffset } — «где сейчас триггер, насколько велик плавающий слой, насколько велик вьюпорт» — всё это UI-слой измеряет в момент вызова и передаёт. Это означает:

  • Одна и та же функция может работать и внутри watchEffect во Vue3, и внутри useLayoutEffect в React, и внутри onPageScroll в uniapp.
  • Для unit-тестов не нужно рендерить компоненты: множество случаев в tests/unit/ — это «создать набор rect, проверить вывод», и все четыре порта разделяют одни и те же результаты тестов (красная линия 3: unit-тесты core существуют только в этом репозитории и в единственном экземпляре).

SSR-предохранители как контракт по умолчанию

Заголовок раздела «SSR-предохранители как контракт по умолчанию»

Несколько функций обязаны читать DOM (getWindowViewport, getFocusableElements, saveActiveElement). Все они в точке входа проверяют typeof document === 'undefined' / typeof window === 'undefined' и возвращают безопасное пустое значение ([] / null / rect нулевого размера), чтобы серверный рендеринг в Nuxt / Next / uniapp не падал. В этой документации каждая функция помечена как «SSR-безопасная» или с указанием конкретного поведения предохранителя.

aria-соглашение «пропускать при ложном значении»

Заголовок раздела «aria-соглашение «пропускать при ложном значении»»

Все построители модуля a11y следуют одному соглашению: булевы aria-атрибуты при ложном значении возвращают undefined, чтобы фреймворк не отрендерил атрибут (aria-required: false как явное отрицание в большинстве случаев — шум). Исключения — атрибуты, семантически требующие явного объявления: aria-expanded: false и aria-invalid: false выводятся как есть. Подробнее см. документацию a11y.

Санитизация на входе, без защитного кода downstream

Заголовок раздела «Санитизация на входе, без защитного кода downstream»

Существование sanitize позволяет остальным функциям core не писать проверку Number.isFinite повсюду. Числа, приходящие извне — номер страницы, интервал, шаг, точность, — перед попаданием в алгоритм проходят через sanitizeNumber, а NaN / Infinity заменяются безопасным значением по умолчанию. Это компромисс «перехватили в одном месте — чисто везде»: вызывающая сторона либо сама гарантирует конечность и использует clamp напрямую, либо явно доверяет санитизации на входе.

Их всего три:

  • keyboard ← sanitize.clamp (кламп навигационных границ)
  • popper ← sanitize.clamp + sanitize.sanitizeNumber (кламп и санитизация параметров)
  • shared/types ← constants.LH_SIZE_CONST / LH_LANGUAGE_CONST / LH_TYPES_CONST (import type; union-типы выводятся из констант)

Кольцевых зависимостей нет, обратных зависимостей от locales или UI-слоя тоже. Существование модуля format — прямой продукт этой линии: интерполяции вроде '{n} 星' в rate или '共 {total} 条' в pagination раньше требовали обращения к синглтону locales, а после очищения до чистой строковой функции core-компоненты больше не зависят от модульных побочных эффектов locales.

@lh-ui/share/core/sanitize → clamp( )
返回值
(填好参数后点运行)

Внизу страницы документации каждого подмодуля есть такой блок-песочница: заполните параметры, запустите прямо в браузере и посмотрите результат.

Copyright © 2024-2026 FengMo WenXue