Иногда библиотеки, которые мы используем ежедневно, могут показаться немного волшебными. Потратив время на понимание фундаментальных функций JavaScript, которые делают эти библиотеки возможными, мы лучше подготовлены к тому, чтобы улучшить использование этих библиотек, применяя их функциональные возможности уникальными и актуальными способами.
В React есть две библиотеки, которые работают вместе, чтобы сделать создание презентационных компонентов очень простым: styled-components
и styled-system
.
Вот пример совместной работы этих библиотек, любезно предоставленный документацией по стилевой системе.
import styled from 'styled-components' import { color } from 'styled-system'
const Box = styled.div` ${color} `
Этот код создал компонент React с именем Box
, который получает реквизиты color
и bg
.
<Box color="#fff" bg="tomato">
Tomato
</Box>
В этой статье я расскажу:
- Создание компонентов React с помощью шаблонов с тегами
- Реализация простой версии стилизованных компонентов
- Погружаемся в то, как
styled-components
иstyled-systems
на самом деле работают вместе
Шаблонные литералы
Чтобы понять, как работают styled-components
и styled-systems
, лучше сначала понять, откуда эти библиотеки черпают свою силу: литералы шаблонов.
Наиболее распространенный вариант использования шаблонных литералов - это конкатенация строк.
// Template Literal const string = `I am a template literal, because I am enclosed by the back-tick`;
// Template Literal with placeholder const expressions = 'dynamic values'; const string = `I can contain ${expressions}`;
Шаблонные литералы чисты, эффективны и настолько хорошо адаптированы для конкатенации строк, что некоторых из нас (например, меня) даже не волнует, как работает этот синтаксис, или они понимают, что его функциональность может быть расширена.
Шаблонные литералы и предыдущая функция
Когда мы реализуем шаблонные литералы, происходит что-то неинтуитивное: содержимое шаблонного литерала (например, текст и заполнители) передается в функцию.
Какая функция? В двух приведенных выше примерах функция по умолчанию с задачей объединения текста и заполнителей в одну строку.
const expressions = 'dynamic values'; const example = `I can contain ${expressions}`;
console.log(example); // >> I can contain dynamic values
Но шаблонные литералы не ограничиваются выполнением только конкатенации строк. JavaScript позволяет нам использовать наши собственные пользовательские функции, чтобы делать все, что мы хотим, с текстом и заполнителями внутри литерала шаблона. Этот тип функции называется тегом, и для его использования вы просто указываете имя функции - тег - перед литералом шаблона. В результате создается шаблон с тегами.
Например, вот простая функция, которая не принимает никаких параметров и выводит статическое сообщение на консоль.
const printStaticMessage = () => { console.log('My static message'); }
Мы можем вызвать эту функцию двумя способами: как традиционный вызов функции и как шаблон с тегами.
printStaticMessage(); // >>> My static message
printStaticMessage``; // >>> My static message
Обратите внимание, что каждый вызов дает одинаковый результат. Итак, мы можем сделать вывод, что шаблоны с тегами - это просто альтернативный способ вызова функции.
Использование шаблонного литерала в качестве аргумента для тега
Более полезный шаблон с тегами будет использовать текст и заполнители внутри литерала шаблона. Давайте создадим тег, который печатает свои аргументы.
const printArguments = (...args) => { console.log(...args); }
const var1 = "my"; const var2 = "message"
printArguments`This is ${var1} custom ${var2}!`; // // ["This is "," custom ","!"], // "my", // "message" //
Первый аргумент - это массив строк, представляющих текст в шаблонном литерале, разделенных на элементы в зависимости от расположения заполнителей.
Остальные аргументы n представляют собой строки со значением каждого заполнителя, упорядоченные в зависимости от того, когда они определены в литерале шаблона.
Зная, что это аргументы, которые получают теги, мы можем догадаться, как выглядит реализация функции конкатенации шаблонных литералов по умолчанию:
const defaultFunction = (stringArray, ...values) => { return stringArray.reduce((acc, str, i) => { return values[i] ? acc + str + values[i] : acc + str; }, ''); }
const var1 = "my"; const var2 = "message"
const example = defaultFunction`This is ${var1} custom ${var2}!`; console.log(example); // >>> This is my custom message!
Передача функций в шаблон с тегами
Поскольку тег просто принимает заполнители шаблонных литералов в качестве переменных аргументов, эти переменные могут содержать любой объект JavaScript, например число, строку или функцию. Вот бесполезный пример, в котором мы передаем функцию в заполнитель шаблонного литерала и выполняем ее в теге.
const executeFirstPlaceholder = (textArray, placeholder) => { placeholder(); }
executeFirstPlaceholder`${() => { console.log('first placeholder')}}`;
// >>> first placeholder
Возврат функций из шаблона с тегами
Как и любая другая функция в JavaScript, шаблон с тегами может возвращать объекты, строки, числа и, конечно же, функции.
const multiply = (multiple) => (num) => parseInt(num[0]) * parseInt(multiple[0]);
const double = multiply`2` const result = double`4`;
console.log(result); // >>> 8
Переход к React
«Функциональные компоненты» React - это очень простые функции JavaScript, которые могут быть отображены в DOM. Вот пример шаблона с тегами, возвращающего компонент функции React.
const withGreeting = ([greeting]) => ({ name }) => <h1>{greeting}, {name}!</h1>;
const Greet = withGreeting`Greetings`;
// Render component <Greet name="Chris" />
// Renders in DOM <h1>Greetings, Chris</h1>
Это суть того, как стилизованные компоненты генерируют компоненты React.
Стилизованные компоненты
Используя тегированные литералы шаблонов (недавнее дополнение к JavaScript) и мощь CSS, styled-components позволяет вам писать реальный код CSS для стилизации ваших компонентов. Он также удаляет сопоставление между компонентами и стилями - использование компонентов в качестве низкоуровневой конструкции стиля очень просто!
Стилизованные компоненты используют шаблоны с тегами для возврата компонентов React.
В следующем примере styled.h1
используется для создания простого компонента React, содержащего HTML-тег <h1>
, отображаемого с использованием стилей CSS, указанных в литерале шаблона.
import styled from 'styled-components';
const Title = styled.h1`color: blue;`;
// Render <Title>Regular Title</Title>
// Renders to DOM <h1 style="color: blue;">Regular Title</h1>
Объект styled
содержит ключи, названные в честь общих тегов HTML, таких как H1, H2 и div. Эти клавиши ссылаются на функцию, которую можно использовать в качестве тега в шаблоне с тегами.
Простая реализация styled.h1
Попробуем сделать простую реализацию styled.h1
. В самом простом случае функция styled.h1
получает стили CSS в обратных тиках и преобразует их в объект стиля, который присоединяется к базовому элементу (например, h1
).
const styledH1 = ([styles]) => ({ children })=> { const lines = styles .split(';') .map(line => line.trim()) .filter(line => line !== "")
// Function courtesy of mck89 on StackOverflow const convertToCamelCase = (key) => key.replace(/-([a-z])/g, (x, up) => up.toUpperCase())
const style = lines.reduce((acc, line) => { const lineParsed = line.split(':'); const key = convertToCamelCase(lineParsed[0]); const val = lineParsed[1]; return { ...acc, [key]: val }; }, {});
return <h1 style={style}>{children}</h1> }
const H1 = styledH1` color: red; font-size: 18px; `;
// Render <H1>Hello</H1>
// Renders in DOM <h1 style="color: red; font-size: 18px;">Hello</h1>
На этом этапе стиль, который мы передаем функции, жестко запрограммирован и исправлен; не может динамически изменяться в зависимости от значений свойств, которые получает компонент.
Давайте посмотрим, как возможность передавать функции в наши шаблоны с тегами может сделать вещи более динамичными.
Использование функций для доступа к свойствам в шаблонных литералах и стилизованных компонентах
Как мы уже обсуждали, функция, переданная в заполнитель шаблонного литерала, может быть выполнена. Стилизованные компоненты используют эту функцию для создания динамических стилей.
import styled from 'styled-components';
const Button = styled.button` color: ${ (props) => props.primary ? 'blue' : 'red' }; `;
class StyledComponentsDemo extends Component { render() { return( <> <Button primary>Primary</Button> <Button>Secondary</Button> <Button primary onSubmit={() => handleSubmit()}>Submit </Button> </> ) } }
Когда стилизованный компонент визуализируется, каждой функции в шаблонном литерале передается props
компонента, и эти свойства могут использоваться для воздействия на представление компонента.
Примечание: не все реквизиты, переданные в стилизованный компонент, должны влиять на презентацию (например, onSubmit); они также могут использоваться только базовым элементом HTML.
Стилизация обычных, нестандартных компонентов
Стилизованные компоненты позволяют стилизовать любой созданный вами пользовательский компонент. Во-первых, пользовательский компонент должен получить свойство className
и передать его базовому элементу DOM. Как только это будет сделано, передайте пользовательский компонент функции styled
и вызовите его как шаблон с тегами, чтобы получить новый компонент со стилем.
import styled from 'styled-components';
const Button = ({ className, children }) => <button className={className}>{children}</button>
const ButtonBlue = styled(Button)`color: blue`;
// Render <ButtonBlue>Blue Button</ButtonBlue>
Стилизация стилизованных компонентов
Стилизованные компоненты используют препроцессор CSS stylis, поддерживающий SCSS-подобный синтаксис для автоматического вложения стилей.
const Thing = styled.button` color: black;
:hover { color: blue; } `
В синтаксисе SCSS &
ссылается на текущий компонент. Вы также можете ссылаться на другие компоненты, как если бы вы ссылались на любой другой тип селектора (например, .class
или #id
), просто ссылаясь на ${OtherComponentName}
, но только если это стилизованный компонент.
import styled from 'styled-components';
const Item = styled.div` color: red; ` const Container = styled.div` & > ${Item} { font-size: 2rem; } ` class StyledComponentsDemo extends Component { render() { return( <Container> <Item>Item 1</Item> </Container> ) } }
Как видите, у нас есть возможность не только указывать стили в наших компонентах, но и добавлять некоторые динамические функции. Основываясь на этом, мы можем лучше приспособить некоторые распространенные варианты использования, такие как добавление тем в наши приложения.
Использование тем
Тематизация достигается путем экспорта компонента ThemeProvider
, передачи объекта его theme
опоре и помещения всего приложения в компонент ThemeProvider
. Это предоставит каждому стилизованному компоненту доступ к объекту theme
.
import styled, { ThemeProvider } from 'styled-components';
const Item = styled.div` color: ${( props ) => props.theme.color.primary} `
const theme = { color: { primary: 'red' } }
class StyledComponentsDemo extends Component { render() { return( <ThemeProvider theme={theme}> <Item>Item 1</Item> </ThemeProvider> ) } }
Компоненты, которые не имеют обозначения. Компоненты также могут получить доступ к теме с помощью функции withTheme
.
import { withTheme } from 'styled-components';
class MyComponent extends React.Component { render() { return <p>{this.props.theme.color.primary}</p> } }
export default withTheme(MyComponent);
Стильная система
Styled System - это набор служебных функций, которые добавляют свойства стиля к вашим компонентам React и позволяют управлять стилями на основе глобального объекта темы с типографскими масштабами, цветами и свойствами макета.
Если вы создали компонент Button
из стилизованных компонентов и хотите, чтобы он получал реквизиты цвета переднего плана и фона, вы можете использовать служебную функцию styled-system
color
и передать ее как функцию-заполнитель в литерале шаблона, чтобы включить эти реквизиты.
import styled, { ThemeProvider } from 'styled-components'; import { color } from 'styled-system'
const theme = { colors: { primary: 'blue' } }
const Box = styled.div` ${color} `
class StyledSystemDemo extends Component { render() { return ( <ThemeProvider theme={theme}> <> <Box color="#fff" bg="tomato">Tomato</Box> <Box color="white" bg="primary">Tomato</Box> </> </ThemeProvider> ) } }
Примечание: все имена сгенерированных реквизитов указаны в системном API стиля.
Если тема доступна, служебная функция попытается сопоставить значение опоры с темой, прежде чем использовать значение в качестве необработанного значения (например, #fff
).
Структурирование объектов темы
Структура объекта темы и styled-system
тесно связаны. Структура соответствует спецификации незавершенной работы, которая называется Спецификация темы пользовательского интерфейса системы.
Например, ключи fontSizes
и colors
соответствуют этой спецификации, а их значения (массивы или объекты) также соответствуют этой спецификации.
export const theme = {
fontSizes: [
12, 14, 16, 20, 24, 32
]
fontSizes.body = fontSizes[1]
colors: {
blue: '#07c',
green: '#0fa',
}
}
В вышеупомянутой теме опора fontSize
в компоненте может получать значение индекса массива или псевдоним body
.
Под капотом color
Давайте посмотрим, как styled-system
реализует функцию полезности color
. Помните, что служебная функция вызывается так:
const Button = styled.div`
${color}
`
Так выглядит функция.
// <https://github.com/styled-system/styled-system/blob/v2.3.6/src/styles.js>
export const color = props => ({ ...textColor(props), ...bgColor(props) })
Это похоже на то, чтобы написать это в литерале шаблона:
const Button = styled.div`
${(props) => ({
...textColor(props),
...bgColor(props)
})}
`
Функции textColor
и bgColor
возвращают объекты стиля, которые распределены внутри функции. Эти функции выглядят так.
// <https://github.com/styled-system/styled-system/blob/v2.3.6/src/styles.js>
export const textColor = responsiveStyle({ prop: 'color', key: 'colors', // theme key })
export const bgColor = responsiveStyle({ prop: 'bg', cssProperty: 'backgroundColor', key: 'colors' })
Функция responsiveStyle
обрабатывает все точки останова, откаты и именование опор. Ниже я упростил код стилизованной системы для демонстрационных целей.
// <https://github.com/styled-system/styled-system/blob/v2.3.6/src/util.js> // I simplified this for demonstrative purposes
export const responsiveStyle = ({ prop, cssProperty, key, }) => { const fn = props => { cssProperty = cssProperty || prop const n = props[prop]; const theme = props.theme; // ... return { [cssProperty]: theme[key][n] } // ...
}
return fn }
Это можно представить примерно так:
const Button = styled.div`
{
${...(props) => (
{ color: props.theme['colors'][props.color] }
)}
${...(props) => (
{ backgroundColor: props.theme['colors'][props.bg] }
)}
}
`
Точки останова и адаптивные темы
Для адаптивных тем styled-system
позволяет установить breakpoints
в теме, а затем позволяет передавать массив в качестве опоры с разными значениями для каждой точки останова. styled-system
использует подход «сначала мобильные», поэтому первый индекс всегда будет наименьшей точкой останова.
<Box width={[ 1, // 100% below the smallest breakpoint (all viewports) 1 / 2, // 50% from the next breakpoint and up 1 / 4, // 25% from the next breakpoint and up ]} />
// theme.js const breakpoints = ['40em', '52em', '64em', '80em']
export default { breakpoints };
Заключение
Меня вдохновило то, как разработчики styled-components
и styled-system
использовали расширенную функциональность шаблонных литералов и тегированных шаблонов, чтобы предоставить пользователям интуитивно понятный способ добавления SCSS в компоненты React.
Видели ли вы в последнее время какое-нибудь интересное использование общих функций в своей работе? Поделись, пожалуйста!