Как стать автором
Обновить

Комментарии 73

«Особенно — в мире React.»
Хорошо бы, чтоб этот костыль там и оставался.

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

Возможно, вы удивитесь, но CSS-in-JS используется даже в самом абсолютном воплощении веб-стандартов – в веб-копонентном фреймворке lit-element: https://lit.dev/docs/components/styles/

Так уж оказалось, что сочетание стилей и остального кода компонента в одом месте – это очень практично.

Так уж оказалось, что сочетание стилей и остального кода компонента в одом месте – это очень практично.


Спорное субьективное утверждение. Мне, например, это максимально непрактично.
Это всегда воспринималось как антишаблон и большие компании всегда пытались разделять представление от реализации (даже если это css, html и JavaScript). Да про что говорить, если бы «всё в одом месте» было хоть как-то практично, то до сих пор верстали бы с использованием style атрибута, но к счастью этого не происходит и никто не жалуется.

Тот же компонент тоже можно поставлять как сборку из нескольких файлов, никакой проблемы с этим вообще нет.

По моему скромному мнению такой подход просто временное решение каких-то возникших проблем с незрелыми технологиями, что мы имеем сейчас. Это никто и никогда не потянет в стандарты.

Спорное – это на ваш взгляд. С точки зрения фреймворков видно консенсус, что стили принадлежат файлу с компонентом. Это происходит не только в Реакте, но и во Vue, Svelte и LitElement.

Ну по крайней мере во Vue это не обязательно. Все проекты, которые я видел и команды с которыми работал рано или поздно приходят к хранению стилей отдельно от компонентов.
Поддержу. Так же во Vue стили загнаны в отдельную секцию вне JS, а не как строки в объекте.

Добавлю еще про "никто и никогда не потянет в стандарты". Гугл как раз активно продвигает такой стандарт: https://github.com/WICG/construct-stylesheets

Я понимаю откуда происходит ваше неприятие этого подхода. 10 лет назад всем внушили концепт разделения сущностей, и многие поняли его слишком буквально. С тех пор концепции поменялись, и то что работало для домашних страничек-визиток, уже не работает для веб-приложений.

Я перечислил очевидные минусы.
В чем плюс?

Самый главный плюс – скрываются детали реализации. Пользователи работают с компонентами, как с черными ящиками, только посредством публичного API. В случае отдельного CSS он топорщится наружу и нарушает принцип черного ящика.

Второй плюс – в читаемости. Никто уже не пишет CSS вручную, вместо этого пользуются препроцессорами вроде SASS. У них свой особенный синтаксис, который нужно учить. Так почему бы не использовать для этого уже знакомый JS? На выходе будет все тот же CSS, со всеми его возможностями.

Третий – производительность. В случае CSS вы загружаете все сразу, даже для неиспользуемых сейчас компонентов. CSS-in-JS дает вам самораспаковывающийся CSS, который добавляется в документ только когда он нужен (а также динамически удаляется, когда нет).

Так что у вас там с минусами? ;) Я увидел только ваше субъективное мнение, ничего конструктивного.

  • Первый не является плюсом и я выше обьяснил почему. Вы можете запаковать несколько файлов как сборку, это успешно реализовано во всех языках практически с их появления и это никак не будет отличается от «один файл». Кроме того разбивание на мелкие файлы имеет преимущество контроля изменения, мерджа в GIT и много других плюсов.
  • «В читаемости» Субьективно вы говорите, для меня читаемость значительно ухудшается, CSS специализированный язык и он хорошо подходит для решения своих задач без всяких библиотек и тем более без ужасной реализации в JS, где потом любой линтер сходит с ума.
  • Выше привели вам пример, что производительность ухудшается и это понятно почему. Вы имели ввиду размер пакета, но даже это успешно решается, простым контролем и линтированием.

Вроде вы все складно объясняете, но это работатет только для небольших проектов с десятком файлов. В больших масштабах "простым контролем" уже не обойтись. С каким самым большим проектом вам доводилось иметь дело и сколько было человек в команде?

Вот например @Fi1osof ниже приводит пример СберТеха, где тоже выбрали Styled-Components и оно примерно понятно почему.

Мне кажется, вы на это смотрите только в разрезе React. Там из-за необдуманой архитектуры самого фреймворка это в какой-то мере необходимо. Но также добавляет большие проблемы, в тех же больших проектах существуют коорпоративные стандарты линтирования, покрытия тестами и написанию кода, например, с репортами в SonarQubе. Те же линтеры не успевают дописывать за всеми библиотеками и нужно очень постараться, чтоб привести styles в JS к JavaScript\TypeScript подобному виду и правилам.
ИМХО, описывать стили в JS — это как стрелять из пушки по воробьям. И сейчас чистая спекуляция от меня — она не получит дальнейшего развития и всегда будет на третьих рядах после «нативный СSS» или «компиляция SASS подобных языков в СSS».

О каком разрезе React вы говорите? Я же уже приводил примеры других фреймворков выше.

SonarQube не решает обозначенные мной проблемы примерно никак, он только проверяет корректность CSS-синтаксиса, никаих более продвинутых правил, вроде обнаружения неиспользуемого кода, там нет.

не получит дальнейшего развития и всегда будет на третьих рядах

Примерно так же думал и я, в 2016 году. Но сейчас уже 2021й, хорошие практики настоялись, технологии вышли на плато продуктивности, и говорить об их непригодности – это чистое ретроградство.

Тоже побуду ретроградом. Не понимаю чем вам нравится CSS-in-JS. Большую часть проблем решает CSS-modules.


  • не нужно мучить себе голову уникальными селекторами
  • на выходе получаете статичные CSS-файлы (SSR, кеширование, линтеры, да даже разбор полётов удобнее)
  • нет проблем с неиспользуемым CSS-кодом
  • удобный синтаксис (SCSS/SASS/whatever), а не интерполяция в JS
  • отсутствие динамических стилей. Наверное самый жирный плюс, именно плюс, а не минус. Особенно для огромных приложений
  • удобно организовывать стили не 1-компонент=1-стиль, а один файл стилей на связанные визуально компоненты
  • отсутствие CSS мусора в JS коде

