CSS-live.ru

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

Перевод статьи I Learned How to be Productive in React in a Week and You Can, Too с сайта css-tricks.com для css-live.ru. Автор — Сара Дрэснер.

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

Честное предупреждение

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

Я пользовалась курсом «React для начинающих» Веса Боса, а также «Введением в React.js для тех, чей уровень знания jQuery достаточен, чтобы справиться с React». Я очень рекомендую эти ресурсы.

Прошло два дня:

See the Pen Baby’s First React Attempt by Максим (@psywalker) on CodePen.

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

Если вы из тех, кто желает сразу же окунуться в Реакт и полностью изучить его, включая сборку и необходимый инструментарий — это потрясающе! Начните с этого. А ещё я бы посоветовала этот великолепный курс от «Frontend Masters»: «Современные веб-приложения»

Разрушители легенд: спецвыпуск о практичности в Реакте

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

Миф #1: для Реакта необходимо использовать встроенные стили.

Неа! Отнюдь. Можно использовать CSS, как и обычно. Потратив уйму времени на рефакторинг гигантского объёма CSS-кода, я поняла, насколько это важно. Реакт относительно новый и ему пока не приходилось выдерживать испытание переделками дизайнов. Если бы мне пришлось заново продираться через тысячи строк встроенных стилей ради обновления padding и line-height, меня бы, как разработчика, это наверняка расстроило.

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

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

Миф #2: для атрибутов элемента придётся использовать JavaScript-синтаксис, который совсем не похож на HTML.

Ещё одна, на мой взгляд, приятная деталь в стиле преподавания Веса Боса, это то, как он постепенно подводит слушателя к более привычному подходу и реализации. Я иногда грешу излишне упрощенным и очевидным кодом, хотя и понимаю, что другим нравятся примеры поабстрактнее.

Он предлагает писать разметку с помощью JSX, который наиболее близок к нашему другу, традиционному HTML, так что лично для меня это гораздо понятнее. Поэтому вместо:

return React.createElement("p", {className: "foo"}, "Привет, мир!");

Мы писали бы:

return (<p className="foo">Привет, мир!</p>);

Каждый из них работает. Но когда наша разметка становится всё сложнее, я обнаружила, что знакомый HTML в виде JSX помогает моему пониманию. Но учтите, что даже с JSX есть незначительные различия.

Миф #3: чтобы опробовать Реакт, нужно понимание всех инструментов для сборки.

И действительно, для Реакта нужно использовать инструменты для сборки, поэтому, как правило, туториалы начинаются с этого. Но я бы посоветовала начинать с того, что если вы совсем новичок, вам следовало бы поковыряться на CodePen, JSBin или JSFiddle. Это — хороший способ повторять и усваивать уроки, прежде чем вкладывать кучу сил в совершенно новую технологию. С целью сделать первый шаг, в сегодняшних примерах я планирую использовать CodePen.

Использование Реакта в типичных приложениях

В типичных приложениях на React 0.14+ прежде всего нам потребуется React и ReactDOM:

var React = require('react');
var ReactDOM = require('react-dom');

А затем вызов ReactDOM.render:

ReactDOM.render(routes, document.querySelector('#main'));

Использование Реакта в CodePen

В нашем случае просто выберем Реакт из выпадающего меню в панели JS (кликнете на иконку шестеренки вверху панели), а затем воспользуемся Babel в качестве компилятора.

Нам не требуется подключать React или ReactDOM с помощью require, поскольку у нас нет никакой маршрутизации, а мы используем компонент приложения напрямую. Просто подправим наш код:

React.render(<App/>, document.querySelector("#main"));
<div id="main"></div>

Также, чтобы приступить к работе, потребуется отладчик для Реакта в виде расширения для Chrome или для Firefox, которые отлично подходят для отладки виртуальной DOM.

В случае CodePen можете выбрать «Отладочный режим», и это расширение автоматически определит Реакт:

