9 полезных советов для тех, кто начинает знакомство с React.js

1. Это просто библиотека для работы с представлениями

Сперва разберем основы. React — это не очередной MVC-фреймворк или какой-то другой фреймворк. Это просто библиотека для рендеринга ваших представлений. Если вы пришли из мира MVC, то стоит понять, что React — это только “V”, а “M” и “C” придётся поискать где-то ещё.

Картинки по запросу реакт

2. Компоненты должны быть небольшими

Звучит очевидно, не правда ли? Каждый толковый разработчик знает, что маленькие классы / модули / да что-угодно легче понять и поддерживать. Моей ошибкой в начале работы с React было то, что я не понял, насколько маленькими должны быть мои компоненты. Конечно, конечный размер будет зависеть от многих факторов, но вообще стоит делать компоненты значительно меньше, чем вы планируете изначально. В качестве примера приведу компонент, отображающий последнюю запись в моём блоге на главной странице сайта:

const LatestPostsComponent = props => (
  <section>
    <div><h1>Latest posts</h1></div>
    <div>
      { props.posts.map(post => <PostPreview key={post.slug} post={post}/>) }
    </div>
  </section>
);

Сам компонент — это <section>, с двумя <div>‘ами внутри. В первом находится заголовок, а второй выводит какие-то данные, отображая <PostPreview> каждого элемента. Примерно такими должны быть ваши компоненты.

3. Пишите функциональные компоненты

Раньше было всего два способа определения React-компонентов, первый — это React.createClass():

const MyComponent = React.createClass({
  render: function() {
    return <div className={this.props.className}/>;
  }
});

… а второй — классы ES6:

class MyComponent extends React.Component {
  render() {
    return <div className={this.props.className}/>;
  }
}

React 0.14 принёс новый синтаксис определения компонентов как функций от свойств:

const MyComponent = props => (
  <div className={props.className}/>
);

Это мой самый любимый способ. Помимо более понятного синтаксиса этот подход даёт ясно понять, когда компонент стоит разделить. Рассмотрим предыдущий пример и представим, что мы его ещё не разделили:

class LatestPostsComponent extends React.Component {
  render() {
    const postPreviews = renderPostPreviews();

    return (
      <section>
        <div><h1>Latest posts</h1></div>
        <div>
          { postPreviews }
        </div>
      </section>
    );
  }

  renderPostPreviews() {
    return this.props.posts.map(post => this.renderPostPreview(post));
  }

  renderPostPreview(post) {
    return (
      <article>
        <h3><a href={`/post/${post.slug}`}>{post.title}</a></h3>
        <time pubdate><em>{post.posted}</em></time>
        <div>
          <span>{post.blurb}</span>
          <a href={`/post/${post.slug}`}>Read more...</a>
        </div>
      </article>
    );
  }
}

Этот класс не так уж и плох. Мы уже вынесли пару методов из метода отрисовки и неплохо инкапсулировали саму идею рендеринга последних постов. Перепишем этот код, используя функциональный синтаксис:

const LatestPostsComponent = props => {
  const postPreviews = renderPostPreviews(props.posts);

  return (
    <section>
      <div><h1>Latest posts</h1></div>
      <div>
        { postPreviews }
      </div>
    </section>
  );
};

const renderPostPreviews = posts => (
  posts.map(post => this.renderPostPreview(post))
);

const renderPostPreview = post => (
  <article>
    <h3><a href={`/post/${post.slug}`}>{post.title}</a></h3>
    <time pubdate><em>{post.posted}</em></time>
    <div>
      <span>{post.blurb}</span>
      <a href={`/post/${post.slug}`}>Read more...</a>
    </div>
  </article>
);

Код почти не изменился, правда, теперь у нас есть чистые функции, а не методы класса. Однако, это весьма разные вещи. В первом примере я вижу class LatestPostsComponent { и сразу просматриваю код в поисках закрывающей скобки, думая: “Это конец класса, а значит, и конец компонента.” Для сравнения, во втором примере я вижу const LatestPostsComponent = props => { и ищу лишь конец этой функции, думая: “Это конец функции, а значит, и конец модуля. Стоп, но что это за код после моего компонента, в том же модуле? А, это другая функция, которая принимает данные и отрисовывает представление! Я могу вынести её в отдельный компонент!”

В будущем React будет оптимизирован так, чтобы функциональные компоненты были более эффективными, но пока что их производительность находится под большим вопросом; я рекомендую вам ознакомиться с этим материалом для прояснения картины.

Важно отметить, что у функциональных компонент есть несколько ограничений, которые я считаю их сильными сторонами. Первое — к функциональному компоненту нельзя привязать ref. Хотя ref и является удобным способом для общения компонента со своими потомками, я считаю, что это не для функционального React, а скорее для императивного jQuery.

Второе ограничение — к функциональным компонентам нельзя прикрепить состояние, и это тоже является преиммуществом, поскольку я советую: …

4. Пишите компоненты без состояний

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

Состояния затрудняют тестирование

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

Состояния затрудняют понимание компонентов

Читая код компонента, насыщенного состояниями, возникает очень много вопросов: “Это состояние уже было инициализировано?”, “Что произойдёт, если я изменю это состояние здесь?”, “Где ещё изменяется это состояние?”, “Есть ли здесь состояние гонки (race condition)?” и подобных — а это лишняя головная боль.

Состояния слишком упрощают добавление бизнес-логики в компоненты

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

Состояния затрудняют обмен информацией между частями приложения

Когда компонент обладает состоянием, его можно передать ниже по иерархии компонентов, но не в других направлениях.

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

Короче говоря, будьте крайне осторожны при добавлении состояний. Когда начнёте, будет очень сложно удержаться от добавления очередной “маленькой фичи”.

5. Используйте Redux.js

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

Вы наверняка слышали про Flux — SPA (style/pattern/architecture) для разработки веб-приложений, зачастую используемых с React. Существуют несколько фреймворков, реализующих идеи Flux, но я однозначно рекомендую Redux.js.