Учитывая как далеко скакнул CSS за последние лет 5-7, почти пропала необходимость подстраивать вёрстку под нужды стилизации. Стало меньше всяких wrapper-ов, и размещения блоков не исходя из логики, а исходя из визуальной составляющей. Поэтому держать стили отдельно от действительно сложных вещей (т.е. кода тех самых компонент) — очень удобно. Плюс, вот сколько я не верстал за последние лет 12, всегда было удобнее видеть всё картинку стилей в целом разом (т.е. открыть SCSS файл). Сам подход над компонентами был (и остался) — пишем data flow, пишем поведение, пишем стили. Зачем это всё перемешивать?


И чего лишаются пользователи CSS-modules в сравнении с CSS-in-JS? Ну по сути возможности засунуть какой-нибудь метод-генератор CSS, чего я бы рекомендовал настоятельно избегать пока на это нет очень весомой причины.


Мне кажется 2021г тут никакой роли не играет.

Для контекста, в начале 2020го в нашей команде были дебаты, что взять для новой версии нашей UI-библиотеки. После долгих дебатов победили CSS-модули, о чем мы сейчас немножечко жалеем.

Во-первых нет единого стандарта, как публиковать CSS-модули в npm. Для библиотеки нужно писать гайдлайн как правильно конфигурировать webpack, css-loader и иже с ними. А есть еще и другие бандлеры, а еще нужно Jest настроить... В CSS-in-JS все просто работает из коробки.

Во-вторых в более-менее сложной дизайн системе нужно генерировать стили (для разных варианов компонента). Циклы на SASS выглядят так громоздко, что нужно быть гуру SASS чтобы это разобрать (не говоря о том, чтобы это кто-то тестировал). На JS/TS это получается в разы читаемее.

В-третьих, темизация. На одних css-переменных далеко не уедешь, потому что бывают составные свойства (border + box-shadow или color + background-color) с правильным соотношением между ними. С помощью css-ного calc такое не выразить.

В-четвертых, переиспользование токенов между CSS и JS. Чтобы прочесть css-переменную, нужен доступ к DOM, а современные фреймворки его тщательно оборачивают. Гораздо проще настроить переиспользование в обратную сторону, то есть CSS-in-JS

Теперь пройдемся по вашим аргументам

на выходе получаете статичные CSS-файлы (SSR, кеширование, линтеры, да даже разбор полётов удобнее)

Тут то же самое, в статье показывается пример linaria, но можно и с другими. Большинство задач линтера прекрасно покрывается тайпскриптом и csstype, современные решения дают это из коробки.

удобный синтаксис (SCSS/SASS/whatever), а не интерполяция в JS

Это про styled-components с их строковым синтаксисом, как я понимаю. Я тоже это не поддерживаю, и к счастью, есть много решений с объектным синтаксисом, например JSS. Там все удобнее некуда. Про неудобство циклов/условий на SASS уже писал выше.

удобно организовывать стили не 1-компонент=1-стиль, а один файл стилей на связанные визуально компоненты

Никто не мешает вам сделать отдельный файл styles.ts, и организовывать стили в нем точно так же.

Остальные аргументы слишком субъективные, не знаю как на них ответить, кроме "а нам норм"

Циклы на SASS

А для чего вам потребовались циклы? Последний раз ими пользовался лет 5 назад. Нужно было для каждой иконки создать по стилю. С тех пор ни разу не пригодились. Большая часть "динамики" элементарно (и нативно!) решается через css-variables. Никакой подобной генерации CSS не используем ни в одном из проектов. Хотя тоже своя палитра компонентов с неадекватными требованиями по стилизации.


и к счастью, есть много решений с объектным синтаксисом, например JSS. Там все удобнее некуда

Это была шутка, да?


Вот это нечто
const styles = {
  '@global': {
    body: {
      color: 'green'
    },
    a: {
      textDecoration: 'underline'
    }
  },
  withTemplates: `
    border-radius: 3px;
    background-color: green;
    color: red;
    margin: 20px 40px;
    padding: 10px;
  `,
  button: {
    fontSize: 12,
    '&:hover': {
      background: 'blue'
    }
  },
  ctaButton: {
    extend: 'button',
    '&:hover': {
      background: color('blue')
        .darken(0.3)
        .hex()
    }
  },
  '@media (min-width: 1024px)': {
    button: {
      width: 200
    }
  }
}

это читаемо и удобно? seriously? camelCase, кавычки, объектная нотация, интерполяция, extend-ы и прочее?


Я не хочу сказать что это настолько ужасно что кушать не могу, но откровенно говоря глядя на такое мне ну совсем не хочется слазить с иглы мужско... scss.


Никто не мешает вам сделать отдельный файл styles.ts, и организовывать стили в нем точно так же.

Да. Но зачем если я могу назвать его style.mod.scss и выкинуть оттуда TypeScript? Ещё я могу в себя иголкой тыкать. Но не вижу смысла. В чём benefits?


P.S. мне кажется что css-modules решает 90% проблем которые решают CSS-in-JS, но делают это лучше

В-третьих, темизация. На одних css-переменных далеко не уедешь, потому что бывают составные свойства (border + box-shadow или color + background-color) с правильным соотношением между ними. С помощью css-ного calc такое не выразить.

Пропустил этот абзац. Да, это вот случай под dynamic css. Имхо для этого не нужен целый css-in-js фреймворк. У нас такой потребности не возникло ни разу, но были другие моменты, где нужно было генерировать стили в runtime. Чаще всего это стили позиционирования. Мне кажется это просто замечательно когда такая дичь специфика сделана ЯВНЫМ ОБРАЗОМ, чтобы это бросалось в глаза. Но понимаю что вопрос холиварный

составные свойства (border + box-shadow или color + background-color) с правильным соотношением между ними

Немного оффтопа. Кажется это плохая идея делать такие взаимосвязи на уровне формул. Из-за того что человеческое цветовосприятие очень плохо ложится на простые математические зависимости. Нельзя, условно, взять "{цвет фона кнопки}" + "20% затемнить" и получить хороший результат. Т.к. эти 20% в зависимости от положения на спектре и, вероятно, других параметров, будет сильно отличатся визуально. То как наш мозг это интерпретирует не совпадёт с тем что задумывал погроммист. Были вроде даже какие-то материалы на эту тему на хабре. Всё что касается цветов и их восприятия это бездонная сложная тема, не проще чем "время" :)