react-devtools

Использование Реакта

Вот основные «кирпичики», которые нам нужно знать для дальнейшей работы:

// Приложение
var App = React.createClass({
  render: function() {
    return (
            <div className="foo">Привет, мир!</div>
    )
  }
});
 
React.render(<App/>, document.querySelector("#main"));

Разберём это по частям. В последней строке мы находим id главного div (main) и отображаем в нём компонент <App />, загружающий всё приложение Реакта. Чтобы увидеть созданный DOM-элемент, можно воспользоваться вкладкой «Реакт» в отладчике.

Мы привязали <App /> в качестве первого компонента. Во-первых, обратите внимание, что этот тег пишется с заглавной буквы — хотя это и не обязательно, но это считается хорошим тоном в работе с Реакт-компонентами. Во-вторых, этот тег самозакрывающийся. Теги в Реакте должны быть закрыты, либо добавочным закрывающим тегом (напр. </div>), либо быть самозакрывающимся (напр. <hr> превратился бы в <hr />). Так работает JSX, иначе выскочит ошибка.

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

И последнее, что стоит отметить, это то, что вместо класса для элемента мы используем className. Это подвох, если вы привыкли писать HTML-разметку для веб-страниц. К счастью, это легко исправить. В этой документации отлично описан JSX и его синтаксис.

// Приложение
var App = React.createClass({
  render: function() {
    return (
            <Header />
    )
  }
});
 
// Шапка
var Header = React.createClass({
 render: function() {
   return (
            <div className="foo">Привет, мир!</div>
   )
 }
});
 
React.render(<App/>, document.querySelector("#main"));

Следующим шагом будет расширение приложения с помощью компонента. Название для заголовка можно выбирать на свой вкус, но для наглядности, пусть оно отражает его роль в документе. Можно заметить, что если нам также понадобиться добавить элемент навигации, сделать это не составит труда. Вот такая же страница со стандартным бутстраповским рядом кнопок для компонента <Nav />, и более семантичным h1 для нашего «Привет, мир!» вместо div:

// Приложение
var App = React.createClass({
render: function() {
 return (
  <div>
            <Nav />
            <Header />
  </div>
  )
 }
});
 
// Навигация
var Nav = React.createClass({
 render: function() {
   return (
   <ul className="nav nav-pills">
            <li role="presentation" className="active">Главная</li>
            <li role="presentation">Профиль</li>
            <li role="presentation">Сообщения</li>
   </ul>
  )
 }
});
 
// Шапка
var Header = React.createClass({
 render: function() {
   return (
            <h1 className="foo">Привет, мир!</h1>
   )
 }
});
 
React.render(<App/>, document.querySelector("#main"));

Ничего сложного, правда? Всё равно, что строительные блоки любой веб-страницы. Мы создали навигацию с шапкой, и применили эти компоненты к компоненту приложения, который отображён в body. Единственным подвохом последнего примера может показаться непонятный дополнительный div вокруг <Header /> и <Nav />. Это потому, что мы должны всегда возвращать один элемент. У нас не может быть два сестринских элемента, вот и приходится оборачивать их в один div.

Теперь, разобравшись со стоительными блоками, вы должны без проблем собрать целую страницу с помощью Реакта и его компонентов. Я бы посоветовала сделать рефакторинг макета уже созданной вами страницы, просто ради учёбы. Добавьте туда таблицу стилей и собирайте шаг за шаг остальные детали, пока не востановите её полностью. Для практики самое то.

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

Обычные подозреваемые: переменные и обработка событий

Давайте добавим чуть-чуть обработки событий и простых переменных, для начала просто чтобы понять, как это происходит.

И снова возьмём первое демо (давайте использовать его на протяжении всей статьи):

У компонента статьи в блоге весьма простая разметка, так что начнём с этого:

// Статья в блоге
var Post = React.createClass({
  render : function() {
    return (
      <div className="blog-post">
        <h3 className="ptitle">{this.props.ptitle}<small>{this.props.date}</small></h3>
        <img className="thumbnail" src={this.props.pimg} />
        <p>{this.props.postbody}</p>
        <div className="callout callout-post">
          <ul className="menu simple">
          <li>Author: {this.props.author}</li>
          <li>Comments: {this.props.comments}</li>
          <li>Tags: {h.getTaggedName()}</li>
          </ul>
        </div>
      </div>
    )
  }
});

Добавим произвольную переменную и обработчик события и посмотрим, как это работает. Первое, что стоит учесть — если мы используем JSX, то фигурные скобки сообщат Реакту, что мы готовы использовать JavaScript. Так что наша переменная будет выглядеть как {var}.

Мы хотим добавить оператор переменной внутрь вызова функции render, но прежде, чем вернуть разметку. Можно затем получить к ней доступ, вызвав переменную, в данном случае {com}. Событие по клику — встроенный обработчик onClick, вероятно, противоречащий хорошему тону, к которому вы привыкли. Это имя мы выбрали сами, когда как render — метод фреймворка. Мы вызваем {this.tryClick} и пишем метод, хранимый как tryClick (произвольное имя) внутри того же компонента, к примеру:

// Статья в блоге
var Post = React.createClass({
  tryClick : function() {
    alert('просто опробовать события по клику лалала');
  },
  render : function() {
    var com = "Comments";
    return (
      <div className="blog-post">
        <h3 className="ptitle">{this.props.ptitle}<small>{this.props.date}</small></h3>
        <img className="thumbnail" src={this.props.pimg} />
        <p>{this.props.postbody}</p>
        <div className="callout callout-post">
          <ul className="menu simple">
          <li><a href="#" onClick={this.tryClick}>Author: {this.props.author}</a></li>
          <li>{com}: {this.props.comments}</li>
          <li>Tags: {h.getTaggedName()}</li>
          </ul>
        </div>
      </div>
    )
  }
});

Синтаксис для событий в Реакте начинается с «on» и использует верблюжийРегистр. Например:

  • click = onClick
  • mouseEnter = onMouseEnter
  • keyPress = onKeyPress

Здесь можно найти полный список всех поддерживаемых событий.

Объединение Реакта с другими библиотеками (в данном случае Greensock)

Для анимации я предпочитаю GSAP. В Реакте можно использовать и другие библиотеки, поэтому объеденим GSAP с Реактом и посмотрим, что будет.

Мы хотим обращаться к другим библиотекам в нужный момент, поэтому нужно убедиться, что они вызываются сразу же после первого метода render. И в этом нам поможет метод componentDidMount. Ещё немного о других методах жизненного цикла мы поговорим позже, но пока для нас важно то, что этот метод вызывается сразу же после вставки компонента в документ.

Если вы привыкли к jQuery, то наверняка знакомы с выборкой элементов прямо из DOM. В этом случае, даже при анимировании DOM-элементов мы воспользуемся методом getDOMNode() Реакта, а не просто выберем их. А также вы заметите, что на самом деле мы вызываем функцию для анимации в компоненте приложения, и просто передаем её ниже нашим боксам. (Чтобы увидеть анимацию, возможно придётся нажать кнопку «Rerun»)

