Стилизация однофайловых Vue компонентов

в 14:09, , рубрики: css, javascript, TypeScript, vue, vue.js, vuejs

Эта статья — перевод оригинальной статьи Lindsay Wardell "Styling Vue Single-File Components"

Также я веду телеграм канал “Frontend по-флотски”, где рассказываю про интересные вещи из мира разработки интерфейсов.

Вступление

Если у вас есть опыт написания однофайловых Vue компонентов, вы, вероятно, сталкивались с написанием CSS в своем компоненте. Они позволяют разработчикам группировать код более логическими способами, а не разбивать компоненты по используемому языку (HTML, CSS или JavaScript). Возможность группировать стили компонентов непосредственно рядом с HTML-кодом, к которому он применяется, является одним из основных преимуществ Vue, включая возможность применять CSS к компоненту, чтобы он не влиял на другие части пользовательского интерфейса.

Однако есть ряд функций взаимодействия Vue с CSS, с которыми вы, возможно, не знакомы, например, применение стилей непосредственно к элементам со слотами или новейшие функции, доступные в Vue 3.2. Давайте рассмотрим некоторые из этих других способов стилизации однофайловых Vue компонентов и их преимущества для ваших приложений.

Scoped стили

Начнем с наиболее частого использования CSS в Vue: стили с ограниченными областями видимости. Одна из трудностей при написании современных приложений заключается в том, что наши CSS файлы начинают расти все больше и больше, пока никто не знает, где используются определенные стили или на что может повлиять данное изменение. Это может привести к копированию определенных CSS селекторов и простому дублированию их для каждого компонента. Для этого есть и другие решения (например, БЭМ или служебные классы), но при работе с компонентной структурой, такой как Vue, имеет смысл сгруппировать классы CSS внутри компонента.

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

<style scoped>
.example {
  color: red;
}
</style>

<template>
  <div class="example">hi</div>
</template>

Класс из примера будет применяться только в этом компоненте. Это достигается путем добавления уникального атрибута data ко всем элементам в компоненте, поэтому по-прежнему применяется обычный CSS каскад. Внешние стили по-прежнему могут влиять на дизайн этого компонента, но его стили с областью видимости не могут проникать в другие компоненты.

Глубокие стили

Это приводит к интересной проблеме. Если стили нашего компонента имеют ограниченную область видимости, как насчет дочерних компонентов? По умолчанию они не получат стили родительского компонента. Однако Vue предоставляет способ сделать это. Давайте посмотрим на пример ниже.

<!-- Card.vue -->
<template>
  <div>
    <header>
      <Title>
        <slot name="title">Card Title</slot>
      </Title>
    </header>
    <section>
      <slot>Lorum ipsum dolor sit amet</slot>
    </section>
  </div>
</template>

<style scoped>
header :deep(.card-title) {
  font-weight: bold;
}

section {
  padding 2rem;
}
</style>

<!-- Title.vue -->
<template>
  <div class="card-title"><slot>Title</slot></div>
</template>

Используя псевдокласс :deep(), мы можем сообщить Vue, что этот конкретный класс (.card-title) не должен иметь области видимости. Поскольку специальный идентификатор по-прежнему применяется к корневому элементу (заголовку), стиль по-прежнему ограничен, но доступен для любого дочернего компонента ниже него.

Slotted стили

Проблема, с которой я сталкивался во многих ситуациях, заключается в том, что у меня есть компонент, в который вставляются слоты, но я не могу контролировать его стиль, как хочу. Vue предлагает решение и для этого с помощью slotted стилей. Давайте рассмотрим приведенный выше пример, но на этот раз мы добавим slotted стиль к нашему компоненту Title.vue.

<!-- Card.vue -->
<template>
  <div>
    <header>
      <Title>
        <slot name="title">Card Title</slot>
      </Title>
    </header>
    <section>
      <slot>Lorum ipsum dolor sit amet</slot>
    </section>
  </div>
</template>

<style scoped>
header :deep(.card-title) {
  font-weight: bold;
}

section {
  padding 2rem;
}
</style>

<!-- Title.vue -->
<template>
  <div class="card-title">
    <slot>Title</slot>
  </div>
</template>

<style scoped>
:slotted(h1) {
  font-size: 3rem;
}
</style>