Но я понимаю, что это не нам решать и к обсуждаемой теме относится очень косвенно. Просто вспомнилось.

SonarQube не решает обозначенные мной проблемы примерно никак, он только проверяет корректность CSS-синтаксиса, никаих более продвинутых правил, вроде обнаружения неиспользуемого кода, там нет.


Я не писал, что он что-то решает, наоборот добавляет проблем, но является стандартом для некоторых компаний. Просто линтеры в целом сходят с ума от CSS в JavaScript.

Примерно так же думал и я, в 2016 году. Но сейчас уже 2021й, хорошие практики настоялись, технологии вышли на плато продуктивности, и говорить об их непригодности – это чистое ретроградство.


Именно, CSS-синтаксис подтянут, зарефакторят некоторые фрейморки и отпадет необходимость забивать гвозди пасатижами.
Это не ретроградство, CSS-in-JS, ИМХО, это тупиковая ветка развития технологии и выше я писал почему так считаю.

В случае отдельного CSS он топорщится наружу и нарушает принцип черного ящика.

можно пару вопросов для саморазвития? сейчас в html есть некие теневые элементы с собственнымы стилями которые не как не смогут пересечься с глобальными, их можно использовать для этого?

Да, можно использовать и ShadowDOM для этого. Но прикол в том, что для стили внутрь ShadowDOM обычно загружаются через тот самый пресловутый CSS-in-JS, про который тут говорят, что он "не нужон" :)

Вы меня запутали еще больше, если есть изолированный кусок html то как к нему привязываеться css-in-js?

Дело в том, что этот изолированный кусок html конструируется в рантайме, то есть через javascript

Самый главный плюс – скрываются детали реализации. Пользователи работают с компонентами, как с черными ящиками, только посредством публичного API.

Сразу повеяло ActiveX'ом, Delphi и WinForms'ом. HTML (+ смежные технологии) потому и победил, что был лучше.
и большие компании всегда пытались разделять представление от реализации (даже если это css, html и JavaScript)

Я работал в СберТехе на проекте с 350 коллегами (не маленькая компания и не маленький проект). Не поверите, там тоже юзается SC.

А еще redux+saga? Собесился туда разок, показали кусок длинной функции с десятком yield put-call-takeLatest-takeEvery и попросили рассказать, что там происходит. Я сказал, что все там свистит-переливается и очень информативно, но мне срочно на другой собес надо) В такие проекты серьезные ребята все-таки не идут, по очень разнообразным причинам, и уверен, что SC внедряли не по трезвой голове.

Все-таки собесились туда? И по результатам собеса развернулись и ушли, и вердикт: «серьезные ребята все-таки не идут»? Такое… Думаю, личного опыта с порога маловато, чтобы решать ходят туда серьезные ребята или нет. Такое больше похоже на «я обиделся». Мое же мнение: там всяких хватает, но и серьезные там есть точно. А собеседование — это не отражение реальности проекта, это может быть просто проверка на спектр знаний.

И да, лично я не юзаю ни redux, ни saga. Всегда был против подобных компонентов.

Да, конечно, у вас больше экспертизы по бывшим коллегам — я видел только маленький кусок кода одного из многих проектов. Может и есть достаточно толковые проекты — сам работал в Домклике, там все очень пристойно было и по коду, и по процессам, и по коллегам. По крайней мере до определенного момента.


Но выбор SC однозначно делали "на попробовать хайповую штуку" без каких-либо трезвых аргументов, потому что после того, как поработаешь с несколькими реализациями, понимаешь, что этот подход некорректен в корне. Как и redux+saga. Есть намного более простые и эффективные решения.

Я для себя выбираю не просто хайповые вещи, а те, которые эффективно решают свои задачи и которые слишком объемные по своему коду, чтобы их можно было просто так быстро написать самому. И как по мне, redux не нужен совсем, его вполне заменяют нативные контексты реакта. А вот со стилями лично мне удобней именно SC. Вероятно, это потому что я не очень силен в SASS и т.п., то есть я не умею вот прям с нуля сразу заложить весь uikit и от него далее плясать по компонентам. Я чаще всего закладываю основу и потом постепенно наращиваю. И вот мне проще SC использовать, потому что у меня здесь типизация работает хорошо и проще покрывать cover-тестами. А все эти CSS-ы (пусть в less, пусть в sass, что угодно), я потом не понимаю что мне нужно после рефакторинга, а что нет. С SC у меня этой проблемы практически нет.

Понимаю, чтобы эффективно чем-то пользоваться, требуется этому сначала обучиться, "выстрадать", придумать десять подходов, прочитать о сотне, найти самое лучшее… В программировании так фактически с каждым инструментом.


Насчет того, что "типизация работает хорошо" — это вряд ли, для этого нужны другие инструменты (stylelint), которые проверяют корректность и параметров, и значений. За редким исключением в css-in-js такого вообще нет, либо проверяются только названия параметров. Но зато действительно проверяется "используемость стилевых классов" и после рефакторинга можно с помощью ESLint плагина


'import/no-unused-modules': ['warn', { unusedExports: true }]

увидеть неиспользуемые экспорты. Что такое cover-тест не знаю, Яндекс утверждает, что "тест на косоглазие", поэтому подтвержденной остается только эта маленькая фича с неиспользуемыми экспортами при рефакторинге. При этом недостатков ну просто море. Стоит ли оно того, и можно ли фичу с простотой удаления ненужного назвать "эффективным решением задач", хотя это вообще не относится к решению основной задачи… Думаю, точно нет.

Насчет того, что «типизация работает хорошо» — это вряд ли, для этого нужны другие инструменты (stylelint), которые проверяют корректность и параметров, и значений. За редким исключением в css-in-js такого вообще нет, либо проверяются только названия параметров.