// Приложение
var App = React.createClass({
 componentDidMount: function() {
    var sq1 = this.refs.first.getDOMNode();
    var sq2 = this.refs.second.getDOMNode();
    var sq3 = this.refs.third.getDOMNode();
    var sq4 = this.refs.fourth.getDOMNode();
    var sq5 = this.refs.fifth.getDOMNode();
    var allSq = [sq1, sq2, sq3, sq4, sq5];
   
    TweenLite.set(allSq, {css:{transformPerspective:400, perspective:400, transformStyle:"preserve-3d"}}); 

    TweenMax.to(sq1, 2.5, {css:{rotationX:230, z:-600}, ease:Power2.easeOut}, "+=0.2");
    TweenMax.to(sq2, 2.5, {css:{rotationY:230, z:-150}, ease:Power4.easeOut}, "+=0.2");
    TweenMax.to(sq3, 2.5, {css:{rotationX:500, z:150}, ease:Power2.easeInOut}, "+=0.2");
    TweenMax.to(sq4, 2.5, {css:{rotationY:500, z:-150}, ease:Power4.easeOut}, "+=0.2");
    TweenMax.to(sq5, 2.5, {css:{rotationX:1000, z:100}, ease:Power2.easeOut}, "+=0.2");
 },
 render: function() {
   return (
     <div className="scene">
        <Box ref="first"></Box>
        <Box ref="second"></Box>
        <Box ref="third"></Box>
        <Box ref="fourth"></Box>
        <Box ref="fifth"></Box>
    </div>
   )
 }
});

// Box
var Box = React.createClass({
  render: function() {
    return (
      <div className="squares"></div>
    )
  }
});
 
 
React.render(<App/>, document.querySelector("#main"));

Возможно, вы привыкли обращаться к DOM через jQuery или ванильный JavaScript. Можно по-прежнему делать так, но здесь мы обращаемся к частям DOM через getDOMNode() и refs, в этой строке: var sq1 = this.refs.squares1.getDOMNode();. Немного подробнее об этом написано в документациии Реакта.

У Реакта есть и другие способы добавить динамики вашим проектам — React-Motion Ченга Лу просто замечателен, еще стоит отметить React-GSAP-Enhancer.

Создание глобальной функции-хелпера

Можно даже писать функции-хелперы, к которым легко может обращаться множество компонентов. Для этих функций подходит та же точечная нотация, что и для this.functionName. Мы сохраняем функцию-хелпер (в этом примере на CodePen — прямо в начале файла, но в реальных приложениях это был бы отдельный файл) и объявляем каждую из них в качестве объекта, таким же способом, как и в структуре компонента.

Что-то вроде правильно отформатированной даты в первом демо со статьями блока могло бы выглядеть так:

var h =  {
  getTime: function() {
     var month = ["jan", "feb", "march"]; // …. and so on
     var d = new Date();
     var mon = month[d.getMonth()];
     var day = d.getDate();
     var year = d.getFullYear();
     var dateAll = mon + " " + day + ", " + year;
   
     return dateAll;
  }
};

И использовано вот так: {h.getTime()}

Настоящее дело: управление состоянием

Хорошо! Теперь, когда мы разобрались со строительными блоками, перейдём к одной из самых класссных вещей.

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

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

  1. Первое — это состояние. Оно — принадлежность компонента, а значит, его область видимости ограничена компонентом, и мы будем обращаться к нему так: {this.state.foo}. Можно обновить состояние, вызвав this.setState().
  2. Второе касается того, как передавать (предназначенные только для чтение) данные от родителя к компоненту (вроде того, как приложение было родителем для header в первом примере). Мы зовём это props, как в слове «property» (свойство), и будем использовать его прямо в компоненте посредством {this.props.foo}. Дочерние элементы компонента не могут менять эти данные.

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

Прелесть виртуальной DOM в том, что Реакт ищет только те ноды в DOM, которые нужно обновить.

Теперь я прочитала кучу всего про состояние, но думаю, Весу удалось выразиться яснее всех, поэтому я перефразирую его: Если вы привыкли к jQuery, то все данные у вас хранятся в DOM. Реакту это ни к чему, он хранит данные в объекте (состоянии), а затем отрисовывает вещи, в зависимости от объекта. Это как главный эталон, от которого всё зависит.

Props на практике

Для начала давайте просто опробуем this.props, добавив {this.props.title} к компоненту Header, а после обратимся к нему в приложении.