Здесь мы добавили псевдокласс :slotted, чтобы к любым тегам h1 со слотами применялся правильный стиль. Это может быть надуманный пример, но подумайте о необходимости иметь разные стили заголовка для каждого тега заголовка (или эквивалентного класса CSS). Компонент Title.vue может управлять всеми этими стилями, а не полагаться на то, что тот, кто будет использовать этот компонент передаст правильный класс или стиль.

Глобальные стили

Конечно, иногда вам нужно применить стили глобально, даже внутри компонента с ограниченной областью видимости. Vue предоставляет нам два разных способа справиться с этим: псевдоселектор :global и несколько блоков стилей.

:global

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

<style scoped>
:global(.red) {
  color: red;
}
</style>

Несколько блоков стилей

Также ничто не мешает вам иметь несколько блоков стилей в вашем компоненте. Просто создайте еще один тег <style> и поместите туда свои глобальные стили.

<style>
/* global styles */
</style>

<style scoped>
/* local styles */
</style>

CSS модули

Если вы работали с React, вероятно, вы более знакомы с CSS модулями, в которых вы импортируете CSS файл и получаете доступ к его классам как к JavaScript объекту. То же самое можно сделать в Vue, используя <style module> вместо <style scoped>. Вот пример из документации Vue:

<template>
  <p :class="$style.red">
    This should be red
  </p>
</template>

<style module>
.red {
  color: red;
}
</style>

С этим может быть особенно приятно работать, так как вам не придётся использовать строки в своих классах (которые подвержены ошибкам и опечаткам). Vue также позволяет вам переименовывать объект, так что вам не нужно обращаться к ним с помощью $style в вашем шаблоне, если вы этого не хотите.

Динамические CSS значения

Последняя функция Vue - это динамические CSS значения, управляемые состоянием. В современном CSS есть тенденция использовать кастомные свойства как способ динамического обновления значения некоторых CSS свойств. Это может сделать наш CSS более гибким и хорошо взаимодействовать с другим кодом нашего приложения. Давайте посмотрим на пример компонента, который отображает индикатор выполнения:


<template>
	<div>
		<strong>
			Progress
		</strong>
		<div>{{ progress }}%</div>
		<div class="progress-bar">
			<div></div>
		</div>
	</div>
</template>

<script setup>
import { watch } from 'vue'

const props = defineProps({
  progress: {
    type: Number,
    required: true
  }
})

watch(props.progress,
  (value) => 
    document
      .documentElement
      .style
      .setProperty('--complete-percentage', value + '%'),
      {
        immediate: true
      })
</script>

<style scoped>
.progress-bar {
	background-color: #ccc;
	border-radius: 13px;
	padding: 3px;
}

.progress-bar > div {
  background-color: #000;
  width: var(--complete-percentage);
  height: 8px;
  border-radius: 10px;
  transition-property: width;
  transition-duration: 150ms;
}
</style>

Этот компонент принимает число (progress), затем отображает это число и обновляет кастомное CSS свойство. По мере изменения хода выполнения CSS свойство постоянно обновляется, чтобы оставаться в синхронизации с JavaScript значением.

Однако в Vue 3.2 нам предоставляется специальная CSS функция, которая делает все это за нас! Взгляните на обновленный код:

<template>
	<div>
		<strong>
			Progress
		</strong>
		<div>{{ progress }}%</div>
		<div class="progress-bar">
			<div></div>
		</div>
	</div>
</template>

<script setup>
const props = defineProps({
  progress: {
    type: Number,
    required: true
  }
})
</script>

<style scoped>
.progress-bar {
	background-color: #ccc;
	border-radius: 13px;
	padding: 3px;
}

.progress-bar > div {
  background-color: #000;
  width:  v-bind(props.progress);
  height: 8px;
  border-radius: 10px;
  transition-property: width;
  transition-duration: 150ms;
}
</style>

Используя v-bind (props.progress), мы устранили необходимость в нашем наблюдателе, и теперь стал ясно, что наш CSS синхронизируется со значением props.progress. Под капотом Vue делает для нас то же самое с кастомным свойством, но это намного приятнее, чем писать его самостоятельно.

Заключение

На практике CSS - сложный язык, и его смешивание с JavaScript еще более усложняет задачу. Vue предоставляет разработчикам инструменты для надежной и предсказуемой обработки CSS, что способствует построению компонентной архитектуры. В следующий раз, когда у вас возникнут проблемы с CSS во Vue, посмотрите, может ли вам пригодиться один из этих методов!

Автор:
qmzik

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js