Вот тут в ответ можно прям ваши же слова приводить:
Понимаю, чтобы эффективно чем-то пользоваться, требуется этому сначала обучиться, «выстрадать», придумать десять подходов, прочитать о сотне, найти самое лучшее… В программировании так фактически с каждым инструментом.


Вероятнее всего SC у вас не тот инструмент, который вы всячески изучили. Вот здесь мы с учеником разбирали конкретный пример реализации реакт-компонента со styled+параметры, то есть когда в SC передаешь параметры и они влияют на конечные стили. И там очень даже проверяется типизация. Если параметр обязательный, то не передать из реакта я не смогу (TypeScript будет ругаться), ровно как и передать не тот тип.

Есть еще много моментов (включая типизацию всей темы и автоподстановку вариантов в IDE даже для вложенных конструкций). CSS и т.п. этого не дают, там все просто живет своей жизнью.

Спасибо за ссылку, посмотрел и понял, что разговор наш если и состоится, то будет слишком долгим, так как буквально все там, на мой взгляд, неправильно — от обозначения проблем до выводов. Контраргументация потянет на статью, а времени на это сейчас нет, да и не то, чтобы хотелось прямо доказать свою правоту — время и так все по своим местам расставит.

Ну да. Разные специалисты используют разные инструменты и имеют разные знания. И это нормально. Именно поэтому я говорю чем мне нравится CS, но не говорю, что все остальное — от лукавого и фтопку всё. Каждому своё, и своё не каждому. А закусился как раз по причине того, что не нравится когда кто-то приходит и говорит «Вот только так надо, а остальное нафиг!».

Вот как ваш пример сделал бы я:


// scss
.popover {
  ...
  &[data-opened=false] { display: false; }
}

// ts
const DropdownMenu = ({ isOpened }: Props) => (
  <div className={css.popover} data-opened={isOpened}>...</div>
)

Что я получаю в итоге:


  • стили не мешаются с логикой
  • легко выносятся в отдельный файл сборщиком
  • этот файл кешируется браузером
  • ввиду чего может использоваться в связке с SSR

И нет вот этого ужаса (нагромождение разнообразного синтаксиса из разных языков, множество самых разных видов скобок, дубляж, лишние синт. конструкции и т.д.):


export const DropdownMenu = styled.ul<DropdownMenu>`
  ...
  ${({ opened }) => {
    if (opened) {
      return css`
        display: block;
      `
    } else {
      return css`
        display: none;
      `
    }
  }}
`

Стили остались простыми, декларативными. При желании можно написать тест в отрыве от стилей вообще. Вселенная "как оно выглядит" находится отдельно от "как оно работает". В случае чего легко добавить каких-нибудь анимаций открытия\закрытия прямо в SCSS файл, не трогая бизнес-логику.


При этом есть линтеры которые не дадут указать класс, которого нет в файле. Есть даже линтеры которые не позволят не задействовать все классы из файла (но вот тут уже может оказаться неудобно).

Удобное, лаконичное решение. Только забыли еще упомянуть про CSS Modules вида css.popover — то есть не будет никаких пересечений глобальных классов и "думать над названиями классов" тоже надо не больше, чем над названиями любых других сущностей и переменных (обычно исходят из семантики и читабельности). А эти два "недостатка CSS", судя по ссылке, и вынудили человека тащить js-надстройки в код и смешивать логику и представление.

>>
// scss
.popover {

&[data-opened=false] { display: false; }
}


По проекту в нескольких компонентах используется [data-opened]=«false».
Потом по какой-то причине переименовали в [data-closed]=«true». В пяти компонентах нашли этот класс и переименовали, а в друх забыли. И как вы это дело в таком случае будет отслеживать? Аргументы типа «нефиг переделывать» не принимаются.
В пяти компонентах нашли этот класс и переименовали, а в друх забыли

Если речь идёт о чистом CSS на @import-ах, то ваш пример был бы логичен. Но чистый CSS (или даже SCSS) в SPA в 2021 это как-то уже совсем неудобно.


Речь про css-modules. А с ним получается такая петрушка:


  • вы решили переименовать data-opened в data-closed
  • у вас есть 5 мест где есть data-opened
  • но, внезапно, они ж ведь никак с друг другом не пересекаются
  • поэтому вы можете смело переименовать это в трёх местах, и не трогать в оставшихся двух или что вы хотели там сделать
  • дело в том что нет "нашли этот класс", т.к. если у вас 5 разных мест, то это 5 разных классов с 5 разными стилями

Т.е. ситуация почти зеркальная к style-components, но декларативно, а не императивно в runtime.

поэтому вы можете смело переименовать это в трёх местах, и не трогать в оставшихся двух или что вы хотели там сделать


Так вот как раз этого-то и не нужно. И как раз это SC+TS и решает. Если я переименовал параметр, то я должен получить ошибку там, где я указываю несуществующий параметр и там, где я передаю не тот параметр (особенно это работает когда не просто Record<string, string> идет, как это часто в вашем примере происходит, а когда используются enum и прочие вложенные типы).

Есть и другой, менее критичный момент, но все же, типа
import cs from 'classname';
import {row, col} from './styles.scss';

И передача дальне в className этих самых полученных классов. Это немного решает проблему в том плане, что если в styles.scss поменялся селектор, то и в импорте можно получить ошибку. Но это не решает того, что должно передаваться в конечную верстку. То есть если у вас там должно быть
<div className={row}>
  <div className={col}>
  </div>
  <div className={col}>
  </div>
</div>


вы это тут никак не обеспечите проверку. Если разработчик забыл прописать нужный класс, то он просто его забыл.
А если я делаю SC с указанием нужного типа, то там будет все обязательно и будет отлично проверять.
Так вот как раз этого-то и не нужно. И как раз это SC+TS и решает

Я право не очень вас понял. По сути примеры на css-modules и style-components работают очень похожим образом. За исключением того что в случае css-modules у вас появляется data-attribute, а в style-components метод + inline style (или всякий раз новый селектор + стиль). По сути ошибку можно совершить в:


  • Имени класса\компонента. Поведение идентично — ошибка
  • Названии data-attribute-а (тут у css-modules -1 бал, т.к. в style-components будет inline-style без аттрибута, и опечататься нельзя)
  • В самом стиле display: none. Тут в scss у вас линтер может заругаться. В style-components возможно тоже есть какие-нибудь линтеры.

