image


Введение


Существует множество подходов для того, чтобы стилизовать React-компоненты, и в этой статье будут рассмотрены некоторые из них. Но, для начала, определимся с ключевыми моментами, на которых основано повествование:


  • Использование React предполагает, что View нашего Web-приложения разбито на компоненты, и каждый из них, так или иначе, отвечает за собственное отображение и функциональность.


  • Подразумевается, что будут использованы современные инструменты разработки вроде babel, webpack, browserify, gulp, post- pre- css-процессоры.


  • Время подобных решений осталось в прошлом:

<!doctype html>
<!--[if lt IE 7]> <html class="ie6 oldie"> <![endif]-->
<!--[if IE 7]>    <html class="ie7 oldie"> <![endif]-->
<!--[if IE 8]>    <html class="ie8 oldie"> <![endif]-->
<!--[if gt IE 8]><!-->
<html class="">
<!--<![endif]--> 

  • Существуют такие инструменты как autoprefixer, и больше не нужно писать подобный CSS руками:

background: #1e5799; /* Old browsers */
background: -moz-linear-gradient(top,  #1e5799 0%, #7db9e8 100%); /* FF3.6-15 */
background: -webkit-linear-gradient(top,  #1e5799 0%,#7db9e8 100%); /* Chrome10-25,Safari5.1-6 */
background: linear-gradient(to bottom,  #1e5799 0%,#7db9e8 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#1e5799', endColorstr='#7db9e8',GradientType=0 ); /* IE6-9 */

  • React можно рендерить как на клиенте, так и на сервере. Есть множество проектов, которые используют эту замечательную возможность. За этим будущее.

Про стилизацию DOMElement-ов


React-компоненты рендерятся в настоящий HTML, т.е. в DOMElement-ы. Существует два способа настроить отображение DOMElement-а:


  1. Внешние CSS стили (тег style в разметке страницы, либо подключение файла стилей через тег link).
  2. Внутренние (inline) стили (атрибут style).

Второй подход имеет множество недостатков:


  • Невозможность использования media-queries, например, чтобы реагировать на изменение ширины экрана и отличать мобильное устройство от десктопа.
  • Невозможность использования псевдоклассов и псевдоэлементов вроде :hover, :active, ::placeholder, :before, :after и прочих.
  • Значительное увеличение размера разметки и снижение производительности . В случае серверного рендеринга увеличится еще и нагрузка на канал трафика, а также время загрузки страницы пользователем.
  • Невозможность переопределять стили по более сложным селекторам. Отсюда невозможность привести отображение в разных браузерах к одному виду (reset.css, normalize.css и т.д.)

Некоторые из этих недостатков могут компенсироваться комбинированием 2-го подхода с 1-ым, но не все.


Про политику Facebook


Известно, что Facebook, как компания-разработчик React, несмотря на описанные выше недостатки всё равно продвигает использование Inline Styles. Их можно понять, они стремятся обеспечить единообразный подход с React Native, ибо там только Inline Styles и имеются. Но нам от этого не легче, ведь для Web сам Facebook в плане стилизации React-компонентов ничего толкового не предлагает. Зато нашлось множество энтузиастов, которые написали свои велосипеды. Далее мы рассмотрим самые популярные из них и разберемся с основными подходами стилизации React-компонентов.


Эволюция подходов для стилизации React-компонентов без Inline Styles


  • Пишем один внешний файл со стилями, подключаем его и используем в React-компонентах className со значениями имен классов из этого файла.


  • Всё точно так же как в предыдущем пункте, но разбиваем стили на множество файлов в соответствии с компонентами. Далее каким-нибудь инструментом собираем все файлы в один и подключаем его. Для избежания коллизий имен используем, например, БЭМ.

/* Button.js */ 
import React, { Component } from 'react';
export default class Button extends Component {
  render() {
    return (
      <div className = {'button ' + (this.props.isRed ? 'button--red' : '')}>
        <div className = 'button__caption'>
          {this.props.caption}
        </div>
      </div>
    ); 
  }
}

/* Button.css */
.button {
  line-height: 32px;
  border: 1px solid #000;
}
.button__caption {
  text-decoration: underline;
  text-align: center;
}
.button--red {
  background-color: red;
}

  • Развивая идею модульности, можно перейти к использованию CSS-Modules.

/* ScopedSelectors.js */
import styles from './ScopedSelectors.css';
import React, { Component } from 'react';
export default class ScopedSelectors extends Component {
  render() {
    return (
      <div className = {styles.root}>
        <p className = {styles.text}>
          Scoped Selectors
        </p>
      </div>
    );
  }
};

/* ScopedSelectors.css */
.root {
  border-width: 2px;
  border-style: solid;
  border-color: #777;
  padding: 0 20px;
  margin: 0 6px;
  max-width: 400px;
}
.text {
  color: #777;
  font-size: 24px;
  font-family: helvetica, arial, sans-serif;
  font-weight: 600;
}

Использование CSS-Modules подразумевает, что имена стилей будут доступны только локально, т.е. каждый React-компонент может иметь свой стиль root, и никаких коллизий не будет — имена стилей трансформируются при сборке. Также существует возможность определить глобальные стили, которые будут доступны везде — их имена трансформироваться не будут. Для поддержки CSS-Modules со стороны Node.js существует проект css-modules-require-hook, либо можно использовать Webpack для компиляции серверного кода приложения (статья про компиляцию серверного кода в двух частях — 1, 2).


Про все эти подходы можно добавить, что к ним успешно и легко прикручиваются autoprefixer и всякие less, sass, postcss, stylus с константами, импортами, миксинами и прочими плюшками. Также нет никаких проблем с подсветкой синтаксиса и подсказками в IDE. В рамках этого списка будет легко переключиться с одного подхода на другой.


Стилизация React-компонентов на основе идей Inline Styles


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


Рассмотрим несколько самых известных представителей данного подхода:


Radium


Хороший проект, который поддерживает media queries и псевдоклассы, например, :hover. Он предлагает следующий синтаксис:


const styles = {
  button: {
    padding: '1em',

    ':hover': {
      border: '1px solid black'
    },

    '@media (max-width: 200px)': {
      width: '100%',

      ':hover': {
        background: 'white',
      }
    }
  },
  primary: {
    background: 'green'
  },
  warning: {
    background: 'yellow'
  },
};

...

<button style={[styles.button, styles.primary]}>
  Confirm
</button>

Чтобы это всё работало, при объявлении компонентов нужно использовать декоратор Radium.


React Style


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


import StyleSheet from 'react-style';

const styles = StyleSheet.create({
  primary: {
    background: 'green'
  },
  warning: {
    background: 'yellow'
  },
  button: {
    padding: '1em'
  },
  // media queries
  '@media (max-width: 200px)': {
    button: {
      width: '100%'
    }
  }
});

...

<button styles={[styles.button, styles.primary]}>
  Confirm
</button>

Как и в Radium, работают media queries, но, в отличие от него, не поддерживаются псевдоклассы и анимация средствами CSS, есть loader для Webpack.


JSS


Поддерживает autoprefixer, псевдоклассы и media queries. Может использоваться с React через react-jss. Также существует экспериментальный jss-loader для Webpack. Пример использования:


import classNames from 'classnames';
import useSheet from 'react-jss';

const styles = {
  button: {
    padding: '1em'
  },
  'media (max-width: 200px)': {
    button: {
      width: '100%'
    }
  },
  primary: {
    background: 'green'
  },
  warning: {
    background: 'yellow'
  }
};

@useSheet(styles)
export default class ConfirmButton extends React.Component {
  render() {
    const {classes} = this.props.sheet;

    return <button
      className={classNames(classes.button, classes.primary)}>
        Confirm
      </button>;
  }
}

jsxstyle


Проект вводит свой волшебный синтаксис, не поддерживает media queries, зато есть loader для Webpack.


// PrimaryButton component
<button
  padding='1em'
  background='green'
>Confirm</button>

Есть неплохой англоязычный обзор всех этих технологий.


Заключение


Нельзя однозначно сказать, какое из решений лучше остальных — всё зависит от конкретных задач и ограничений. Какому-то проекту больше подойдут CSS-Modules, другому хватит БЭМ-именований для классов, третьему — что-то основанное на идее Inline Styles.


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

Поделиться с друзьями
-->

Комментарии (10)


  1. Murmurianez
    11.05.2016 12:43

    Как то даже удивительно — в статье нет упоминания о CSS-loader. На мой взгляд ровно то что нужно. Во всяком случае явных недостатков не нашёл.


  1. f0rmat1k
    11.05.2016 12:43

    Для того, чтоб не писать БЭМ руками, есть модуль b_.


    1. JiLiZART
      11.05.2016 13:59

      Я с него перешел на https://github.com/albburtsev/bem-cn как мне кажется у последнего более лаконичный синтаксис


    1. ElianL
      11.05.2016 15:32

      Смотрели, не понравилось.
      Написали свое react-bem-classes — может кому понравится


    1. rosko
      15.05.2016 01:24

      Народ, объясните, зачем Реакту БЭМ? CSS modules имхо полностью решают все проблемы самой малой кровью


      1. f0rmat1k
        15.05.2016 08:26
        +1

        Ну, при условии, что БЭМ не пишется руками — то оба решения на самом деле предлагаю примерно одно и то же одной и той же кровью. Так что да, вполне можно использовать их.


  1. FanAs
    11.05.2016 13:55

    Проще использовать связку компонент + className с именем компонента + .less файл под каждый компонент с одинаковым названием. Избавляет от множества проблем и компоненты выглядят небольшими, без необходимости изучать что-то кроме CSS


    1. Ronnie_Gardocki
      11.05.2016 17:59

      А потом внезапно появляется задача, где надо внутри одного компонента заюзать пару кусков из другого. В обычных «глобальных» бэм стилях вы бы просто применили нужные классы в разметке и все бы работало идеально. А тут что? Копипастить? И не говорите только что такие ситуации редко встречаются.


      1. Kelly
        12.05.2016 11:11
        +1

        Копипастить. Потому что если начать использовать куски с других мест — потеряется сама идея компонентов. И будет больше вероятность что что-то сломается если вы поменяете код в другом месте


    1. f0rmat1k
      13.05.2016 18:05

      Поздравляю, вы только что открыли для себя БЭМ)
      В БЭМе компонент (блок) итак подразумевает className в стилях по имени компонента. Просто если появится больше 1-го дочернего тега в компоненте, то Элементы из БЭМа так или иначе понадобятся (className__elemName). Совсем необязательно (и не стоит) использовать БЭМ, как приводят в примерах всякие клевые бэмоненавистники:

      <a class="link link__control menu-list__link menu-list__link_type_simple menu-list__link_size_small i-bem"></a>
      


      Чаще всего нормальная компонента, это блок и ~ 3 элемента с лаконичными именами, реже модификаторы.