// Приложение
var App = React.createClass({
  render: function() {
    return (
      <Header greeting="Hello, world!" />
    )
  }
});
 
// Шапка
var Header = React.createClass({
  render: function() {
    return (
      <div className="foo">
        <h1>{this.props.greeting}</h1>
      </div>
    )
  }
});
 
React.render(<App/>, document.querySelector("#main"));

Для props можно также добавить значение по умолчанию, в методе жизненного цикла с очень подходящим названием getDefaultProps. Это здорово, поскольку порой не хочется указывать props для каждого компонента. Примером может быть аватар по умолчанию, пока пользователь не выбрал себе настоящий, как яйцо в Твиттере, или установка списка в качестве пустого массива, который будет наполнен позже.

var ProfilePic = React.createClass({
  getDefaultProps: function() {
    return {
      value: 'twitter-egg.jpg'
    };
   ...
});

Практика

Давайте немного разовьем это и поработаем с состояниями. Если мы в итоге собираемся менять this.state, нужно довести его до готовности. Для этого воспользуемся getInitialState, что позволит установить, какое состояние самое первое. Без этого мы не сможем добавить состояние.

Давайте просто менять фон в зависимости от выбора пользователя.

// Приложение
var App = React.createClass({
  /*установка состояния*/
  getInitialState: function() {
    return {
      bgColor: "teal"
    };
  },
  /*меняет состояние*/
  handleColorChange: function (color) {
    // когда состояние устанавливается напрямую,
    // реакт не знает об этом, вот почему мы используем setState
    this.setState({ bgColor: color });
  },
  /*для методов жизненного цикла*/
  updateBackgroundColor: function () {
    var body = document.querySelector('body')
    body.style.background = this.state.bgColor
  },
  /*методы жизненного цикла*/
  componentDidMount: function () { 
    this.updateBackgroundColor()
  },
  componentDidUpdate: function () { 
    this.updateBackgroundColor()
  },
  render: function() {
    return (
    /* вызвав заголовок здесь в компоненте, можно обращаться к this.props в шапке */
      <div className="foo">
        <h1>Hello, World!</h1>
        <label>What color?
          <ColorPicker value={this.state.bgColor} onColorChange={this.handleColorChange}/>
        </label>
      </div>
    )
  }
});

// компонент ColorPicker
var ColorPicker = React.createClass({
  propTypes: {
    value: React.PropTypes.string.isRequired,
    onColorChange: React.PropTypes.func
  },
  handleChange: function(e) {
    e.preventDefault();
    var color = e.target.value
    
    // если тому, кто нас (ColorPicker) отобразит, захочется узнать,
    // когда изменился цвет, дайте ему знать
    if (this.props.onColorChange)
      this.props.onColorChange(color);
  },
  render: function() {
    return (
      <select value={this.props.value} onChange={this.handleChange}>
        <option value="orangered">orangered</option>
        <option value="teal">teal</option>
        <option value="orange">orange</option>
        <option value="indigo">indigo</option>
        <option value="red">red</option>
      </select>
    )
  }
});

React.render(<App/>, document.querySelector('#main'));

Важная часть этого, которую нужно учитывать — требуется функция handleChange

С нативными select-ами в обычном DOM ничего подобного бы не понадобилось, поскольку они автоматически обновлялись бы по выбору пользователей. В Реакте все состояния управляются компонентом, поэтому если нужно, чтобы input или select следил за изменениями, вам придется описать это в коде. Это означает, что за состояние отвечает компонент, а не пользователь.

На первый взгляд неясно, ради чего было столько возиться, но мы намеренно оставляем эти примеры маленькими. По-настоящему Реакт проявляет себя, когда дело доходит до больших сложностей. Как бы я ни любила jQuery (а я до сих пор люблю jQuery, это не то, что можно выразить булевой переменной true/false), вам не удастся уйти от довольно частой проверки при изменениях. В Реакте это решается тем, что эта необходимость устраняется благодаря простоте направления потока данных, по этой же причине его проще отслеживать в очень сложных приложениях.

Основные понятия состояния

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

Реакт показывает лучшие результаты работы, когда мы обновляем состояния и перерисовываем низкоуровневые компоненты. По сути мы хотим, чтобы эти компоненты оставались без состояния и брали данные из высокоуровневых компонентов. Компоненты на верхних уровнях иерархии (вроде нашего приложения) работают отлично именно тогда, когда запоминают состояния. Таким образом они управляют большей частью логики взаимодействия и передают состояние вниз с помощью props. Тем не менее, моменты, при которых у каждого компонента будет собственное состояние, не исключены. Однако, важно помнить, что соседние компоненты должны «общаться» не напрямую, а через родительский компонент.

Поэтому нам нужно по максимуму сохранять состояние в компоненте. На самом деле, вам не стоит беспокоиться о том, когда что-то перерисовывается. Всё, что вам важно учитывать — когда изменяется состояние, а об отображении Реакт позаботится сам.

Из такой идеологии разработки напрямую следует то, почему я также не советую использования props в getInitialState, и здесь можно подробнее почитать о том, почему это считается антипаттерном.

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

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

Ref-ы

Информацию об элементе можно также получить с помощью так называемого ref. Использование Ref-ов осуществляется за счёт прикрепления их к любому компоненту. Затем они возвращаются во время вызова метода render() и после мы можем ссылаться на них снаружи render(). Это крайне полезно.

И снова возьмём первый пример на CodePen. Давайте на минутку задумаемся, как создаются эти новые статьи в блоге, и при этом применим ref-ы.

На каждом поле формы у нас есть ref, наподобие такого:

<input type="text" ref="name" placeholder="Full Name required" required />

А на самом элементе формы мы вызываем:

onSubmit={this.createPost}

который ссылается на функцию createPost выше функции render, и использует ref-ы для хранения информации из отправляемой формы:

var post = {
 name : this.refs.name.value,
 ...
}

а затем можно обратиться к нему в состоянии приложения с помощью this.props:

this.props.addPost(post);

Это крайне удобно, поскольку теперь есть переменная post, с объектом, хранящим своё название (показано тут), дату, детали и т.д. Мы по-прежнему не храним ничего из этого в состоянии приложения, а только лишь дали приложению возможность это использовать. Рассмотрим это подробнее в следующем разделе, заодно с keys.

Этот пример я взяла в качестве способа поговорить о ref-ах в целом, но в приложениях из реальной жизни, вам, возможно, захочется рассмотреть что-то вроде form-serialize, чтобы упаковать поля формы для отправки аяксом.

Keys и использование Ref-ов

В последнем разделе мы создали способ хранить данные, собранные в форме, без передачи их куда-либо. Это потому, что в Реакте мы стремимся управлять этим состоянием в верхнем уровне иерархии. Для сохранения этого состояния, мы запускаем начальное состояние с пустым объектом. Вот где в итоге сохраняются данные из формы.

getInitialState : function() {
  return {
    posts: {}
  }
},

Затем возьмём функцию addPost. Заметьте, что управление находится здесь, а не в форме, даже хотя вызов происходит из формы. Мы даём состоянию временнУю метку, чтобы статьи оставались уникальными, а также настраиваем состояние для статей.

addPost: function(post) {
  var timestamp = (new Date()).getTime();
  // обновление объекта состояния
  this.state.posts['post-' + timestamp] = post;
  // установка состояния
  this.setState({ posts : this.state.posts });
},

У каждого дочернего элемента в массиве должен быть уникальный key.prop. Это важно, поскольку при любой возможности Реакт всё равно будет повторно использовать существующий DOM. Реакт использует keys для отслеживания того, какие элементы в DOM он должен обновить. Он избавляет нас от перерисовки всех DOM-элементов, когда это требуется. Это улучшает производительность нашего приложения.

Теперь давайте снова обратимся к приложению, и посмотрим, что мы делаем с только что сохранёнными данными из формы:

// Приложение
var App = React.createClass({
  getInitialState : function() {
    return {
      posts : {}
    }
  }, 
  addPost : function(post) {
    var timestamp = (new Date()).getTime();
    // обновление объекта состояния
    this.state.posts['post-' + timestamp] = post;
    // установка состояния
    this.setState({ posts : this.state.posts });
  },
  renderPost : function(key){
    return <NewPost key={key} index={key} details={this.state.posts[key]} />
  },
  render : function() {
   ...
    return (
      <div>
        <Banner />
	    ...
          <div className="list-of-posts">
              {Object.keys(this.state.posts).map(this.renderPost)}
          </div>
          <Submissions addPost={this.addPost}/>
        </div>
      </div>
    )
  }
});

Можно видеть, что когда приложение отображается, мы используем Object.keys для создания нового массива. Затем берём .map() с функцией renderPost, которую создадим заранее. Для тех, кто не знаком с .map(), она полезна для создания массива из существующего массива, можете представить её в виде цикла.

<div className="list-of-posts">
{Object.keys(this.state.posts).map(this.renderPost)}
</div>

Теперь давайте создадим эту самую функцию renderPost, которую только что вызвали. Мы передаём ключ в качестве параметра и назначаем его в качестве ключа, индекса, и создаём объект, который мы называем details, чтобы хранить информацию о состоянии статьи.

renderPost: function(key) {
  return <NewPost key={key} index={key} details={this.state.posts[key]} />
},

Может показаться странным, что мы передаём key и index, но внутри компонента невозможно обратиться к ключу prop, поэтому мы передаём ещё один ключ под названием index. Функция renderPost просто создаёт компонент <NewPost /> для каждого элемента в массиве, с подробностями, переданными вниз по иерархии, к которым затем можно обратиться. Это довольно-таки круто, поскольку, если в нашем состоянии что-то обновится, это распространится вниз по иерархии и мы сможем управлять им в одном месте.

В компоненте <NewPost /> хранятся подробности, вынесенные вниз по иерархии для отображения всей информации. А созданная нами ранее функция-хелпер, нужная для правильно отформатированной даты, выглядит здесь так: {h.getTime()}:

/*
  Новая статья
  <NewPost />
*/
var NewPost = React.createClass({
  render : function() {
    var details = this.props.details;
    return (
      <div className="blog-post">
        <h3 className="ptitle">{details.title}<small>{h.getTime()}</small></h3>
        <img className="thumbnail" src={details.image} alt={details.name}/>
        <p>{details.desc}</p>
        <div className="callout callout-post">
          <ul className="menu simple">
          <li>Author: {details.name}</li>
          <li>Comments: 0</li>
          <li>Tags: {h.getFunName()}</li>
          </ul>
        </div>
      </div>
    )
  }
});

keys-overview-cropped

А теперь всё вместе

Теперь, когда с keys всё ясно, взглянем на минутку на общую картину. Мы рассмотрели getInitialState, componentDidMount и render, но в Реакте есть ещё один метод при подключении — componentWillMount. Он похож на componentDidMount, но выполняется прямо перед первым отображением компонента.

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

Заключение

О Реакте написано гораздо больше, а эта статья лишь начало. Надеюсь, это путешествие новичка позволило понять, как подготовиться к работе с Реакта. Эта статья растянулась аж на 20 страниц, но при этом мы даже не затронули массу других важных тем в связи с Реактом, включающих ES6, Browserify, Gulp, Webpack, несметное число альтернативных реализаций и многое другое.

Опять же, для дальнейшего изучения, я настоятельно рекомендую погрузиться в курс Веса Боса, и он предлагает 10%-скидку для читателей CSS-Tricks с кодом CSSTRICKS, а также посмотреть некоторые видео на Frontend Masters. У Майкла Джексона есть прекрасный учебный курс, на котором можно зарегистрироваться для участия (мы ждём его на Trulia в Сан-Франциско в марте!). Есть также отличный учебник Артемия Федосеева под названием «Основы Реакта» и этот список Артёма Сапегина, который стоит занести в закладки. Ну и не забывайте, что документация по Реакту тоже очень неплоха. Счастливого изучения!

Большое спасибо Майклу Джексону, Весу Босу и Вэл Хед за проверку этой статьи.

P.S. Это тоже может быть интересно:

14 комментариев

  1. АЛЯПОВА́ТЫЙ перевод. Без локализации и примечаний переводчика. тупо слово в слово с ошибками автора и недоразумение вроде «вызывается непосредственно перед элементом»

    1. слово в слово с ошибками автора

      Можно примеры? Пару-тройку ошибок автора (типа пропущенных важных мест в примерах кода) мы выловили, возможно, что-то осталось — в конце концов, автор честно признается, что сама в Реакте новичок и делится опытом преодоления самых начальных барьеров именно с точки зрения новичка. Так что если можете предложить уточнение/исправление каких-то формулировок — пожалуйста!

      непосредственно перед элементом

      Где это?

      1. «непосредственно перед элементом» на картинках

        Я не всё ещё прочитал. но вот например в абзацах «Миф #1» говорится о неких «встроеных стилях». Затем ниже упоминаются «статические» стили.

        Не понятно что из этого является инлайн-стилями в аттрибуте «style» элемента , а что цсс-классами…

        Подключите «орфо» или скажите ссылку на гитхаб чтоб по мере прочтения я мог добавлять спорные места и предлагать корректировки. :)

        1. Да, не повезло многострадальному inline с однозначно узнаваемым русским переводом: он то «встроенный», то «строчный», то в лоб «инлайновый»… Мы ориентируемся на достаточно известный словарик терминов сообщества «Веб-стандарты», но да, ситуация, когда английский оригинал или «лобовая» калька понятнее перевода, мне самому очень знакома. Буду благодарен за идеи, как обходить подобные ситуации — учитывая, что среди читателей могут быть новички с совсем нулевым знанием английского (напр. учившие в школе немецкий или испанский).

          Ссылки на гитхаб по историческим причинам у нас пока нет, но насчет «орфуса» или чего-то подобного обязательно что-нибудь придумаем, спасибо за подсказку!

      2. Безусловно, руководство написаное новичком это чуть ли не единственный способ научить таких же новичков(и не только).
        Большинство материалов которые есть по реакту писаны людьми которые пришли в джаваскрипт чёрт знает откуда. С сишарпов ерлангов джавы кложура питона и пр. Они общаются на своём языке и зачастую не могут не только донести суть широким массам читателей, но также друг другу ;).

  2. теперь если на Codepen выбирать бабель и реакт, кроме белого экрана ничего не появляется(

  3. <select value={this.props.value} onChange={this.handleChange}>

    Вы навешиваете в качестве обработчика события метод класса, который использует this, без танцев с бубном.

    Поясните, откуда этот метод возьмет this? Может я что-то пропустил? Может Метод createClass непрозрачно биндит все методы класса?

    Я смог заставить этот код работать либо привязывая контекст:
    <select value={this.props.value} onChange={this.handleChange.bind(this)}>

    Либо используя стрелочную функцию в объявлении метода:

    export default class ColorPicker extends Component {
    handleChange = (e) => {

  4. Ужасный перевод. Читать прямо физически тяжело, вы хоть после гуглтранслейт редактировали? Автор, не переводи больше, пожалуйста

    1. Будем благодарны за примеры одного-двух самых тяжелых для чтения фрагментов и подсказки, как надо было перевести их лучше!

Оставить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Получать новые комментарии по электронной почте. Вы можете подписаться без комментирования.