В итоге почти паритет, за счёт динамики небольшой плюс в строгости у style-components, правда очень большой ценой.


Если разработчик забыл прописать нужный класс, то он просто его забыл. А если я делаю SC с указанием нужного типа, то там будет все обязательно и будет отлично проверять.

Ничего не понял если честно. Что мешает вам в случае style components написать <div/>? Разве что если у вас есть линтер который категорически запрещает любые не style-components теги. Но это прямо какой-то эребор.


К тому же в чём собственно проблема? Вот забыл я указать класс. Получаю ошибку что класс в стилях не используется. Ок, пусть я ещё и стиль забыл указать. Ну я ж не слепой, я же вижу что верстаю. Как это уйдёт в merge-request? А потом, когда вёрстка готова проблема тем более не актуальна — ведь класс уже на месте.


Мне кажется или эти "проблемы" ну совсем высосаны из пальца? Ну и кстати не имеют никакого отношения к типизации. Разве что к статической линковке и линтерам.

Да, мы друг друга не поймем, скорее всего. Я тогда не знаю что вы вообще под типизацией подразумеваете. Если только наличие/отсутствие переменных, то это не так. Типизация подразумевает именно типы, то есть, к примеру, если у меня указано opened: boolean, то и передать можно только opened={true||false}. Если я потом меняю тип на opened: «true» | «false», то и передать можно только «true» | «false» (то есть именно один из этих строковых вариантов и никакой другой строчный вариант не пройдет). А если я enum прокину, та и вовсе придется передавать свойство его.

Ничего не понял если честно. Что мешает вам в случае style components написать <div/>?

Если вы просто на своей стороне что-то переписываете, то конечно ничто не мешает. Но если юзаете готовый uikit, то будете все-таки компоненты из него юзать, верно?

Вот забыл я указать класс. Получаю ошибку что класс в стилях не используется. Ок, пусть я ещё и стиль забыл указать. Ну я ж не слепой, я же вижу что верстаю.

Вот это как раз проблема, для решения которой и юзается SC с типизацией. Если вы фулстек и проект полностью готовите в одно лицо, то конечно, делайте как хотите и как знаете. Но если вы работает в команде 350+ человек, то поверьте моему опыту, такое мало прокатывает уже. И там более четко делится на членов UI team и на конечных программистов. Когда я, как программист, программирую какую-то логику и хочу результат вывести в готовый сторонний компонент, я не хочу думать о том, какие там классы надо передать. Я хочу увидеть необходимые параметры с комментариями к ним и передать необходимое.
Я тогда не знаю что вы вообще под типизацией подразумеваете

вот это:


.selector {
  display: wrong; // "wrong" is not an applicable value for "display"
}

если у меня указано opened: boolean, то и передать можно только opened={true||false}

Проблема в том что у вас это указано внутри компоненты и обеспечивается там же и теми же механизмами что и у меня. Т.е. в этом месте у нас нет никакой разницы в коде. У вас это отдельный компонент, и у меня. У вас это тип props-ов, и у меня. У вас там тип указан как boolean, и у меня. Всё одно и то же.


Но если юзаете готовый uikit, то будете все-таки компоненты из него юзать, верно?

Не понимаю. У вас какой-то оторванный от жизни пример. Я беру и пишу тег без класса. Зачем я его пишу? Ну если такая проблема стоит — возьмите готовый (если есть) или напишите свой lint-rule который запретит теги без классов. В чём разница с css-modules?


Вот это как раз проблема, для решения которой и юзается SC с типизацией.

Чуть ниже я вам привёл пример со статическое линковкой которая решит вашу проблему. Я правда снова не понимаю причём тут TS и типы. Ну да не важно. Вы хотели ошибку? Вы её получили.


Реальное отличие между css-modules и style-components только в том, что style-components умеют в динамически генерируемые стили, а css-modules нет. Но мы тут спорим не об этом.

Проблема в том что у вас это указано внутри компоненты и обеспечивается там же и теми же механизмами что и у меня. Т.е. в этом месте у нас нет никакой разницы в коде. У вас это отдельный компонент, и у меня. У вас это тип props-ов, и у меня. У вас там тип указан как boolean, и у меня. Всё одно и то же.

Нет, разница большая, в том, что у вас это два отдельных не связанных друг с другом компонента. Один генерит стили и кто хочет, тот пусть их и юзает, а не хочет — пусть не юзает. А кто хочет юзать, то пусть внимательно смотрят что именно передают. А в моем случае это два взаимосвязанных компонента, живущих совместной жизнью, и изменение одного влияет на другого.

Не понимаю. У вас какой-то оторванный от жизни пример. Я беру и пишу тег без класса. Зачем я его пишу?

Это из серии спора JS-разработчика и TS-разработчика. Вроде пишут практически на одном языке, а парадигмы совсем разные (по себе знаю, потому как не один год на чистом JS писал, прежде чем на TS перейти).
Отвечаю: нет, у меня очень даже не оторванный от жизни пример. Просто вы проблемы не видите, вот и решение вам не интересно. Так вот, в вашем случае проверка только на передачу класса, передан он или нет. При этом передаваемый класс (селектор) — это просто строка. Так вот, вы можете только проверить на то передан был класс или нет. При чем className="" — это как бы тоже передан, потому что параметр есть и он строковый, хоть это и пустая строка. А можно передать className=«dsf sdfhegr tr wefwef sdf dsf и еще много всего», и валидировать здесь нечего, есть значение и ладушки.

В моем же примере возможно использование нескольких отдельных переменных и конечный вызов может быть таким:
<Comp 
  opened={true}
  size="small"
  limit={10}
  {...other}
/>

И я каждый параметр в отдельности могу валидировать и готовить под них нужные стили, а не просто проверять передан ли какой-то произвольный параметр или нет.

Вы долго и упорно сравниваете разные куски программы и получается какой-то нелепый спор. Вы зачем-то сравниваете содержимое моего компонента, с вызовом в React.createElement вашего компонента. Но зачем?


