React, или как перестать беспокоиться и начать жить
React

React, или как перестать беспокоиться и начать жить

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

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

Первое впечатление от React’a

На самом деле, привычное разделение html и js — это не разделение ответственности. В jQuery мы используем селекторы для работы с элементами и навешивания обработчиков событий, в Backbone селекторы используются в представлении для навешивания обработчиков событий, в Angular мы не используем селекторы в js, но в html вызываем Javascript-функции. В любом случае, это порождает высокую связанность (coupling).

В React же синтаксис JSX — это не HTML, это всего лишь синтаксический сахар для javascript-функций. Этот синтаксис может транслироваться в чистый javascript как непосредственно на странице, так и на этапе сборки. Для прекомпиляции можно использовать удобный и привычный инструмент (gulp, grunt, webpack, что больше нравится).

Функция render компонента в общем случае вызывается при любом изменении состояния и свойства компонента. Если для маленького компонента это не кажется страшным, то для более или менее сложного постоянная перерисовка «на каждый чих» кажется избыточной. Но в React используется так называемый виртуальный DOM (Virtual DOM). Фактически React работает с копией реального DOM’а в оперативной памяти. Все изменения вносятся в это виртуальное дерево, потом текущая копия сравнивается с предыдущей копией и, наконец, в реальный DOM вносятся только отличия между этими копиями.

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

* При условии, что компонент — самодостаточная сущность, т.е. cтруктура компонентов логично продумана. Этому стоит уделять особое внимание. Есть отличная статья в официальной документации Thinking in React, перевод на русский.

Второе впечатление от React’а

Пример компонента

Рассмотрим еще раз простой пример компонента.


import React from 'react';

import './user.scss';

class User extends React.Component {
    render () {
        return <div className='user'>
            <img src={this.props.avatar} />
            <span>{this.props.name}</span>
        <div>;
    }
}

export default User;

Здесь мы подключаем сам React, импортируем стили, если необходимо (в примере используется scss, но это не принципиально). Сам компонент в простейшем случае может иметь только функцию render, которая отрисовывает некий кусочек шаблона.

Чтобы использовать компонент, его нужно предварительно подключить, а потом можно вызвать по аналогии с обычным HTML-тегом. Свойства компонента передаются как атрибуты, а внутри компонента они доступны с помощью this.props.


import React from 'react';

import User from '../user/User.jsx';

class Container extends React.Component {
   render() {
       return <div className='container'>
           <User name='Jack Black' avatar='/images/jblack.jpg' />
       </div>;
   }
}

export default Container;

Композиция компонентов

Как мы уже говорили, компоненты можно и нужно компоновать, т.е. включать друг в друга.

Например, компонент Avatar:


import React from 'react';

import './avatar.scss';

class Avatar extends React.Component {
    render() {
        return <img src={this.props.url} />
    }
}

Используется в компоненте User:


import React from 'react';

import Avatar from '../avatar/Avatar.jsx';

import './user.scss';

class User extends React.Component {
    render () {
        return <div className='user'>
            <Avatar src={this.props.avatar} />
            <span>{this.props.name}</span>
        <div>;
    }
}

export default User;

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

Свойства, состояние, контекст

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


class ParentComponent extends React.Component {
	...
  render() {
	return <div>
  	    <ChildComponent foo='bar' />
	</div>;
  }
}

Внутри дочернего компонента свойства доступны в виде объекта this.props.


class ChildComponent extends React.Component {
	...
  render() {
	return <span>
  	    {this.props.foo}
	</span>;
  }
}

Свойства компонента неизменяемы, т.е. внутри компонента изменять this.props нельзя.

Однако компонент может иметь внутреннее состояние. Это объет this.state, доступный внутри самого компонента.


class ChildComponent extends React.Component {
	...
  toggleActive() {
	this.setState({ active: !this.state.active });
  }

  render() {
	return <input type='checkbox' checked={this.state.active} />;
  }
}

Свое состояние компонент может передать дочернему в виде свойства:


class ParentComponent extends React.Component {
	...
  render() {
	return <div>
  	<ChildComponent active={this.state.active} />
	</div>;
  }
}

class ChildComponent extends React.Component {
	...
  render() {
	return <input type='checkbox' checked={this.props.active} />;
  }
}

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


class MessageList extends React.Component {
  static childContextTypes = {
	color: React.PropTypes.string
  };

  getChildContext() {
	return { color: 'purple' };
  }
}

class Message extends React.Component {
  render() {
	return <div>
  		{this.props.text} <Button>Delete</Button>
	</div>;
  }
}

class Button extends React.Component {
  static contextTypes = {
	color: React.PropTypes.string
  };

  render() {
	return <button style={{background: this.context.color}}>
  		{this.props.children}
	</button>;
  }
}
Обратите внимание, что использование контекста делает поток данных в приложении неочевидным, запутывает код и усложняет тестирование и поддержку компонентов. Есть и хорошие примеры использования контекста в документации и в статье. В любом случае, использовать его нужно с умом и очень ограничено.

Обработка событий

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

Рассмотрим простой пример интерфейса.

Выделим следующие компоненты:

  • Users — корневой компонент для всего интерфейса
  • Search — форма поиска
  • UsersList — список пользователей
  • User — блок одного пользователя

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

Добавим в компонент Search обработчик события onChange:


onChange = e => {
  this.props.onChange && this.props.onChange(e.target.value);
};

...
<input type='text' onChange={this,onChange} />

А в компоненте Users передадим функцию filterList в качестве свойства onChange в компонент Search.


<Search onChange={this.filterList} />

Заключение

Мы рассмотрели базовые возможности и особенности React. За пределами рассмотрения оказались такие вещи как жизненный цикл компонентов, компоненты высшего порядка, stateless-компоненты, валидация свойств.

Рассылка
Подпишитесь на рассылку и получайте дайджест новостей и статей.
Никакого спама!
Подписаться