Hello world!
По данным 2023 JavaScript Rising Stars библиотека StyleX заняла второе место в разделе Styling / CSS in JS (первое место вполне ожидаемо занял TailwindCSS).
stylex — это решение CSS в JS от Facebook, которое недавно стало открытым и быстро набрало популярность (на сегодняшний день у библиотеки 7500 звезд на Github). Это обусловлено ее легковесностью, производительностью и небольшим размером итоговой таблицы стилей.
В этой статье мы разберемся, как stylex работает. Но давайте начнем с примера ее использования.
Код проекта, который мы создадим, включая выдержки из исходного кода stylex (директория stylex), можно найти здесь.
Пример
В качестве примера создадим простой компонент кнопки, предусматривающий разные варианты стилизации.
Создаем шаблон приложения с помощью Vite:
# stylex-testing - название проекта # react-ts - используемый шаблон yarn create vite stylex-testing --template react-ts # или npm create vite@latest stylex-testing -- --template react-ts
Переходим в директорию и устанавливаем зависимости:
cd stylex-testing yarn add @stylexjs/stylex yarn add -D vite-plugin-stylex-dev # или npm i @stylexjs/stylex npm i -D vite-plugin-stylex-dev
vite-plugin-stylex-dev — неофициальный плагин stylex для vite.
Редактируем файл vite.config.ts:
import react from '@vitejs/plugin-react' import { defineConfig } from 'vite' import { stylexPlugin } from 'vite-plugin-stylex-dev' export default defineConfig({ plugins: [react(), stylexPlugin()], })
Определяем минимальные стили в файле index.css:
html, body, #root { height: 100%; } body { margin: 0; }
Переходим к stylex.
Начнем с определения переменных/токенов. Создаем файл tokens.stylex.ts следующего содержания:
import stylex from '@stylexjs/stylex' // Медиа-запрос темной цветовой схемы const DARK = '@media (prefers-color-scheme: dark)' // Цвета export const colors = stylex.defineVars({ light: { default: '#FBFBFB', [DARK]: '#332D2D' }, dark: { default: '#332D2D', [DARK]: '#FBFBFB' }, primary: '#3B71CA', success: '#14A44D', }) // Отступы export const spacing = stylex.defineVars({ none: '0px', xsmall: '4px', small: '8px', medium: '12px', large: '16px', })
Создаем файл components/Button.tsx для компонента кнопки. Определяем стили кнопки:
import stylex, { type StyleXStyles } from '@stylexjs/stylex' import type { HTMLAttributes, PropsWithChildren } from 'react' import { colors, spacing } from '../tokens.stylex' // 3 варианта стилизации const styles = stylex.create({ default: { // Используем переменные // https://stylexjs.com/docs/learn/theming/using-variables/ padding: `${spacing.small} ${spacing.large}`, backgroundColor: colors.light, border: `1px solid ${colors.dark}`, outline: 'none', borderRadius: spacing.small, boxShadow: { default: '0 2px 3px rgba(0, 0, 0, 0.25)', ':active': 'none', }, cursor: 'pointer', transition: 'all 0.25s ease-in-out', ':hover': { backgroundColor: colors.dark, color: colors.light, }, }, primary: { backgroundColor: colors.primary, color: colors.light, ':hover': { backgroundColor: null, color: null, }, }, dark: { backgroundColor: colors.dark, color: colors.light, ':hover': { backgroundColor: colors.light, color: colors.dark, }, }, })
Определение стилей с помощью stylex сильно напоминает то, как это делается в React Native.
Определяем типы пропов и компонент кнопки:
type Props = PropsWithChildren< HTMLAttributes<HTMLButtonElement> & { // Вариант перезаписывает стилизацию `default` variant?: 'primary' | 'dark' // Кастомные цвета фона и текста, определенные с помощью `stylex` // С точки зрения типизации стилей `stylex` лучше `tailwindcss` customStyles?: StyleXStyles<{ backgroundColor?: string color?: string }> } > export default function Button({ children, variant, customStyles, ...rest }: Props) { return ( <button // Применяем стили // https://stylexjs.com/docs/learn/styling-ui/using-styles/ {...stylex.props( styles.default, variant && styles[variant], customStyles, )} {...rest} > {children} </button> ) }
Определяем стили контейнера и кастомной кнопки и рендерим несколько кнопок в файле App.tsx:
import stylex from '@stylexjs/stylex' import Button from './components/Button' import { colors, spacing } from './tokens.stylex' // Стили контейнера и кастомной кнопки const styles = stylex.create({ app: { width: '100%', height: '100%', display: 'flex', justifyContent: 'center', alignItems: 'center', gap: spacing.medium, }, customButton: { backgroundColor: colors.success, color: colors.light, }, }) export default function App() { return ( <div {...stylex.props(styles.app)}> <Button>Default</Button> <Button variant='dark'>Dark</Button> <Button variant='primary'>Primary</Button> <Button customStyles={styles.customButton}> Custom </Button> </div> ) }
Запускаем сервер для разработки:
yarn dev # или npm run dev
Результат:
Это почти весь функционал, предоставляемый stylex. Мы не рассмотрели только создание тем путем перезаписи переменных, но, думаю, вы сами легко с этим разберетесь.
Давайте взглянем на стили и пропы, генерируемые stylex. Редактируем App.tsx следующим образом:
import stylex from '@stylexjs/stylex' const stylesForLog = stylex.create({ root: { display: 'flex', flexDirection: 'column', }, }) const stylesForLog2 = stylex.create({ root2: { display: 'flex', }, }) // Утилита для красивого вывода const stringify = (obj: Record<string, unknown>) => JSON.stringify(obj, null, 2) export default function App() { console.log('result of create:', stringify(stylesForLog)) console.log('result of create 2:', stringify(stylesForLog2)) console.log(stylesForLog.root.display === stylesForLog2.root2.display) console.log( 'result of props:', stringify(stylex.props(stylesForLog.root, stylesForLog2.root2)), ) console.log( 'result of props 2:', stringify( // @ts-ignore stylex.props(stylesForLog.root, stylesForLog2.root2, { display: 'flex' }), ), ) return ( <div {...stylex.props(stylesForLog.root)}></div> ) }
Вот что мы видим в консоли:
Строки типа App__stylesForLog.root нужны для отладки, поэтому их можно игнорировать.
display: "flex" превратилось в display: "x78zum5", причем поле display в обоих случаях имеет одно и тоже значение, что подтверждается сравнением stylesForLog.root.display === stylesForLog2.root2.display. Это наводит на мысль о том, что строки типа x78zum5 — это хеш правил CSS (свойство: значение).
Значения полей объекта styles объединяются и становятся значением поля className объекта props (например, className: "xdt5ytf x78zum5"). className путем распаковки объекта props передается компоненту (устанавливается элементу button).
При передаче display: flex в виде простого объекта возвращается поле style со значением в виде этого объекта. При этом, соответствующий хеш-класс опускается.
Запомните поле $$css: true, мы вернемся к нему позже.
Взглянем на разметку:
В таблице стилей с data-vite-dev-id=" vite-plugin:stylex.css" мы видим «хеш-классы» и соответствующие им стили: .x78zum5{display:flex}.xdt5ytf{flex-direction:column}. Те же хеш-классы мы видим у контейнера: <div class="x78zum5 xdt5ytf"></div>.
Выполняем сборку приложения:
yarn build # или npm run build
Открываем файл dist/assets/index-[hash].css:
/* stylex */ .x78zum5 { display: flex; } .xdt5ytf { flex-direction: column; } /* index.css */ html, body, #root { height: 100%; } body { margin: 0; }
Отлично. Начнем погружаться в исходный код stylex.
Реверс-инжиниринг
Обратите внимание: дальнейший разбор кода актуален для stylex@0.4.1. В будущем код может и наверняка изменится, возможно, до неузнаваемости ?
Также обратите внимание, что с целью упрощения кода для облегчения его восприятия я беспощадно удалял строки и даже целые блоки кода ?
Мы ограничимся изучением работы методов create (создание стилей) и props (применение стилей).
Выдержки из кода соответствуют порядку применения переменных и функций, а не порядку их определения.
Начнем с файла packages/stylex/src/stylex.js:
// 294 - номер строки кода export default _stylex // 239 function _stylex(...styles) { const [className] = styleq(styles) return className } _stylex.props = props _stylex.create = create // 36 // Об этой библиотеке поговорим позже import { styleq } from 'styleq' // 136 export const create = stylexCreate // 76 function stylexCreate(styles) { if (__implementations.create != null) { const create = __implementations.create return create(styles) } throw new Error( 'stylex.create should never be called. It should be compiled away.', ) } // 280 const __implementations = {} // 282 export function __monkey_patch__(key, implementation) { __implementations[key] = implementation }
Метод create извлекается из объекта __implementations, который инициализируется с помощью функции __monkey_patch__.
Следуем за __monkey_patch__() в файл packages/dev-runtime/src/index.js:
// 10 import { __monkey_patch__ } from '@stylexjs/stylex' import { styleSheet } from '@stylexjs/stylex/lib/StyleXSheet'; // 20 import getStyleXCreate from './stylex-create'; // 45 export default function inject({ insert = defaultInsert, ...config }) { // Инициализация `create()` __monkey_patch__('create', getStyleXCreate({ ...config, insert })); } // 24 const defaultInsert = ( key, ltrRule, priority, // На это можно не обращать особого внимания, // это стили для направления текста "справа налево" rtlRule, ) => { if (priority === 0) { if (injectedVariableObjs.has(key)) { throw new Error('A VarGroup with this name already exists: ' + key); } else { injectedVariableObjs.add(key); } } styleSheet.insert(ltrRule, priority, rtlRule); }; // 22 const injectedVariableObjs = new Set();
Метод create — это функция, возвращаемая getStyleXCreate({ ...config, defaultInsert }).
В функции defaultInsert вызывается метод insert объекта styleSheet. Этот объект создается в файле packages/stylex/src/StyleXSheet.js:
// 364 export const styleSheet = new StyleXSheet({ supportsVariables: true, rootTheme: {}, rootDarkTheme: {}, }); // 85 export class StyleXSheet { static LIGHT_MODE_CLASS_NAME = LIGHT_MODE_CLASS_NAME; static DARK_MODE_CLASS_NAME = DARK_MODE_CLASS_NAME; constructor(opts) { this.tag = null; this.injected = false; this.ruleForPriority = new Map(); this.rules = []; this.rootTheme = opts.rootTheme; this.rootDarkTheme = opts.rootDarkTheme; } // Цветовые схемы rootTheme; rootDarkTheme; // Массив, содержащий все добавленные правила. Используется для // отслеживания индексов правил в таблице стилей rules; // Индикатор добавления тега `style` в `document` injected; // Элемент `style` для добавления правил tag; // Для поддержки приоритетов необходимо хранить правило, // которое находится в начале приоритета ruleForPriority; /** * Извлекает тег `style` */ getTag() { const { tag } = this; invariant(tag != null, 'expected tag'); return tag; } /** * Добавляет тег `style` в `head` */ inject() { if (this.injected) { return; } this.injected = true; // Создаем тег `style` this.tag = makeStyleTag(); this.injectTheme(); } /** * Вставляет стили темы - переменные/токены */ injectTheme() { if (this.rootTheme != null) { this.insert( buildTheme(`:root, .${LIGHT_MODE_CLASS_NAME}`, this.rootTheme), 0, ); } if (this.rootDarkTheme != null) { this.insert( buildTheme( `.${DARK_MODE_CLASS_NAME}:root, .${DARK_MODE_CLASS_NAME}`, this.rootDarkTheme, ), 0, ); } } /** * Добавляет правила в таблицу стилей */ insert(rawLTRRule, priority) { // Добавляем таблицу стилей при отсутствии if (this.injected === false) { this.inject(); } const rawRule = rawLTRRule; // Не добавляем правило при наличии (исключаем дубликаты) if (this.rules.includes(rawRule)) { return; } // Нормализованное правило с определенной специфичностью, // это нас не интересует const rule = this.normalizeRule( addSpecificityLevel(rawRule, Math.floor(priority / 1000)), ); // Получаем позицию для вставки правила по его приоритету, // это нас не интересует const insertPos = this.getInsertPositionForPriority(priority); this.rules.splice(insertPos, 0, rule); // Устанавливаем правило как конец группы приоритета this.ruleForPriority.set(priority, rule); const tag = this.getTag(); const sheet = tag.sheet; if (sheet != null) { try { // https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet/insertRule sheet.insertRule(rule, Math.min(insertPos, sheet.cssRules.length)); } catch (err) { console.error('insertRule error', err, rule, insertPos); } } } } // 344 /** * Функция добавления `:not(#\#)` для повышения специфичности; полифилл для @layer */ function addSpecificityLevel(selector, index) { if (selector.startsWith('@keyframes')) { return selector; } // Чем больше `not(#\\#)`, тем выше специфичность const pseudo = Array.from({ length: index }) .map(() => ':not(#\\#)') .join(''); const lastOpenCurly = selector.includes('::') ? selector.indexOf('::') : selector.lastIndexOf('{'); const beforeCurly = selector.slice(0, lastOpenCurly); const afterCurly = selector.slice(lastOpenCurly); return `${beforeCurly}${pseudo}${afterCurly}`; } // 45 /** * Создает тег `style` и добавляет его в `head` */ function makeStyleTag() { const tag = document.createElement('style'); tag.setAttribute('type', 'text/css'); tag.setAttribute('data-stylex', 'true'); const head = document.head || document.getElementsByTagName('head')[0]; invariant(head, 'expected head'); head.appendChild(tag); return tag; } // 28 /** * Принимает тему и генерирует переменные CSS */ function buildTheme(selector, theme) { const lines = []; lines.push(`${selector} {`); for (const key in theme) { const value = theme[key]; lines.push(` --${key}: ${value};`); } lines.push('}'); return lines.join('\n'); }
Здесь основной является строка sheet.insertRule(rule, position);, отвечающая за однократное добавление переданного правила в таблицу стилей с учетом его приоритета.
Возвращаемся в файл packages/dev-runtime/src/index.js и следуем за getStyleXCreate в файл packages/dev-runtime/src/stylex-create.js:
// 177 export default function getStyleXCreate( config, ) { const stylexCreate = ( styles, ) => { return createWithFns(styles, config); }; return stylexCreate; } // 106 // Значением поля объекта стилей может быть функция, // мы не будем рассматривать такой вариант function createWithFns( styles, { insert, ...config }, ) { const stylesWithoutFns = {}; for (const key in styles) { const value = styles[key]; stylesWithoutFns[key] = value; } // Одна из самых важных строк! const [compiledStyles, injectedStyles] = create(stylesWithoutFns, config); // Добавляем хеш-классы и стили в таблицу стилей for (const key in injectedStyles) { const { ltr, priority, rtl } = injectedStyles[key]; insert(key, ltr, priority, rtl); } const temp = compiledStyles; const finalStyles = { ...temp }; // Возвращаем скомпилированные стили return finalStyles; } // 16 import { create, IncludedStyles, utils } from '@stylexjs/shared';
Функция getStyleXCreate возвращает функцию stylexCreate, которая возвращает функцию createWithFns, которая вызывает функцию create, добавляет одни стили (injectedStyles) в таблицу стилей и возвращает другие (finalStyles). finalStyles — это стили, которые впоследствии передаются в метод stylex.props, которая находится в файле packages/stylex/src/stylex.js:
// 47 export function props( this, ...styles ) { const options = this; if (__implementations.props) { return __implementations.props.call(options, styles); } // Мы подробнее поговорим об этой библиотеке в конце const [className, style] = styleq(styles); const result = {}; if (className != null && className !== '') { result.className = className; } if (style != null && Object.keys(style).length > 0) { result.style = style; } // result = { className: "хеш-классы", style: { ...стили } } return result; }
Следуем за функцией create в файл packages/shared/src/index.js:
// 43 export const create = styleXCreateSet; // 23 import styleXCreateSet from './stylex-create';
Без комментариев ? Следуем за функцией styleXCreateSet в файл packages/shared/src/stylex-create.js:
// 24 // Эта функция принимает объект со стилями, передаваемый в метод `stylex.create` и преобразует его. // Преобразование заключается в замене значений стилей на названия CSS-классов. // // Функция также собирает все внедряемые (injected) стили. // Возвращает кортеж с преобразованным объектом стилей и объектом внедряемых стилей. // // Перед возвратом выполняется проверка отсутствия дубликатов во внедряемых стилях. export default function styleXCreateSet( namespaces, options = defaultOptions, ) { const resolvedNamespaces = {}; const injectedStyles = {}; for (const namespaceName of Object.keys(namespaces)) { const namespace = namespaces[namespaceName]; const flattenedNamespace = flattenRawStyleObject(namespace, options); const compiledNamespaceTuples = flattenedNamespace.map(([key, value]) => { return [key, value.compiled(options)]; }); const compiledNamespace = objFromEntries(compiledNamespaceTuples); const namespaceObj = {}; for (const key of Object.keys(compiledNamespace)) { const value = compiledNamespace[key]; if (value instanceof IncludedStyles) { namespaceObj[key] = value; } else { const classNameTuples = value.map((v) => (Array.isArray(v) ? v : null)).filter(Boolean); const className = classNameTuples.map(([className]) => className).join(' ') || null; namespaceObj[key] = className; for (const [className, injectable] of classNameTuples) { if (injectedStyles[className] == null) { injectedStyles[className] = injectable; } } } } // `$$css: true` требуется для `styleq` resolvedNamespaces[namespaceName] = { ...namespaceObj, $$css: true }; } return [resolvedNamespaces, injectedStyles]; }
Весь рассмотренный код можно найти в файле stylex/source-code.js нашего проекта.
Код функции styleXCreateSet и всех используемых ей утилит занимает почти 800 строк. Я вынес его в отдельный файл stylex/create-props.js. Вы можете внимательно изучить его самостоятельно, я же остановлюсь только на основных моментах.
Посмотрим на результат, возвращаемый функцией styleXCreateSet:
const [compiledStyles, injectedStyles] = styleXCreateSet({ root: { display: 'flex', flexDirection: 'column', }, }) console.log(stringify({ compiledStyles, injectedStyles }))
Результат:
{ "compiledStyles": { "root": { "display": "x78zum5", "flexDirection": "xdt5ytf", "$$css": true } }, "injectedStyles": { "x78zum5": { "priority": 3000, "ltr": ".x78zum5{display:flex}", "rtl": null }, "xdt5ytf": { "priority": 3000, "ltr": ".xdt5ytf{flex-direction:column}", "rtl": null } } }
injectedStyles добавляются в таблицу стилей через метод styleSheet.insert. compiledStyles пропускаются через библиотеку styleq и передаются компоненту.
Вызовем функцию props:
const result = props(compiledStyles.root) console.log(stringify({ result })) const result2 = props(compiledStyles.root, { display: 'flex' }) console.log(stringify({ result2 }))
Результат:
{ "result": { "className": "x78zum5 xdt5ytf" } } { "result2": { // Обратите внимание на отсутствие хеш-класса для `display: flex` "className": "xdt5ytf", "style": { "display": "flex" } } }
Посмотрим на цепочку преобразований такого объекта:
{ root: { display: 'flex', }, }
в такие:
{ "compiledStyles": { "root": { "display": "x78zum5", "$$css": true } }, "injectedStyles": { "x78zum5": { "priority": 3000, "ltr": ".x78zum5{display:flex}", "rtl": null } } }
function styleXCreateSet(namespaces, options = defaultOptions) { const resolvedNamespaces = {} const injectedStyles = {} for (const namespaceName of Object.keys(namespaces)) { const namespace = namespaces[namespaceName] // ! console.log(stringify({ namespace })) const flattenedNamespace = flattenRawStyleObject(namespace, options) // ! console.log(stringify({ flattenedNamespace })) const compiledNamespaceTuples = flattenedNamespace.map(([key, value]) => { return [key, value.compiled(options)] }) // ! console.log(stringify({ compiledNamespaceTuples })) const compiledNamespace = objFromEntries(compiledNamespaceTuples) // ! console.log(stringify({ compiledNamespace })) const namespaceObj = {} for (const key of Object.keys(compiledNamespace)) { const value = compiledNamespace[key] const classNameTuples = value .map((v) => (Array.isArray(v) ? v : null)) .filter(Boolean) const className = classNameTuples.map(([className]) => className).join(' ') || null namespaceObj[key] = className for (const [className, injectable] of classNameTuples) { if (injectedStyles[className] == null) { injectedStyles[className] = injectable } } } resolvedNamespaces[namespaceName] = { ...namespaceObj, $$css: true } } return [resolvedNamespaces, injectedStyles] }
Результат:
{ "namespace": { "display": "flex" } } { "flattenedNamespace": [ [ "display", { "property": "display", "value": "flex", "pseudos": [], "atRules": [] } ] ] } { "compiledNamespaceTuples": [ [ "display", [ [ "x78zum5", { "priority": 3000, "ltr": ".x78zum5{display:flex}", "rtl": null } ] ] ] ] } { "compiledNamespace": { "display": [ [ "x78zum5", { "priority": 3000, "ltr": ".x78zum5{display:flex}", "rtl": null } ] ] } }
За генерацию хеша на основе правила CSS отвечает функция convertStyleToClassName:
function convertStyleToClassName( objEntry, pseudos, atRules, options = defaultOptions, ) { // ! console.log(stringify({ objEntry, pseudos, atRules })) const { classNamePrefix = 'x' } = options const [key, rawValue] = objEntry const dashedKey = dashify(key) const value = Array.isArray(rawValue) ? rawValue.map((eachValue) => transformValue(key, eachValue, options)) : transformValue(key, rawValue, options) const sortedPseudos = arraySort(pseudos ?? []) const sortedAtRules = arraySort(atRules ?? []) const atRuleHashString = sortedPseudos.join('') const pseudoHashString = sortedAtRules.join('') const modifierHashString = atRuleHashString + pseudoHashString || 'null' const stringToHash = Array.isArray(value) ? dashedKey + value.join(', ') + modifierHashString : dashedKey + value + modifierHashString // ! console.log(stringify({ dashedKey, value, modifierHashString, stringToHash })) // Обратите внимание: `<>` используется для обеспечения стабильности хешей. // Это должно быть удалено в будущих версиях const className = classNamePrefix + createHash('<>' + stringToHash) const cssRules = generateRule(className, dashedKey, value, pseudos, atRules) // ! console.log(stringify({ key, className, cssRules })) return [key, className, cssRules] }
Результат:
{ "objEntry": [ "display", "flex" ], "pseudos": [], "atRules": [] } { "dashedKey": "display", "value": "flex", "modifierHashString": "null", // Строка для хеширования "stringToHash": "displayflexnull" } { "key": "display", // Результат хеширования "className": "x78zum5", "cssRules": { "priority": 3000, "ltr": ".x78zum5{display:flex}", "rtl": null } }
Строка "displayflexnull" преобразуется/хешируется в x78zum5
Настоящая магия происходит в функции createHash:
function createHash(str) { return murmurhash2_32_gc(str, 1).toString(36) } function murmurhash2_32_gc(str, seed = 0) { let l = str.length, h = seed ^ l, i = 0, k while (l >= 4) { k = (str.charCodeAt(i) & 0xff) | ((str.charCodeAt(++i) & 0xff) << 8) | ((str.charCodeAt(++i) & 0xff) << 16) | ((str.charCodeAt(++i) & 0xff) << 24) k = (k & 0xffff) * 0x5bd1e995 + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16) k ^= k >>> 24 k = (k & 0xffff) * 0x5bd1e995 + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16) h = ((h & 0xffff) * 0x5bd1e995 + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16)) ^ k l -= 4 ++i } switch (l) { case 3: h ^= (str.charCodeAt(i + 2) & 0xff) << 16 case 2: h ^= (str.charCodeAt(i + 1) & 0xff) << 8 case 1: h ^= str.charCodeAt(i) & 0xff h = (h & 0xffff) * 0x5bd1e995 + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16) } h ^= h >>> 13 h = (h & 0xffff) * 0x5bd1e995 + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16) h ^= h >>> 15 return h >>> 0 }
MurmurHash2 — это простая и быстрая хеш-функция общего назначения, разработанная Остином Эпплби. Она не является криптографически-безопасной и возвращает 32-разрядное беззнаковое число.
В заключение, поговорим о функции styleq, которая вызывается в функции props:
const [className, style] = styleq(styles);
styleq — это быстрая и небольшая среда выполнения JavaScript для объединения названий классов HTML, созданных компиляторами CSS. Вызов styleq(...styles) объединяет объекты стилей и генерирует строку className и объект встроенных (inline) стилей (с названиями свойств в стиле camelCase):
const [className, inlineStyle] = styleq(styles.root, { opacity });
Функция styleq эффективно объединяет глубоко вложенные массивы как извлеченных (extracted), так и встроенных объектов стилей:
- компилируемые стили должны содержать свойство
$$cssсо значениемtrue(помнитеresolvedNamespaces[namespaceName] = { ...namespaceObj, $$css: true }?) - объект стилей без свойства
$$cssсчитается встраиваемым стилем - компилируемые стили должны быть статичными для лучшей производительности
- ключи объектов компилируемых стилей не обязательно должны совпадать с названиями свойств
CSS; разрешена любая строка - значения компилируемого объекта стилей должны быть строками классов
HTML
const styles = { root: { // Обязательное поле $$css: true, // Классы для отладки 'debug::file:styles.root': 'debug::file:styles.root', // Атомарные классы display: 'display-flex-class', alignItems: 'alignItems-center-class' } }; const [className, inlineStyle] = styleq(styles.root, props.style);
Таким образом, styleq обеспечивает эффективное объединение хеш-классов в одну строку className без дублирования и с учетом встроенных стилей, которые возвращаются в виде объекта style. className и style возвращаются в виде, пригодном для прямой передачи компоненту React для установки элементу HTML в качестве соответствующих атрибутов.
Пожалуй, это все, о чем я хотел рассказать вам в этой статье.
Happy coding!
Новости, обзоры продуктов и конкурсы от команды Timeweb.Cloud — в нашем Telegram-канале ↩
ссылка на оригинал статьи https://habr.com/ru/articles/795473/

Добавить комментарий