Гарантии TS предоставляет нам одни и те же. В одном и том же месте. И ошибку мы можем совершить в одном и том же месте.


Вот так мы вызываем компонент:


<Comp 
  opened={true}
  size="small"
  limit={10}
  {...other}
/>

заметьте код одинаковый для обоих случаев. Ничего не поменялось. Те же самые типы. Те же самые ошибки в случае ошибки.


А вот уже внутри вы можете совершить какую-нибудь ошибку. Например не задать нужные стили, опечататься в имени property, упасть с ошибкой и пр.


У вас внутри идёт задание стилей напрямую. У меня указание классов из css файла. Я могу указать не те классы, или не указать те. Вы можете нахимичить что-то со стилями и макет сломается. Я могу тоже нахимичить что-то со стилями, но уже в отдельном файле.


Суть ведь от этого не меняется. Ну не даёт вам TS в случае style-components никаких дополнительных гарантий. Он просто тупо не умеет читать мысли.


Вы НЕПРАВИЛЬНО сравниваете ваш и мой код. У нас одни и те же гарантии, заданные в одном и том же месте.

Пять компонентов с маркером открытости — ок, пусть будут модалка, конферм, поповер, аккордеон и дропдаун. В поповере решили изменить на [data-closed]="true", потом в остальных. Вроде правильно понял условие задачи?


  1. Условие немного нереалистично — компоненты независимые, и если прямо с лупой в каждый не залезать, и не узнаешь, что там есть атрибут data-opened. Может вполне быть и другая реализация, и в целом разработчику все равно на этот "черный ящик"-компонент, если требования не меняются.
  2. Значит, изменились требования, причем ко всем компонентам? Или какой-то внезапный порыв все переписать у недавно пришедшего разработчика? Ок.
  3. Надо, значит надо. Берем поповер, переименовываем атрибут, проверяем в библиотеке компонентов или на реальной страничке — не работает. По одному клику на класс переходим в файл стилей, видим &[data-opened=false], переименовываем — работает. Git commit.
  4. Повторить для оставшихся четырех компонентов. Переименовал и не проверил что не работает? В субботу, джун, у тебя рабочий день, учишься писать e2e тесты и покрываешь эти компоненты автоматизированными тестами.
Вот когда-то и я делал примерно по вашему сценарию (он классический).
А сейчас я так не делаю. Если я что-то переименовал и зацепил, то я получил ошибку еще на уровне IDE, или на уровне прехука, или просто запустив скрипт проверки.
Более того, это работает и с импортируемыми сторонними компонентами, написанными в том же стиле. И это уже не черный ящик. И если сторонний компонент обновился и там что-то поменялось и зацепило у меня, мне не надо ходить с лупой по проекту или держать тестировщика, который целыми днями кликает проект (хотя где надо, я настраиваю cypress). И обновляюсь спокойно, а не в рулетку играю.

P.S. и все это — важная составляющая CI/CD. Когда я виливаю проект в гитхаб, там сробатвают экшены и все проверяется. И в большинстве случаев это решает проблемы типа «на моей тачке работало», особенно в случае использования воркспейсов и т.п…

Ок. Давайте предположим, что вам нужна статическая линковка на ещё более глубоком уровне, чем в моём примере. И вы даже готовы пожертвовать краткостью и простотой кода.


Вуаля:


// scss
.popover { ... }
.openedPopover { display: none; }

// ts
<div className={cn(css.popover, isOpened && css.openedPopover)}>...</div>

Всё равно куда приятнее чем в style-components, и без его недостатков. Всё ещё декларативный css, разделение логики и пр. и пр. Не хочу повторяться. Теперь вы не можете забыть переименовать слово в двух местах.


Но я сознательно так не пишу, ибо код с data-attribute-ми суммарно на кодовую базу оказывает (по нашему опыту) более благотворный эффект, нежели чуть больше статических проверок, за счёт ухудшения читаемости.


Впрочем тут каждый решает сам.

Еще раз: вы оперируете только css-селекторами. SC позволяет работать с произвольными параметрами и дополнительно использовать логику для формирования стилей.

type CompProps = {
  opened: boolean
}

export const Comp = styled.div<CompProps>`
  ${({opened}) => {
    if(opened) {
      return css`...`
    }
    else { 
       display: none;
    }
  }}
`


При чем в ретурн можно не одну строчку вернуть, а много CSS-кода, включая медиа-квери и т.п.

В кастомный класс вы можете засунуть не одну стоку кода, а много. Включая media-query. Вы, кажется, давно не писали на CSS

type CompProps = {
  opened: boolean
}

export const Comp = styled.div<CompProps>`
  ${({opened}) => {
    if(opened) {
      return css`
         display: block;
         width: 100%;
         @media screen and (min-width: 737px) {
             max-width: 300px;
         }
      `
    }
    else { 
       display: none;
    }
  }}
`


Более развернутый вам пример, чтобы было понятней.
// css
.comp { 
  /* some styles */ 
  display: none;
};

@media screen and (min-width: 737px) {
  .opened { max-width: 300px; }
}

// ts
type CompProps = {
  opened: boolean
}

export const Comp = ({ opened }: CompProps) => <div
  className={cn(css.comp, opened && css.opened)}
/>

Что изменилось?

Смотрим комменты выше.
Давайте уже завязывать. Вы меня не убедили (хотя я вас скорее всего тоже). Пусть хабравчане рассудят доводы За и Против.
Вы меня не убедили

Самое смешное — я пока даже не понимаю в чём я вас убеждаю. В том что css-modules лучше или хуже? Да я вроде не успел даже начать. Я пока пытаюсь вам показать что мы сравниваем "2+3" и "2+2+1". 5 === 5


Я прекрасно понимаю что гарантирует вам те ошибки, которые вы защищаете. И вижу что те же самые ошибки, я получу в том же самом месте. Без каких-либо "но".


Пусть хабравчане рассудят доводы За и Против.

Я вам умоляю. "Хабровчане" тут только первые пару дней после публикации статьи. Потом материал просто умирает.


