Эта статья участвовала в "Проект «Звезда раскопок»”, чтобы выиграть творческий подарочный пакет и бросить вызов творческим поощрительным деньгам.
Примечание. Чтобы сохранить пространство как можно более кратким, когда я вставляю исходный код в некоторых местах, я пытаюсь вставить исходный код, который лучше всего отражает содержание, которое необходимо объяснить, а другие повторяющиеся коды опущены, поэтому, если вы попытаетесь чтобы прочитать исходный код самостоятельно, вы можете найти несоответствия с кодом в статье. Репозиторий исходного кода, используемый в статье для запуска через Naive UI:GitHub.com/PF Том/наивный…
лаконичная абстракция
Front-end разработчики теперь почти неотделимы от библиотек компонентов пользовательского интерфейса, таких как Ant Design, Material Design и Naive UI, который недавно появился в экосистеме Vue.Библиотека компонентов предоставляет простую, гибкую и удобную форму. использования, например страницы. Наиболее распространенные способы использования кнопок:
<template>
<n-button>Default</n-button>
<n-button type="primary">Default</n-button>
<n-button type="info" dashed>Default</n-button>
<n-button type="success" dashed size="small">Default</n-button>
<n-button text>Default</n-button>
<n-button text tag="a" href="https://anyway.fm/news.php" type="warning"
>安妮薇时报</n-button
>
<n-button disabled> 不许点 </n-button>
</template>
Приведенные выше несколько простых строк кода могут привести к следующим интересным эффектам:
Даже вы можете переключать скины одним щелчком мыши, например темный режим:
Конечно, мы также можем обрабатывать события, добавлять значки, обрабатывать загрузку и т. д. Просто задав некоторые реквизиты, мы можем получить красивую и практичную кнопку, которая действительно отличается от исходных тегов HTML...
теория айсберга
Несмотря на то, что библиотека компонентов обеспечивает гибкость и удобство, ее внутренние принципы не так просты, как ее использование, и столь же наводят на размышления, как приведенная выше диаграмма айсберга.
Давайте взглянем на CHANGELOG Naive UI, недавнего новичка в библиотеке компонентов Vue, чтобы получить представление о том, сколько времени уходит на написание вводной библиотеки компонентов:
Как видите, он выйдет 21 марта 2020 г.1.x
версии, в то время как в1.x
Это был долгий период обдумывания, проектирования и разработки, а должен был длиться более двух лет.
Чтобы запустить наивную кнопку пользовательского интерфейса, требуются примерно следующие файлы или коды:
.
|_____utils
| |____color
| | |____index.js
| |____vue
| | |____index.js
| | |____flatten.js
| | |____call.js
| | |____get-slot.js
| |____index.js
| |____naive
| | |____warn.js
| | |____index.js
| |____cssr
| | |____create-key.js
| | |____index.js
|_____internal
| |____loading
| | |____index.js
| | |____src
| | | |____Loading.jsx
| | | |____styles
| | | | |____index.cssr.js
| |____index.js
| |____icon-switch-transition
| | |____index.js
| | |____src
| | | |____IconSwitchTransition.jsx
| |____fade-in-expand-transition
| | |____index.js
| | |____src
| | | |____FadeInExpandTransition.jsx
| |____wave
| | |____index.js
| | |____src
| | | |____Wave.jsx
| | | |____styles
| | | | |____index.cssr.js
| |____icon
| | |____index.js
| | |____src
| | | |____Icon.jsx
| | | |____styles
| | | | |____index.cssr.js
|_____styles
| |____common
| | |_____common.js
| | |____light.js
| | |____index.js
| |____transitions
| | |____fade-in-width-expand.cssr.js
| | |____icon-switch.cssr.js
| |____global
| | |____index.cssr.js
|____config-provider
| |____src
| | |____ConfigProvider.js
|____button
| |____styles
| | |_____common.js
| | |____light.js
| | |____index.js
| |____src
| | |____Button.jsx
| | |____styles
| | | |____button.cssr.js
|____assets
| |____logo.png
|_____mixins
| |____use-style.js
| |____use-theme.js
| |____index.js
| |____use-form-item.js
| |____use-config.js
За, казалось бы, трудным
Несмотря на прохождение, казалось бы, простого<Button />
Требуется много работы за кулисами, включая десятки файловых зависимостей, но для библиотеки компонентов сложность представляет собой приближение на порядок, т.е. от простого<Button />
к комплексу<Form />
, вообще-то в области библиотек компонентов 90% контента похоже, так что если вы понимаете<Button />
В принципе можно сказать, что вы поняли почти 90% содержимого библиотеки компонентов, а оставшиеся 10% — это конкретная реализация конкретных компонентов.
Таким образом, чтобы понять ядро библиотеки интерфейсных компонентов, вам все равно нужно понимать<Button />
Разнообразная подготовительная работа, необходимая для прогона, который является первым высоким столбцом на приведенном выше рисунке, и разработки библиотеки компонентов должна быть сначала сосредоточена на проектировании, чтобы позволить по крайней мере одинButton
План бега.
Технологическая цепочка, стоящая за Button
мы начинаем сNaive UI Для объекта исследования детально проанализировать его<Button />
Есть две интуитивные причины для различных принципов реализации:
- В его технологическом стеке преобладают Vite, Vue3 и TypeScript, что соответствует последнему технологическому стеку автора.
- По сравнению с другими библиотеками компонентов, она находится на относительно скомпрометированном уровне с точки зрения зрелости, популярности и совершенства кода, не слишком сложна, но требует относительно большего объема знаний и больше подходит для изучения и исследования ее принципов.
из шаблона
Если вы хотите понять компонент, конечно, в первую очередь нужно понять его скелет, что мы часто говорим о HTML/JSX.Во-первых, давайте взглянем на шаблон компонента Button наивного пользовательского интерфейса:
const Button = defineComponent({
name: 'Button',
props: {},
setup(props) {},
render() {
// 第一部分
// n
const { $slots, mergedClsPrefix, tag: Component } = this;
const children = flatten(getSlot(this));
return (
<Component
ref="selfRef"
// 第二部分
class={[
`${mergedClsPrefix}-button`,
`${mergedClsPrefix}-button--${this.type}-type`,
{
[`${mergedClsPrefix}-button--disabled`]: this.disabled,
[`${mergedClsPrefix}-button--block`]: this.block,
[`${mergedClsPrefix}-button--pressed`]: this.enterPressed,
[`${mergedClsPrefix}-button--dashed`]: !this.text && this.dashed,
[`${mergedClsPrefix}-button--color`]: this.color,
[`${mergedClsPrefix}-button--ghost`]: this.ghost, // required for button group border collapse
},
]}
tabindex={this.mergedFocusable ? 0 : -1}
type={this.attrType}
style={this.cssVars}
disabled={this.disabled}
onClick={this.handleClick}
onBlur={this.handleBlur}
onMousedown={this.handleMouseDown}
onKeyup={this.handleKeyUp}
onKeydown={this.handleKeyDown}
>
// 第三部分
{$slots.default && this.iconPlacement === "right" ? (
<div class={`${mergedClsPrefix}-button__content`}>{children}</div>
) : null}
// 第四部分
<NFadeInExpandTransition width>
{{
default: () =>
$slots.icon || this.loading ? (
<span
class={`${mergedClsPrefix}-button__icon`}
style={{
margin: !$slots.default ? 0 : "",
}}
>
<NIconSwitchTransition>
{{
default: () =>
this.loading ? (
<NBaseLoading
clsPrefix={mergedClsPrefix}
key="loading"
class={`${mergedClsPrefix}-icon-slot`}
strokeWidth={20}
/>
) : (
<div
key="icon"
class={`${mergedClsPrefix}-icon-slot`}
role="none"
>
{renderSlot($slots, "icon")}
</div>
),
}}
</NIconSwitchTransition>
</span>
) : null,
}}
</NFadeInExpandTransition>
// 第三部分
{$slots.default && this.iconPlacement === "left" ? (
<span class={`${mergedClsPrefix}-button__content`}>{children}</span>
) : null}
// 第五部分
{!this.text ? (
<NBaseWave ref="waveRef" clsPrefix={mergedClsPrefix} />
) : null}
// 第六部分
{this.showBorder ? (
<div
aria-hidden
class={`${mergedClsPrefix}-button__border`}
style={this.customColorCssVars}
/>
) : null}
// 第六部分
{this.showBorder ? (
<div
aria-hidden
class={`${mergedClsPrefix}-button__state-border`}
style={this.customColorCssVars}
/>
) : null}
</Component>
)
}
});
Видно, что упомянутый выше основной дисплей<Button />
Шаблонная часть компонента на основе Vue3defineComponent
определять компоненты на основеrender
Метод использует форму JSX для написания шаблона, и часть шаблона в основном разделена на 6 частей, которые отмечены в коде комментариями:
- В основном связанные с атрибутами, есть три основных атрибута:
$slots
,mergedClsPrefix
,tag
,в$slots
В поле Vue объект, которому принадлежит дочерний узел,mergedClsPrefix
Это префикс пространства имен всей библиотеки компонентов. В простом пользовательском интерфейсе этот префиксn
,tag
Указывает, с какой меткой должен отображаться этот компонент, по умолчанию<button />
, вы также можете заменить его на<a />
, сделать кнопку похожей на ссылку
-
В основном определяют
Button
Связанные свойства:- в
class
Затем по входящим атрибутам определить, какой принадлежитtype
:primary
,info
,warning
,success
,error
И какое государство в настоящее время находится в:disabled
,block
,pressed
,dashed
,color
,ghost
, согласно этимtype
и укажите соответствующие имена классов для определения стилей CSS, которым принадлежат соответствующие имена классов для компонента. -
tabIndex
означает использованиеtab
ключ, будет ли выбрана эта кнопка,0
указывает, что его можно выбрать,-1
Указывает, что его нельзя выбрать; -
type
выражается какbutton
,submit
,reset
и другие типы кнопок, так что кнопки могут быть интегрированы в<Form />
компоненты для выполнения более сложных операций, таких как инициирование отправки форм; -
style
Это передача необходимых переменных CSS для этого компонента, то есть переменных CSS, и вsetup
функция, она пройдетuseTheme
(будет рассмотрено позже) крючок для крепленияButton
Связанные стили, которые широко используют переменные CSS для настройки различных свойств CSS компонентов, а также для управления глобальным переключением тем, например темным режимом и т. д. -
disabled
Он должен контролировать, работает ли эта кнопка,true
Делегат отключен, неработоспособен,false
Представляет работоспособный по умолчанию - Остальные являются соответствующими обработчиками событий:
click
,blur
,mouseup
,keyup
,keydown
Ждать
- в
- в основном решают
iconPlacement
дляleft
,right
, форма отображения дочерних узлов компонента, то есть, когда значок находится слева и справа, дочерние узлы распределяются как<span />
или<div />
Отображается в виде метки, когдаright
, установлен в<div />
Это для лучшей обработки макета и позиционирования
- Для контента, связанного со значками,
NFadeInExpandTransition
Чтобы управлять анимацией перехода появления и исчезновения значка,NIconSwitchTransition
контрольloading
Переключение анимации перехода для иконок формы и других иконок
- когда кнопка не начинается с
text
Когда узел отображается в виде узла, должна быть пульсация для обработки обратной связи по нему.В приведенном выше видео вы также можете видеть, что будет соответствующий эффект пульсации, когда вы нажимаете кнопку, чтобы дать обратную связь по щелчку.Нет Волновой эффект
- главным образом через
<div />
Чтобы смоделировать границу компонента:border
а такжеstate-border
, первое в основном статично, по умолчанию обрабатывается цвет границы, ширина и т. д., второе обрабатывается в разных состояниях:focus
,hover
,active
,pressed
ждатьborder
стиль
Вы можете увидеть оба из них в действии на практическом примере:
.n-button .n-button__border {
border: var(--border);
}
.n-button .n-button__border, .n-button .n-button__state-border {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
border-radius: inherit;
transition: border-color .3s var(--bezier);
pointer-events: none;
}
.n-button:not(.n-button--disabled):hover .n-button__state-border {
border: var(--border-hover);
}
.n-button:not(.n-button--disabled):pressed .n-button__state-border {
border: var(--border-pressed);
}
style attribute {
--bezier: ;
--bezier-ease-out: ;
--border: 1px ;
--border-hover: 1px ;
--border-pressed: 1px ;
--border-focus: 1px ;
--border-disabled: 1px ;
}
можно увидетьstate-border
В основном для работы с некоторыми эффектами, которые будут динамически изменяться, например, вhover
,pressed
Эффект отображения границы в других состояниях иborder
Он отвечает за начальный эффект по умолчанию.
Поняв содержание, относящееся к основному шаблону, вы можете выразить сомнения относительно содержания, которое чаще всего появляется при объяснении всего шаблона, а именно:
${mergedClsPrefix}-button
${mergedClsPrefix}-button--${``this``.type}-type
${mergedClsPrefix}-button__content
${mergedClsPrefix}-button--disabled
Почему существует такой странный способ написания классов CSS? и при присвоении свойств компонентам:
style``={``this``.cssVars}
Типичный пример:
const cssVars = {
// default type
color: "#0000",
colorHover: "#0000",
colorPressed: "#0000",
colorFocus: "#0000",
colorDisabled: "#0000",
textColor: textColor2,
}
Зачем вам нужно назначать кучу переменных CSS?
Если вы озадачены этими вопросами и хотите изучить их обоснование, то вам следует вздохнуть с облегчением, а затем сосредоточиться и продолжить чтение следующей части статьи: искусство организации стилей.
искусство организации
В области библиотек компонентов большая часть времени уходит на то, чтобы лучше и больше настроить организацию общей системы стилей.
Фреймворк Naive UI имеет интересную особенность: он не использует языки стилей предварительной и постобработки, такие как Less/Sass/PostCSS и т. д., а строит свой собственный CSS-подобный CSS с функциями SSR, которые встроены для фреймворка и к фреймворку отношения не имеет.Решение на JS:css-render, и разработал систему подключаемых модулей для этого решения. В настоящее время существует два основных подключаемых модуля:
Эта статья в основном посвящена объяснению КСО, поэтому я сосредоточусь только наplugin-bem
связанный контент.
Текущий базовый сценарий использования css-render — это коллокация.plugin-bem
Используйте плагины для написания легко организуемого CSS в стиле БЭМ в коде, подобном JS.Что касается того, почему это «классовое» решение CSS в JS, я объясню позже.
После устанавливаем соответствующий пакет:
$ npm install --save-dev css-render @css-render/plugin-bem
Его можно использовать следующим образом:
import CssRender from 'css-render'
import bem from '@css-render/plugin-bem'
const cssr = CssRender()
const plugin = bem({
blockPrefix: '.ggl-'
})
cssr.use(plugin) // 为 cssr 注册 bem 插件
const { cB, cE, cM } = plugin
const style = cB(
'container',
[
cE(
'left, right',
{
width: '50%'
}
),
cM(
'dark',
[
cE(
'left, right',
{
backgroundColor: 'black'
}
)
]
)
]
)
// 查看渲染的 CSS 样式字符串
console.log(style.render())
// 将样式挂载到 head 标签里面,可以提供 options
style.mount(/* options */)
// 删除挂载的样式
style.unmount(/* options */)
Эффект приведенного выше журнала заключается в следующем:
.ggl-container .ggl-container__left,
.ggl-container .ggl-container__right {
width: 50%;
}
.ggl-container.ggl-container--dark .ggl-container__left,
.ggl-container.ggl-container--dark .ggl-container__right{
background-color: black;
}
Вы можете видеть, что приведенный выше код в основном используетсяcB
,cE
,cM
Функция для выполнения вложенной комбинации различных меток и стилей для достижения эффекта определения стандартных классов CSS и соответствующих стилей.Для дальнейшего объяснения функции этой библиотеки и эффекта, которого она достигает в наивном пользовательском интерфейсе, нам нужно сначала понять, что это БЭМ.
Что такое БЭМ?
B (блок), E (элемент), M (модификатор), а именно блоки, элементы и модификаторы, являются широко используемым соглашением об именах для классов, используемых в HTML/CSS:
/* 块 */
.btn {}
/* 依赖块的元素 */
.btn__price {}
.btn__text {}
/* 修改块状态的修饰符 */
.btn--orange {}
.btn--big {}
- Вышеупомянутый средний блок (Блок), а именно
btn
, представляет собой абстрактный новый компонент верхнего уровня, то есть блок не может содержать блок, а также рассматривается как родительский узел в дереве, используя.btn
выражать - Элемент, то есть
price
,text
, что означает, что он принадлежит блоку, является дочерним элементом этого блока, следует за блоком и отделяется двойным подчеркиванием..btn__price
,.btn__text
выражать - Модификатор, т.е.
orange
,big
, используется для изменения состояния блока, добавления определенной темы или стиля в блок, следования за блоком, разделенных двойным дефисом, использования.btn--orange
,.btn--big
выражать
Приведенная выше форма CSS отражается в HTML, и получается следующая структура:
<a class="btn btn--big btn--orange" href="">
<span class="btn__price">¥9.99</span>
<span class="btn__text">订购</span>
</a>
В основном есть несколько преимуществ использования этого стиля именования классов в стиле БЭМ:
- Он может представлять почти все элементы и их подчиненные отношения, причем связь ясна и семантика понятна.
- И даже если разработчики в других областях, таких как разработка на стороне клиента или дизайнеры, не понимают язык CSS, они могут узнать об элементах, их иерархических отношениях и состояниях из этого стиля именования.
- После создания аналогичной структуры именования вам нужно всего лишь изменить несколько имен классов, чтобы получить элементы разных стилей, такие как кнопки:
/* Block */
.btn {
text-decoration: none;
background-color: white;
color: #888;
border-radius: 5px;
display: inline-block;
margin: 10px;
font-size: 18px;
text-transform: uppercase;
font-weight: 600;
padding: 10px 5px;
}
/* Element */
.btn__price {
background-color: white;
color: #fff;
padding-right: 12px;
padding-left: 12px;
margin-right: -10px; /* realign button text padding */
font-weight: 600;
background-color: #333;
opacity: .4;
border-radius: 5px 0 0 5px;
}
/* Element */
.btn__text {
padding: 0 10px;
border-radius: 0 5px 5px 0;
}
/* Modifier */
.btn--big {
font-size: 28px;
padding: 10px;
font-weight: 400;
}
/* Modifier */
.btn--blue {
border-color: #0074D9;
color: white;
background-color: #0074D9;
}
/* Modifier */
.btn--orange {
border-color: #FF4136;
color: white;
background-color: #FF4136;
}
/* Modifier */
.btn--green {
border-color: #3D9970;
color: white;
background-color: #3D9970;
}
body {
font-family: "fira-sans-2", sans-serif;
background-color: #ccc;
}
Вышеупомянутое нужно только изменить модификаторorange
,green
,blue
,big
и так далее, можно получить разные эффекты:
Как работает CSS-рендеринг?
CSS Render — это, по сути, генератор CSS, который затем обеспечиваетmount
а такжеunmount
API для загрузки сгенерированной строки CSS в шаблон HTML и удаления тега стиля CSS из HTML.Реализует схему Sass/Less/CSS-in-JS с помощью плагина соглашения об именах БЭМ и переменных CSS.Может уменьшить повторяющаяся логика и размер пакета общего CSS.
После понимания BEM и приведенного выше введения в CSS Render давайте рассмотрим следующий код:
import CssRender from 'css-render'
import bem from '@css-render/plugin-bem'
const cssr = CssRender()
const plugin = bem({
blockPrefix: '.ggl-'
})
cssr.use(plugin) // 为 cssr 注册 bem 插件
const { cB, cE, cM } = plugin
const style = cB(
'container',
[
cE(
'left, right',
{
width: '50%'
}
),
cM(
'dark',
[
cE(
'left, right',
{
backgroundColor: 'black'
}
)
]
)
]
)
// 查看渲染的 CSS 样式字符串
console.log(style.render())
// 将样式挂载到 head 标签里面,可以提供 options
style.mount(/* options */)
// 删除挂载的样式
style.unmount(/* options */)
Приведенный выше код в основном выполняет следующую работу:
-
Инициализируйте экземпляр CSS Render, затем инициализируйте экземпляр подключаемого модуля BEM и добавьте общий класс стиля.
.ggl-
приставка -
Экспорт из плагина БЭМ
cB
,cE
,cM
метод, а затем следуйте концепции БЭМ, чтобы упорядочить, вложить и объединить классы стилей на основе этих трех методов, чтобы сформировать наш окончательный класс стиля и соответствующий стиль- прежде всего
cB
, который определяет блочный элемент верхнего уровня какcontainer
- Тогда этот блок содержит два дочерних элемента, которые
cE
, представляющий дочерние элементы, подчиненные родительскому блокуleft
а такжеright
, что соответствует примерноwidth
стиль; иcM
, модификатор, изменяющий родительский блокdark
-
dark
Модификатор, в свою очередь, содержит дочерний элемент, принадлежащийcE
, что означает подчинение модифицируемому этим модификатором блоку, включая дочерние элементыleft
а такжеright
, что соответствует примерноbackgroundColor
стиль
- прежде всего
Поняв вышеупомянутые отношения иерархической вложенности, мы можем написать приведенное вышеstyle
провестиrender
После воздействия:
// .ggl- 前缀,以及 cB('container', [cE('left, right', { width: '50%' } )])
.ggl-container .ggl-container__left,
.ggl-container .ggl-container__right {
width: 50%;
}
// .ggl- 前缀,以及 cB('container', [cM('dark', [cE('left, right', { backgroundColor: 'black' } )])])
.ggl-container.ggl-container--dark .ggl-container--left,
.ggl-container.ggl-container--dark .ggl-container__right {
background-color: black;
}
можно увидетьcM
Определенный модификатор фактически является блоком прямой модификации, то есть при генерации класса он будет.ggl-container.ggl-container--dark
Он записывается непосредственно с классом родительского блока, который принадлежит отношению модификации, а не подчиненному отношению.
Наивная организация стиля пользовательского интерфейса
Наивный пользовательский интерфейс в основном следует следующей логике с точки зрения организации стилей, по-прежнему взяв в качестве примера кнопку:
- Подключить переменные CSS, где есть переменные по умолчанию и пользовательские переменные,
cssVars
передано на лейблstyle
поле для крепления - Смонтируйте базовые стили, связанные с кнопками, стили, связанные с темой, и создайте имена классов CSS.
- Смонтируйте глобальный стиль по умолчанию (этот шаг находится в конце, убедитесь, что глобальный стиль по умолчанию не будет перезаписан)
Выполнив указанные выше три шага, вы можете определить все классы и стили, связанные с кнопками, а также поддерживать настройку темы, перезагрузку темы и другие функции с помощью переменных CSS.
Вышеупомянутые три шага в основном вsetup
вызов внутри функцииuseTheme
Хук, который обрабатывает монтирование стиля, связанного с кнопкой, и монтирование глобального стиля по умолчанию, а затем обрабатывает определение и использование переменных CSS:
const Button = defineComponent({
name: "Button",
props: buttonProps,
setup(props) {
const themeRef = useTheme(
"Button",
"Button",
style,
buttonLight,
props,
mergedClsPrefixRef
);
return {
// 定义边框颜色相关
customColorCssVars: computed(() => {}),
// 定义 字体、边框、颜色、大小相关
cssVars: computed(() => {}),
}
}
render() {
// 定义 button 相关的 CSS 变量
<Component style={this.cssVars}>
// 定义边框颜色独有的 CSS 变量
<div class={`${mergedClsPrefix}-button__border`} style={this. customColorCssVars} />
<div class={`${mergedClsPrefix}-button__state-border`} style={this. customColorCssVars} />
</Component>
}
});
Стили, связанные с горой Баттн
Монтаж, связанные с кнопкой, и содержание, связанное со содержанием монтажа в глобальном стилеButton
компонентsetup
в методеuseTheme
крючки,useTheme
функция ловушки со следующей структурой:
/* 全局 CSS Variables 的类型 */
type ThemeCommonVars = typeof { primaryHover: '#36ad6a', errorHover: '#de576d', ... }
// Button 独有的 CSS Variable 类型
type ButtonThemeVars = ThemeCommonVars & { /* Button 相关的 CSS Variables 的类型 */ }
// Theme 的类型
interface Theme<N, T = {}, R = any> {
// 主题名
name: N
// 主题一些通用的 CSS Variables
common?: ThemeCommonVars
// 相关依赖组件的一些 CSS Variables,如 Form 里面依赖 Button,对应的 Button
// 需要包含的 CSS Variables 要有限制
peers?: R
// 主题自身的一些个性化的 CSS Variables
self?: (vars: ThemeCommonVars) => T
}
// Button Theme 的类型
type ButtonTheme = Theme<'Button', ButtonThemeVars >
interface GlobalThemeWithoutCommon {
Button?: ButtonTheme
Icon?: IconTheme
}
// useTheme 方法传入 props 的类型
type UseThemeProps<T> = Readonly<{
// 主题相关变量,如 darkTheme
theme?: T | undefined
// 主题中可以被重载的变量
themeOverrides?: ExtractThemeOverrides<T>
// 内建主题中可以被重载的变量
builtinThemeOverrides?: ExtractThemeOverrides<T>
}>
// 最终合并的 Theme 的类型
type MergedTheme<T> = T extends Theme<unknown, infer V, infer W>
? {
common: ThemeCommonVars
self: V
// 相关依赖组件的一些 CSS Variables,如 Form 里面依赖 Button,对应的 Button
// 需要包含的 CSS Variables 要有限制
peers: W
// 相关依赖组件的一些 CSS Variables,如 Form 里面依赖 Button,对应的 Button
// 需要包含的 CSS Variables 要有限制,这些 CSS Variables 中可以被重载的变量
peerOverrides: ExtractMergedPeerOverrides<T>
}
: T
useTheme<N, T, R>(
resolveId: keyof GlobalThemeWithoutCommon,
mountId: string,
style: CNode | undefined,
defaultTheme: Theme<N, T, R>,
props: UseThemeProps<Theme<N, T, R>>,
// n
clsPrefixRef?: Ref<string | undefined>
) => ComputedRef<MergedTheme<Theme<N, T, R>>>
можно увидеть,useTheme
Он в основном получает 6 параметров:
-
resolveId
Пара "ключ-значение" для таргетинга в теме глобального стиля, здесь'Button'
-
mountId
Стили монтируются наhead
При маркировке,style
изid
-
style
Метка стиля и строка стиля, сгенерированные формой CSS Render компонента, то есть соответствующий скелет класса, класса и стиля, связанного с кнопкой, который содержит ряд переменных CSS, которые необходимо использовать. -
defaultTheme
дляButton
Переменные CSS по умолчанию, связанные с темой -
props
При использовании компонента для пользователя входящие свойства можно настроить так, чтобы они переопределяли переменные стиля по умолчанию. -
clsPrefixRef
Префикс для общего класса стиля, в наивном пользовательском интерфейсе этоn
useTheme
Возвращает коллекцию стилей, которая объединяет встроенные стили, глобально определенные стили, связанные с кнопкой, и пользовательские стили.ComputedRef<MergedTheme<Theme<N, T, R>>>
.
понялuseTheme
После ввода и вывода функции-хука можно продолжить рассмотрение основной логики ее функции:
function useTheme(
resolveId,
mountId,
style,
defaultTheme,
props,
clsPrefixRef
) {
if (style) {
const mountStyle = () => {
const clsPrefix = clsPrefixRef?.value;
style.mount({
id: clsPrefix === undefined ? mountId : clsPrefix + mountId,
head: true,
props: {
bPrefix: clsPrefix ? `.${clsPrefix}-` : undefined,
},
});
globalStyle.mount({
id: "naive-ui/global",
head: true,
});
};
onBeforeMount(mountStyle);
}
const NConfigProvider = inject(configProviderInjectionKey, null);
const mergedThemeRef = computed(() => {
const {
theme: { common: selfCommon, self, peers = {} } = {},
themeOverrides: selfOverrides = {},
builtinThemeOverrides: builtinOverrides = {},
} = props;
const { common: selfCommonOverrides, peers: peersOverrides } =
selfOverrides;
const {
common: globalCommon = undefined,
common: globalSelfCommon = undefined,
self: globalSelf = undefined,
peers: globalPeers = {},
} = {},
} = NConfigProvider?.mergedThemeRef.value || {};
const {
common: globalCommonOverrides = undefined,
= {},
} = NConfigProvider?.mergedThemeOverridesRef.value || {};
const {
common: globalSelfCommonOverrides,
peers: globalPeersOverrides = {},
} = globalSelfOverrides;
const mergedCommon = merge(
{},
selfCommon || globalSelfCommon || globalCommon || defaultTheme.common,
globalCommonOverrides,
globalSelfCommonOverrides,
selfCommonOverrides
);
const mergedSelf = merge(
// {}, executed every time, no need for empty obj
(self || globalSelf || defaultTheme.self)?.(mergedCommon),
builtinOverrides,
globalSelfOverrides,
selfOverrides
);
return {
common: mergedCommon,
self: mergedSelf,
peers: merge({}, defaultTheme.peers, globalPeers, peers),
peerOverrides: merge({}, globalPeersOverrides, peersOverrides),
};
});
return mergedThemeRef;
}
можно увидетьuseTheme
Основная логика состоит из двух частей:
-
Первая часть - монтировать
button
связанные стили сclsPrefix + mountId
,Включатьbutton
Скелет класса связанного стиля и смонтировать глобальный общий стиль вnaive-ui/global
, а процесс монтирования этого стиля находится вonBeforeMount
Когда вызывается хук, его можно уточнить в соответствии с порядком монтирования стиля, описанным ранее:- Порядок
setup
Вернуть переменные CSS внутри, а затем пройдите меткуstyle
Регистрация переменных CSS - Затем смонтируйте скелет стиля, связанный с кнопкой
- Затем смонтируйте скелет глобального общего стиля, чтобы убедиться, что скелет стиля, связанный с кнопкой, не переопределяет глобальный общий стиль.
- Порядок
-
Вторая часть создает новый набор переменных темы для интеграции тем, определяемых пользователем, и тем, настроенных внутри.
- Пользовательские темы
props
:Включатьtheme
,themeOverrides
,builtinThemeOverrides
- Тема внутренней конфигурации
NConfigProvider?.mergedThemeRef.value
а такжеNConfigProvider?.mergedThemeOverridesRef.value
- Пользовательские темы
Далее мы сосредоточимся на объяснении значения конкретных кодов и связанных с ними переменных этих двух частей.
в первой частиbutton
Соответствующие стили следующие:
import { c, cB, cE, cM, cNotM } from "../../../_utils/cssr";
import fadeInWidthExpandTransition from "../../../_styles/transitions/fade-in-width-expand.cssr";
import iconSwitchTransition from "../../../_styles/transitions/icon-switch.cssr";
export default c([
cB(
"button",
`
font-weight: var(--font-weight);
line-height: 1;
font-family: inherit;
padding: var(--padding);
// .... 更多的定义
`, [
// border ,边框相关的样式类骨架
cM("color", [
cE("border", {
borderColor: "var(--border-color)",
}),
cM("disabled", [
cE("border", {
borderColor: "var(--border-color-disabled)",
}),
]),
cNotM("disabled", [
c("&:focus", [
cE("state-border", {
borderColor: "var(--border-color-focus)",
}),
]),
c("&:hover", [
cE("state-border", {
borderColor: "var(--border-color-hover)",
}),
]),
c("&:active", [
cE("state-border", {
borderColor: "var(--border-color-pressed)",
}),
]),
cM("pressed", [
cE("state-border", {
borderColor: "var(--border-color-pressed)",
}),
]),
]),
]),
// icon 相关的样式类骨架
cE(
"icon",
`
margin: var(--icon-margin);
margin-left: 0;
height: var(--icon-size);
width: var(--icon-size);
max-width: var(--icon-size);
font-size: var(--icon-size);
position: relative;
flex-shrink: 0;
`,
[
cB(
"icon-slot",
`
height: var(--icon-size);
width: var(--icon-size);
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
display: flex;
`,
[
iconSwitchTransition({
top: "50%",
originalTransform: "translateY(-50%)",
}),
]
),
fadeInWidthExpandTransition(),
]
),
// content 子元素内容相关的样式类骨架
cE(
"content",
`
display: flex;
align-items: center;
flex-wrap: nowrap;
`,
[
c("~", [
cE("icon", {
margin: "var(--icon-margin)",
marginRight: 0,
}),
]),
]
),
// 更多的关于 backgroundColor、base-wave 点击反馈波纹,icon,content,block 相关的样式定义
],
// 动画、过渡相关的样式类骨架
c("@keyframes button-wave-spread", {
from: {
boxShadow: "0 0 0.5px 0 var(--ripple-color)",
},
to: {
// don't use exact 5px since chrome will display the animation with glitches
boxShadow: "0 0 0.5px 4.5px var(--ripple-color)",
},
}),
c("@keyframes button-wave-opacity", {
from: {
opacity: "var(--wave-opacity)",
},
to: {
opacity: 0,
},
}),
]);
Приведенный выше код, связанный с CSS Render, в конечном итоге создаст что-то вроде следующего:
.n-button {
font-weight: var(--font-weight);
line-height: 1;
font-family: inherit;
padding: var(--padding);
transition:
color .3s var(--bezier),
background-color .3s var(--bezier),
opacity .3s var(--bezier),
border-color .3s var(--bezier);
}
.n-button.n-button--color .n-button__border {
border-color: var(--border-color);
}
.n-button.n-button--color.n-button--disabled .n-button__border {
border-color: var(--border-color-disabled);
}
.n-button.n-button--color:not(.n-button--disabled):focus .n-button__state-border {
border-color: var(--border-color-focus);
}
.n-button .n-base-wave {
pointer-events: none;
top: 0;
right: 0;
bottom: 0;
left: 0;
animation-iteration-count: 1;
animation-duration: var(--ripple-duration);
animation-timing-function: var(--bezier-ease-out), var(--bezier-ease-out);
}
.n-button .n-base-wave.n-base-wave--active {
z-index: 1;
animation-name: button-wave-spread, button-wave-opacity;
}
.n-button .n-button__border, .n-button .n-button__state-border {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
border-radius: inherit;
transition: border-color .3s var(--bezier);
pointer-events: none;
}
.n-button .n-button__icon {
margin: var(--icon-margin);
margin-left: 0;
height: var(--icon-size);
width: var(--icon-size);
max-width: var(--icon-size);
font-size: var(--icon-size);
position: relative;
flex-shrink: 0;
}
.n-button .n-button__icon .n-icon-slot {
height: var(--icon-size);
width: var(--icon-size);
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
display: flex;
}
.n-button .n-button__icon.fade-in-width-expand-transition-enter-active {
overflow: hidden;
transition:
opacity .2s cubic-bezier(.4, 0, .2, 1) .1s,
max-width .2s cubic-bezier(.4, 0, .2, 1),
margin-left .2s cubic-bezier(.4, 0, .2, 1),
margin-right .2s cubic-bezier(.4, 0, .2, 1);
}
.n-button .n-button__content {
display: flex;
align-items: center;
flex-wrap: nowrap;
}
.n-button .n-button__content ~ .n-button__icon {
margin: var(--icon-margin);
margin-right: 0;
}
.n-button.n-button--block {
display: flex;
width: 100%;
}
.n-button.n-button--dashed .n-button__border, .n-button.n-button--dashed .n-button__state-border {
border-style: dashed !important;
}
.n-button.n-button--disabled {
cursor: not-allowed;
opacity: var(--opacity-disabled);
}
@keyframes button-wave-spread {
from {
box-shadow: 0 0 0.5px 0 var(--ripple-color);
}
to {
box-shadow: 0 0 0.5px 4.5px var(--ripple-color);
}
}
@keyframes button-wave-opacity {
from {
opacity: var(--wave-opacity);
}
to {
opacity: 0;
}
}
можно увидетьbutton
Связанные стили обрабатывают различные сценарии, используя стиль именования БЭМ:
- border и state-border , о стилях в отключенном, нажатом, наведении, активном и других состояниях
.n-button.n-button--color:not(.n-button--disabled):focus .n-button__state-border {
border-color: var(--border-color-focus);
}
- Стиль пульсации при нажатии кнопки
.n-button .n-base-wave
- Стили, связанные со значками, в кнопках
.n-button .n-button__icon
- Стили для таких вещей, как текст в кнопках
.n-button .n-button__content
В то же время вы можете видеть, что соответствующие CSS-переменные зарезервированы для различных свойств в стиле, включая box-shadow--ripple-color
, значок ширина и высота--icon-size
, анимация переходаtransition
из--bezier
, эти переменные оставляют место для последующей настройки различных стилей и тем.
То есть при проектировании системы стилей библиотеки компонентов шаблоны стилей, относящиеся к компонентам, определяются заранее с использованием стиля БЭМ, а затем переменные, связанные с темой, которые необходимо настроить, настраиваются с помощью переменных CSS для достижения эффекта настройка темы.
монтировать глобальный стиль
Глобальный стиль в основном связан с какой-то простой базовой конфигурацией, код выглядит следующим образом:
import { c } from "../../_utils/cssr";
import commonVariables from "../common/_common";
export default c(
"body",
`
margin: 0;
font-size: ${commonVariables.fontSize};
font-family: ${commonVariables.fontFamily};
line-height: ${commonVariables.lineHeight};
-webkit-text-size-adjust: 100%;
`,
[
c(
"input",
`
font-family: inherit;
font-size: inherit;
`
),
]
);
главным образом дляmargin
,font-size
,font-family
,line-height
и другой сопутствующий контент — это стандартизация кода CSS, необходимая для совместимости с браузерами.Normalize.css: Make browsers render all elements more consistently..
вcommonVariables
следующим образом:
export default {
fontFamily:
'v-sans, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"',
fontFamilyMono: "v-mono, SFMono-Regular, Menlo, Consolas, Courier, monospace",
fontWeight: "400",
fontWeightStrong: "500",
cubicBezierEaseInOut: "cubic-bezier(.4, 0, .2, 1)",
cubicBezierEaseOut: "cubic-bezier(0, 0, .2, 1)",
cubicBezierEaseIn: "cubic-bezier(.4, 0, 1, 1)",
borderRadius: "3px",
borderRadiusSmall: "2px",
fontSize: "14px",
fontSizeTiny: "12px",
fontSizeSmall: "14px",
fontSizeMedium: "14px",
fontSizeLarge: "15px",
fontSizeHuge: "16px",
lineHeight: "1.6",
heightTiny: "22px",
heightSmall: "28px",
heightMedium: "34px",
heightLarge: "40px",
heightHuge: "46px",
transformDebounceScale: "scale(1)",
};
Вышеупомянутые общие переменные являются одними из самых основных «сырьевых материалов» для библиотеки компонентов пользовательского интерфейса для построения вверх, а также являются передовыми отраслевыми практиками, которые не рекомендуется изменять по умолчанию, например, определениеsize
Есть 5 категорий, а именноtiny
,small
,medium
,large
,huge
, определить шрифты, шрифты кода и т. д.
Определение и регистрация переменных CSS
Основной код этого блока:
const NConfigProvider = inject(configProviderInjectionKey, null);
const mergedThemeRef = computed(() => {
const {
theme: { common: selfCommon, self, peers = {} } = {},
themeOverrides: selfOverrides = {},
builtinThemeOverrides: builtinOverrides = {},
} = props;
const { common: selfCommonOverrides, peers: peersOverrides } =
selfOverrides;
const {
common: globalCommon = undefined,
[resolveId]: {
common: globalSelfCommon = undefined,
self: globalSelf = undefined,
peers: globalPeers = {},
} = {},
} = NConfigProvider?.mergedThemeRef.value || {};
const {
common: globalCommonOverrides = undefined,
[resolveId]: globalSelfOverrides = {},
} = NConfigProvider?.mergedThemeOverridesRef.value || {};
const {
common: globalSelfCommonOverrides,
peers: globalPeersOverrides = {},
} = globalSelfOverrides;
const mergedCommon = merge(
{},
selfCommon || globalSelfCommon || globalCommon || defaultTheme.common,
globalCommonOverrides,
globalSelfCommonOverrides,
selfCommonOverrides
);
const mergedSelf = merge(
// {}, executed every time, no need for empty obj
(self || globalSelf || defaultTheme.self)?.(mergedCommon),
builtinOverrides,
globalSelfOverrides,
selfOverrides
);
return {
common: mergedCommon,
self: mergedSelf,
peers: merge({}, defaultTheme.peers, globalPeers, peers),
peerOverrides: merge({}, globalPeersOverrides, peersOverrides),
};
Первый изinject
получатьconfigProviderInjectionKey
соответствующий контент, которыйconfigProviderInjectionKey
Соответствующее содержание определяется следующим образом:
provide(configProviderInjectionKey, {
mergedRtlRef,
mergedIconsRef,
mergedComponentPropsRef,
mergedBorderedRef,
mergedNamespaceRef,
mergedClsPrefixRef,
mergedLocaleRef: computed(() => {
// xxx
}),
mergedDateLocaleRef: computed(() => {
// xxx
}),
mergedHljsRef: computed(() => {
// ...
}),
mergedThemeRef,
mergedThemeOverridesRef
})
Вы можете увидеть почти все конфигурации, включая rtl, значок, границу, пространство имен, clsPrefix, локаль (интернационализацию), дату, тему, themeOverrides и т. д. Основная цель здесь — получить конфигурацию, связанную с темой:
-
mergedThemeRef
: Настраиваемая тема, например
<template>
<n-config-provider :theme="darkTheme">
<app />
</n-config-provider>
</template>
<script>
import { darkTheme } from 'naive-ui'
export default {
setup() {
return {
darkTheme
}
}
}
</script>
-
mergedThemeOverridesRef
: Настраиваемые переменные темы, такие как
const themeOverrides = {
common: {
primaryColor: '#FF0000'
},
Button: {
textColor: '#FF0000'
backgroundColor: '#FFF000',
},
Select: {
peers: {
InternalSelection: {
textColor: '#FF0000'
}
}
}
// ...
}
Вышеуказанные два в основном включают в себя глобальныеcommon
связанные, иButton
серединаcommon
связанные юниформ-переменные,self
Некоторые переменные, связанные с настройкой кнопок, иButton
Ограничения, связанные с использованием с другими компонентамиpeers
Переменная.
отuseTheme
возврат в функции ловушкиthemeRef
Позже,themeRef
Связанный контент будет использоваться для сборки различных стилей, задействованных в кнопке, в основном из следующих четырех направлений:
fontProps
colorProps
borderProps
sizeProps
cssVars: computed(() => {
// fontProps
// colorProps
// borderProps
// sizeProps
return {
// 处理 动画过渡函数、透明度相关的变量
"--bezier": cubicBezierEaseInOut,
"--bezier-ease-out": cubicBezierEaseOut,
"--ripple-duration": rippleDuration,
"--opacity-disabled": opacityDisabled,
"--wave-opacity": waveOpacity,
// 处理字体、颜色、边框、大小相关的变量
...fontProps,
...colorProps,
...borderProps,
...sizeProps,
};
});
fontProps
Соответствующий код выглядит следующим образом:
const theme = themeRef.value;
const {
self,
} = theme;
const {
rippleDuration,
opacityDisabled,
fontWeightText,
fontWeighGhost,
fontWeight,
} = self;
const { dashed, type, ghost, text, color, round, circle } = props;
// font
const fontProps = {
fontWeight: text
? fontWeightText
: ghost
? fontWeighGhost
: fontWeight,
};
В основном он оценивает переменные CSS и значения, связанные со шрифтом в стандартном состоянии, когда кнопка отображается как текстовый узел, когда она отображается с прозрачным фоном и в стандартном состоянии.
colorProps
Соответствующий код выглядит следующим образом
let colorProps = {
"--color": "initial",
"--color-hover": "initial",
"--color-pressed": "initial",
"--color-focus": "initial",
"--color-disabled": "initial",
"--ripple-color": "initial",
"--text-color": "initial",
"--text-color-hover": "initial",
"--text-color-pressed": "initial",
"--text-color-focus": "initial",
"--text-color-disabled": "initial",
};
if (text) {
const { depth } = props;
const textColor =
color ||
(type === "default" && depth !== undefined
? self[createKey("textColorTextDepth", String(depth))]
: self[createKey("textColorText", type)]);
colorProps = {
"--color": "#0000",
"--color-hover": "#0000",
"--color-pressed": "#0000",
"--color-focus": "#0000",
"--color-disabled": "#0000",
"--ripple-color": "#0000",
"--text-color": textColor,
"--text-color-hover": color
? createHoverColor(color)
: self[createKey("textColorTextHover", type)],
"--text-color-pressed": color
? createPressedColor(color)
: self[createKey("textColorTextPressed", type)],
"--text-color-focus": color
? createHoverColor(color)
: self[createKey("textColorTextHover", type)],
"--text-color-disabled":
color || self[createKey("textColorTextDisabled", type)],
};
} else if (ghost || dashed) {
colorProps = {
"--color": "#0000",
"--color-hover": "#0000",
"--color-pressed": "#0000",
"--color-focus": "#0000",
"--color-disabled": "#0000",
"--ripple-color": color || self[createKey("rippleColor", type)],
"--text-color": color || self[createKey("textColorGhost", type)],
"--text-color-hover": color
? createHoverColor(color)
: self[createKey("textColorGhostHover", type)],
"--text-color-pressed": color
? createPressedColor(color)
: self[createKey("textColorGhostPressed", type)],
"--text-color-focus": color
? createHoverColor(color)
: self[createKey("textColorGhostHover", type)],
"--text-color-disabled":
color || self[createKey("textColorGhostDisabled", type)],
};
} else {
colorProps = {
"--color": color || self[createKey("color", type)],
"--color-hover": color
? createHoverColor(color)
: self[createKey("colorHover", type)],
"--color-pressed": color
? createPressedColor(color)
: self[createKey("colorPressed", type)],
"--color-focus": color
? createHoverColor(color)
: self[createKey("colorFocus", type)],
"--color-disabled": color || self[createKey("colorDisabled", type)],
"--ripple-color": color || self[createKey("rippleColor", type)],
"--text-color": color
? self.textColorPrimary
: self[createKey("textColor", type)],
"--text-color-hover": color
? self.textColorHoverPrimary
: self[createKey("textColorHover", type)],
"--text-color-pressed": color
? self.textColorPressedPrimary
: self[createKey("textColorPressed", type)],
"--text-color-focus": color
? self.textColorFocusPrimary
: self[createKey("textColorFocus", type)],
"--text-color-disabled": color
? self.textColorDisabledPrimary
: self[createKey("textColorDisabled", type)],
};
}
Основная обработка осуществляется в четырех формах: обычная, текстовый узел, призрачный фон, прозрачный, пунктирная пунктирная линия, для разных состояний Стандарт,pressed
,hover
,focus
,disabled
и т. д. иметь дело с соответствующими свойствами и значениями CSS
borderProps
Соответствующий код выглядит следующим образом:
let borderProps = {
"--border": "initial",
"--border-hover": "initial",
"--border-pressed": "initial",
"--border-focus": "initial",
"--border-disabled": "initial",
};
if (text) {
borderProps = {
"--border": "none",
"--border-hover": "none",
"--border-pressed": "none",
"--border-focus": "none",
"--border-disabled": "none",
};
} else {
borderProps = {
"--border": self[createKey("border", type)],
"--border-hover": self[createKey("borderHover", type)],
"--border-pressed": self[createKey("borderPressed", type)],
"--border-focus": self[createKey("borderFocus", type)],
"--border-disabled": self[createKey("borderDisabled", type)],
};
}
В основном это касается пяти различных состояний стандарта,pressed
,hover
,focus
,disabled
обработки в таких случаях.
здесьborderProps
На самом деле, это в основном для определения всегоborder
свойства, а свойства, связанные с цветом границы, фактически передаются черезsetup
внутриcustomColorCssVars
Определенный код выглядит следующим образом:
customColorCssVars: computed(() => {
const { color } = props;
if (!color) return null;
const hoverColor = createHoverColor(color);
return {
"--border-color": color,
"--border-color-hover": hoverColor,
"--border-color-pressed": createPressedColor(color),
"--border-color-focus": hoverColor,
"--border-color-disabled": color,
};
})
sizeProps
Соответствующий код выглядит следующим образом:
const sizeProps = {
"--width": circle && !text ? height : "initial",
"--height": text ? "initial" : height,
"--font-size": fontSize,
"--padding": circle
? "initial"
: text
? "initial"
: round
? paddingRound
: padding,
"--icon-size": iconSize,
"--icon-margin": iconMargin,
"--border-radius": text
? "initial"
: circle || round
? height
: borderRadius,
};
основная обработкаwidth
,height
,font-size
,padding
,icon
,border
содержание соответствующего размера, гдеmargin
Он обрабатывается, когда монтируется глобальный стиль по умолчанию, значение по умолчанию равно 0.
резюме
Выполните три шага выше:
- Смонтируйте скелет класса стиля, связанного с кнопкой, оставив множество переменных CSS для пользовательских стилей.
- Смонтируйте глобальный стиль по умолчанию
- Соберите и определите соответствующие переменные CSS для заполнения скелета класса стиля
Мы успешно применили CSS Render, плагин BEM и переменные CSS, чтобы завершить дизайн общего стиля кнопки, который легко понять и настроить.
Однако также стоит отметить, что, глядя на логику обработки стилей в вышеуказанных компонентах, только определенные вsetup
На самом деле можно увидеть основные сценарии использования CSS Render: то есть все условия заранее стандартизированы, соответствующие CSS-переменные предварительно заданы, а затем даны
Необходимая обработка событий не может быть меньше
Наивный интерфейс в основном обеспечивает обработку следующих типов событий:
-
mousedown
:handleMouseDown
-
keyup
:handleKeyUp
-
keydown
:handleKeyDown
-
click
:handleClick
-
blur
:handleBlur
Вы можете посмотреть код отдельно:
handleMouseDown
:
const handleMouseDown = (e) => {
e.preventDefault();
if (props.disabled) {
return;
}
if (mergedFocusableRef.value) {
selfRef.value?.focus({ preventScroll: true });
}
};
основная обработкаdisabled
не отвечает, и если можноfocus
случай, звонитеselfRef
Сосредоточьтесь и активируйте соответствующий стиль.
handleKeyUp
:
const handleKeyUp = (e) => {
switch (e.code) {
case "Enter":
case "NumpadEnter":
if (!props.keyboard) {
e.preventDefault();
return;
}
enterPressedRef.value = false;
void nextTick(() => {
if (!props.disabled) {
selfRef.value?.click();
}
});
}
};
основная обработкаEnter
,NumpadEnter
ключ, определите, поддерживается ли работа с клавиатурой, и активируйте нажатия кнопок, если это необходимо.
handleKeyDown
:
const handleKeyDown = (e) => {
switch (e.code) {
case "Enter":
case "NumpadEnter":
if (!props.keyboard) return;
e.preventDefault();
enterPressedRef.value = true;
}
};
основная обработкаEnter
,NumpadEnter
key, определите, поддерживается ли обработка клавиатуры, и при необходимости обновите ее.enterPressedRef
значение, флаг в настоящее времяkeydown
проходить.
handleClick
:
const handleClick = (e) => {
if (!props.disabled) {
const { onClick } = props;
if (onClick) call(onClick, e);
if (!props.text) {
const { value } = waveRef;
if (value) {
value.play();
}
}
}
};
Вызовите соответствующую функцию обработки кликов в соответствии с состоянием и волновым эффектом щелчка кнопки воспроизведения под нетекстовым узлом.
handleBlur
:
const handleBlur = () => {
enterPressedRef.value = false;
};
возобновитьenterPressedRef
Значение флага в настоящее время размыто.
Резюме и перспективы
В этой статье анализируется весь процесс создания кнопки наивного пользовательского интерфейса слой за слоем и на уровне исходного кода.Можно обнаружить, что в области библиотеки компонентов большая часть идей тратится на то, как разработать расширяемую систему стилей, от Ant Design, Element Пользовательский интерфейс использует Less для организации системы стилей, а Material Design использует CSS-in-JS, напримерstyled-components
Для организации системы стилей, а сейчас Naive UI использует CSS Render для организации системы стилей.Хотя существует множество форм организации системы стилей, на самом деле, насколько я понимаю, при проектировании классов стилей, соответствующих стилей, расширений стилей и настройка темы в целом должна оставаться одинаковой.
Если вы можете понять весь процесс работы с Button с помощью этой запутанной статьи и по-прежнему сохранять интерес к общему исходному коду и инженерному направлению Naive UI, вы можете следовать этой логике, чтобы понять принципы проектирования других компонентов, так же, как мне нравится изображение в начале, вы будете чувствовать себя все более и более простым, поскольку вы понимаете общий код:
Понимание дизайна исходного кода отличных библиотек и изучение исходного кода Дэниела может помочь нам понять передовой опыт в отрасли, отличные дизайнерские идеи, улучшить способ написания кода и стать лучшими разработчиками Мы поощряем вас и меня 💪!
использованная литература
- css-tricks.com/bem-101/
- woooooo.smashing magazine.com/2018/06/by the devil…
- ТБ em.com/intro duct IO…
- Как насчет Colas.GitHub.IO/normalize. From…
- Woohoo.naive UI.com/this-cn/OS-Diva…
- GitHub.com/07AK IO вы/в это время…
- Уууу, я гражданский.com/UC's/4243012…
- ТБ em.com/intro duct IO…
❤️/ Спасибо за поддержку /
Выше приведено все содержание этого обмена, я надеюсь, что это поможет вам ^_^
Если вам понравилось, не забудьте поделиться, поставить лайк и добавить в избранное~
Добро пожаловать на общедоступный номеравтобус программиста, три брата из Byte, Shopee и Zhaoyin, делятся опытом программирования, техническими галантерейными товарами и планированием карьеры, чтобы помочь вам избежать окольных путей, чтобы попасть на большую фабрику.