Ещё раз. Единственное реальное преимущество, в рамках обсуждаемой темы, которое style-components имеют против css-modules, это поддержка настоящих динамических свойств. Те самые случаи когда выразить что-то статически декларативно либо невозможно, либо очень сложно. Вот вам пара примеров:


  • Математика цветов. Всевозможные "на 10% темнее чем цвет который юзер только что выбрал в color-picker"
  • Позиционирование position: absolute/fixed элементов. Никто не будет писать стилей вида .width_999 { width: 999px; }
  • Позиционированные анимации. Например когда нужно свапнуть красиво два блока произвольных неравных размеров
  • Ну и тому подобная экзотика

Объединяет их одно — эти вещи никак не ложатся на заранее сформированный статический стиль. Вот тут style-components кладут на лопатки заведомо более слабый в таких вопросах css-modules. Так как первый работает в runtime, а второй в compile time.


Но это всё, как нетрудно заметить, не имеет отношения ни к линтингу, ни к типам.

Если вы так хорошо знаете TS, то почему настойчиво закрываете глаза на приведенные примеры?
Я вам говорю, что у меня на вход 3 параметра (может больше, может меньше) и у меня валидация всех параметров как внутри SC, так и в вызывающих компонентах. Вы мне в ответ шлете реакт-компонент с отрисовкой div-а с передачей в него CSS-селекторов. Да, понятное дело, что можно заточить отдельный реакт-компонент, суть которого будет только в принятии указанных параметров и формирования конечного набора className, стили для которых уже сформированы и тут типа тогда проверка будет всего и все и очень похоже на то, что в моем примере. Но вопрос: а будет ли тогда это сильно быстрее работать, если суть логики все равно выполняется в реакт-компоненте (то есть в рантайме, против которого вы и топите?). Это очень похоже на одно и то же. И воторой момент, о котором я говорил выше: использование других styled-компонентов в качестве селекторов. SC это поддерживает нативно. А в вашей реализации я что должен делать? if(props.children instanceof OtherComponent) {...}? Так чтоли? Зачем мне все это переписывать, если SC имеет из коробки?
Вот и получается, что вы говорите «Вам это не нужно, потому что в целом это поддерживается там, почти все». А я в ответ говорю «Мне того почти всего не достаточно, мне надо все». И я точно знаю, что мне надо, а не выбираю из чего-то незнакомого. Я и то и другое уже изучал и выбор осознанно сделан, даже если вы этого не понимаете.

И говорю еще раз: можно завязывать с обсуждением, я не хочу жевать одно и то же без толку.
И говорю еще раз: можно завязывать с обсуждением, я не хочу жевать одно и то же без толку.

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


Если вы так хорошо знаете TS, то почему настойчиво закрываете глаза на приведенные примеры?

Возможно потому что я слишком знаю TS/JS, CSS, HTML и DOM, чтобы видеть картинку в целом, у нас получается так, что мы говорим с вами на разных языках. Для меня действительно нет никаких скрытых переменных в этом уравнении. Всё как на ладони.


Все приведённые вами контр-аргументы я разбил. Но вы возможно этого не заметили.


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


Вы писали, что у вас opened покрыт типами, а у меня нет. При почти идентичном коде. Наконец с N-го раза мне удалось (кажется) вам показать что там зеркальная ситуация и мы получаем одни и те же гарантии. А ошибку которую ни линтер\ни type checker не поймает можем совершить в тех же самых местах (декларация стилей, логика внутри компонента обёртки).


Теперь вы вот пишете что в CSS у меня не получится сделать совместные стили между разными компонентами. Но ведь это совсем не так. Вешаются (внезапно) className на оба компонента (да они должны прописать себе это в props). В стилях используются этих className в любом потребном виде (да хоть .container > .child). Вы загляните какой код кодирует вам style-components. Увидите там ровно то же самое. Он ведь выше CSS не прыгнет, под капотом всегда CSS. Т.е. задействуются те же самые механизмы. Ну нет тут никакой магии.


А ещё раз повторю очевидную вещь — существенная разница только в compile-time vs run-time. Любые весомые преимущества css-in-js может получить ТОЛЬКО за счёт тех вещей, которые в runtime сделать проще. Я привёл несколько примеров выше. Я даже спорить не буду, что сделать color: darken(${props.randomColor}, 10%) проще именно в css-in-js. Но приведённые именно вами (не мною) примеры очень далеки от хоть какой-либо реальной потребности в этом.


А ещё я вам привёл ссылку на статью про действительно типизированный CSS. И вот там у вас есть гарантии. Но типы до ума доводить придётся долго… Вот когда вы говорите про типизированный CSS вы не путайте TS и CSS. CSS это стили. И когда вы пишете "css{ display: none }, стили это то что между { и }. И нет у вас тут никакой типизации. Это… СТРОКА. string.


А то где вы видите типизацию — это просто компонент обёртка. Вы думаете я от балды сам написан подобную обёртку в своих примерах? Нет, именно так мы и пишем production код. Этот подход имеет ряд преимуществ. Я могу отдельно об этом написать, если интересно. Там не всё так просто, мы делаем в таких обёртках куда больше вещей нежели чем просто перекладывание props и указание классов. Но я не стал переусложнять примеры. Если и правда интересно как и зачем это работает я могу рассказать. Но я догадываюсь, что не интересно. И да, хотя это и имеет дополнительные накладные расходы за счёт создания обёрток, наши обёртки сильно легче чем css-in-js. Если непонятно почему, я могу рассказать.


И я точно знаю, что мне надо, а не выбираю из чего-то незнакомого. Я и то и другое уже изучал и выбор осознанно сделан, даже если вы этого не понимаете.

У меня пока складывается ощущение, что вы переоцениваете свои познания и навыки. Кажется у нас с вами очень разный уровень квалификации. Даже не знаю чем я с вами спорю.

потому что у меня здесь типизация работает хорошо

Хотелось бы уточнить, а что вы подразумеваете под типизацией здесь? Пока что мне попадались на глаза только всевозможные вариации на { [key in string]: string | ... }.

В комментарии выше дал ссылку и добавил аргументы.

Но ведь там нет никакой типизации. А вот тут есть. И, кажется, больше нигде нет.

А какое отношение ваш скриншот имеет к обсуждаемому вопросу? В моём примере, не укажи вы isOpened, вы получите ровно ту же самую ошибку. А стили ваши как были нетипизированными, так и остались. Мы точно об одном и том же говорим?

Отношение такое имеет, что здесь говорит «Вы не указали opened», то есть обязывает меня указать opened={true||false}, а не как у вас, что просто нельзя передать несуществующий параметр. Здесь как раз можно свои параметры добавлять, рулить их типы и обязательность.

UPD: если интересно, вот сам исходный код:
github.com/Pivkarta/pivkarta.ru-2/blob/e9399f973243548b38e54d7760e1f3fce7554def/src/pages/_App/Layout/MainMenu/DropdownMenu/styles.ts#L19

github.com/Pivkarta/pivkarta.ru-2/blob/e9399f973243548b38e54d7760e1f3fce7554def/src/pages/_App/Layout/MainMenu/DropdownMenu/index.tsx
а не как у вас, что просто нельзя передать несуществующий параметр.

type Props = { isOpened: boolean };
const Dropdown = ({ isOpened }: Props) => <div.../>

<Dropdown/>; // error -  isOpened is not defined

В чём разница? Это же TS, он никак не зависит от styled-components. И от css-modules. Это просто типы. Они просто работают. И в вашем и в моём случаях. Потому что они практически идентичны

Вот в чем разница:
type Props = { isOpened: boolean };
export const Dropdown = ({ isOpened }: Props) => <div isOpened={isOpened}/>


Type '{ isOpened: boolean; }' is not assignable to type 'DetailedHTMLProps<HTMLAttributes, HTMLDivElement>'.
Property 'isOpened' does not exist on type 'DetailedHTMLProps<HTMLAttributes, HTMLDivElement>'.ts(2322)

Это же TS, он никак не зависит от styled-components


От TS вообще почти ничто не зависит. Конечный JS все равно будет работать как JS, и можно забить на ошибки. Но TS вводят не для того, чтобы на него забивать, а чтобы он помогал, и чтобы было видно где что и как. И как раз в этом плане React+SC+TS — это как раз очень хороший союз. Если здесь TS исключать, то я бы вообще не стал спорить. Более того, так и сидел бы дальше на less.
вот в чем разница

Какое отношение нижележащий код имеет к css-modules или зачем вы его привели? И главноо что вы хотели им сказать. Вы взяли просто <div/> и задали ему неправильное свойство, в которое он не умеет. Слава TS он выдал вам ошибку. А что вы хотели этим сказать? Тут ни css-modules, ни styled-components нет. Я решительно не понимаю о чём вы говорите.


Если здесь TS исключать, то я бы вообще не стал спорить. Более того, так и сидел бы дальше на less.

Поверьте я прекрасно знаю зачем нужен TS, и могу не один доклад про него рассказать. Я ни разу не топлю за "типы не нужны". Я топлю за — вы куда-то не туда смотрите, когда не видите ровно тех же самых гарантий у меня

У меня ваша ссылка лептоп вешает...

Возможность юзать Styled-components + TypeScript — для меня последний аргумент в пользу отказаться от сторонних CSS-файлов. Здесь SC дает больше чистоты проекту, особенно когда дело касается рефакторинга кода. В обычном режиме замахаешься сращивать где какой CSS у тебя используется, а где нет.
тоже пришел к выводу, что Styled-components с TypeScript в плане DX удобнее и эффективнее, чем препроцессоры. Из коробки решаются те проблемы, которые в каких либо пост/препроцессорах решается установкой 100500 плагинов
Тут можно еще одну важную фишку вспомнить: возможность использовать другие компоненты в качестве селекторов, а не придумывать кучу уникальных имен классов.
import styled from 'styled-components'
import { Menu } from './Menu/styles'

export const WrapperStyled = styled`
  margin-top: 50px;

  ${Menu} {
    margin: 80px;
  }
`

 оно не поддерживает серверный рендеринг, в результате всё рендерится на стороне клиента 

Что такое серверный рендеринг? Что вообще происходит?
У меня лично ощущение что, происходит какая то дичь в IT последние лет так 5 наверное... Сюда так же можно и железо приплести с их закрытыми инструментами разработки (эй, linux уже 30 лет существует, busybox с 1996 года, gcc 1987 год). Почему я не могу прошивку для купленных блютус наушников сам написать (qualcomm не дает мне документов на кремний в наушниках)? куда делись даташиты на современные чипы (бесполезные огрызки)? А теперь вы еще про серверный рендеринг говорите? Хотите сказать я не смогу сайт на ЛИЧНОМ роутере ДЛЯ ЛИЧНОГО ИСПОЛЬЗОВАНИЯ захостить (типа умный дом)? Мне теперь еще думать: А в этом роутере железо потянет серверный рендеринг?

Facebook гори синим пламенем!

Не переживайте Вы так, сможете сайт на личном роутере захостить, без серверного рендеринга

название не интуитивно и вводит в заблуждение)

styled components в некоторых специфичных кейсах могут не просто замедлять приложение, они его хоронят. Я в подобную ситуацию попал, когда использовал antd-tablе с 15 колонками и 150-200 строками. В каждой ячейке были какие-то компоненты, обёрнутые в styled. Так вот при некотором сочетании пропсов таблицы, она начинает пытаться динамически вычислять ширину колонок и sticky headers. Это приводит к тому, что она может попытаться перерендериться несколько десяток или сотен раз за секунду. И, будь в ячейках просто текст, это не убивало бы производительность так сильно. Но styled попытается добавлять/удалять из дома стили на каждый ререндер. И это просто намертво всё фризит.

Мне, в целом, нравится styled и отказываться от него не хочется. Но, в некоторых ситуациях эта штука может выстрелить в колено и разорвать не только ногу, но и всё остальное в радиусе 100 метров.

Судя по вашему описанию проблема у вас не в styled components, а в самом в подходе к стилизации таблицы. А css-in-js просто позволили выявить проблему на меньших масштабах.

Зарегистрируйтесь на Хабре , чтобы оставить комментарий