Starting with React

Что и зачем?

Очень коротко из оф. доки: "React is a JavaScript library for building user interfaces".

  • Можно самостоятельно (aka руками) манипулировать элементами страницы используя JavaScript и DOM API, но React предлагает уже готовый консистентный подход.

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

  • Уже предлагается data binding с эффективным рирендерингом DOM'а. Data binding - это, к примеру, когда я просто изменяю поле в JavaScript объекте, а React за меня отрисует изменение на странице. То есть нам меньше надо думать о том как лучше всего работать с DOM, мы больше концентрируемся на самой business logic. Считается, что React предлагает один из самых быстрых рендерингов DOM, используя различные оптимизации и подходы, такие как Virtual DOM.

  • Добавляя другие библиотеки экосистемы React (routing, i18n, etc) мы можем расширять возможности нашего приложения не меняя фундамент. Это же может выступать как минус, когда у других фреймворков много фич уже встроено изначально. Но это может стать огромным плюсом, к примеру, если надо постепенно внедрять React в уже существующее приложение.

  • TODO: add more statements

В чем особенности React (в сравнении с Angular)?

  • "Из коробки" React – это только отслеживание изменений и рендеринг. Для отправки HTTP запросов, роутинга, более удобной работы DOM и тестирования компонентов надо подключать отдельные библиотеки. Зачастую, как для роботы с HTTP, их несколько (superagent, axios) и "приходиться выбирать".

  • Шаблон компонента описуется на JS в коде самого компонента. Нет привычных *ngIf и *ngFor . Вместо этого, используем тернарные операторы и массивы из компонентов соответственно.

  • За своевременное и правильное обновление состояния компонента и последующий рендер отвечает сам разработчик. Сами по себе события браузера (setTimeout, XmlHttpRequest, Element.addEventListener) не приводят к проверке дерева компонентов на наличие изменений. В Angular за это отвечает NgZone.

Как создать костяк проекта с нуля?

Можно поиграться с React на готовых playground страницах, когда не нужно ничего у себя устанавливать: https://reactjs.org/docs/getting-started.html#online-playgrounds

Можно добавить React к новому или существующему приложению без каких-либо инструментов сборки и упаковки, т.е. просто подключив <script src="*.js">: https://reactjs.org/docs/add-react-to-a-website.html

Для более удобной разработки обычно используются готовые инструменты:

С чего начинается работа React app?

Обычно точкой входа (entry point) является вызов ReactDOM.render(), к примеру:

ReactDOM.render(<App />, document.getElementById('root'));

Второй аргумент говорит React'у какой существующий элемент на странице он должен держать под своим контролем. Т.е. в этом примере выше у нас имеется некая index.html, где есть пустой <div id="root"></div>. А первый аргумент передает "корневой" компонент React'а, в данном случае это App компонент. Это особенный объект самого React'а, который обычно получают при вызове React.createElement().

Также, в этом примере виден нестандартный для JavaScript синтаксис: <App />. Это JSX. Он является расширением к JavaScript, который или "интерпретируется" на ходу в браузере или изначально транспайлится в JavaScript (обычно во время сборки приложения). По факту этот <App /> "превращается" в вызов функции React.createElement(App), где App - это JavaScript class, описывающий данный компонент. Обычно проекты пишут на JSX.

Эти начальные концепты хорошо описывает оф. документация:

React приложение - это дерево компонентов

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

Официально что такое компонент: "Conceptually, components are like JavaScript functions. They accept arbitrary inputs (called “props”) and return React elements describing what should appear on the screen."

Есть два способа определения компонентов: как обычная JavaScript функция или как ES6 class.

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}
class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

Компоненты, определенные через функцию, еще называют глупыми, функциональными или state-less. Они используются на "низком уровне" (дерева компонентов), редко содержат дочерние компоненты и отвечают, по большей части, только за рендеринг небольшого элемента. Бизнес логику не выполняют, побочные эффекты (к примеру запросы на сервер) не создают.

Изначальная "настройка" компонента делается через props параметр. К примеру, в рамках JSX мы передаем name как строку:

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

ReactDOM.render(
  <Welcome name="Sara" />,
  document.getElementById('root')
);

Детали: https://reactjs.org/docs/components-and-props.html

У каждого компонента есть свое личное состояние (state)

Изначально переданные props считаются read-only информацией, а для изменяемой информации используется this.state компонента. Обычно его заполняют изначальными default значениями и информацией из props. Про отличия между props и state: https://reactjs.org/docs/faq-state.html#what-is-the-difference-between-state-and-props

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { date: new Date(), x: props.x, y: props.y };
  }

  render() {
    return (
      <div>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
        <div>Position: ({this.state.x}, {this.state.y}).</div>
      </div>
    );
  }
}

Для изменения состояния нужно использовать специальную функцию this.setState(), чтобы React знал об изменениях state и мог соответственно менять DOM. Состояние компонента недоступно другим компонентам, т.е. другие компоненты не знают stateful или stateless другой компонент, который они используют. Влиять на state другого компонента можно динамически передавая ему соответствующие props, что означает структуру компонентов "сверху-вниз".

У каждого компонента есть lifecycle с соответствующими callback функциями, где можно добавить свою логику на определенном этапе жизненного цикла компонента. К примеру, самые известные из lifecycle callbacks:

  • componentDidMount() - компонент уже отрисован (добавлен в DOM), т.е. на данном этапе можно запустить асинхронные процессы связанные с business logic компонента, к примеру инициировать запрос на сервер, запустить таймер и т.д.

  • componentWillUnmount() - здесь можно произвести деинициализацию ресурсов и процессов прежде чем компонент будет удален из DOM, к примеру отменить периодический таймер и т.п.

Смотрим детали по этой теме в оф. доке:

Важность деинициализации / описки

Мы привыкли, что после нас созданные объекты чистит garbage collector. Но это не работает c обработчиками асинхронных событий. О каких событиях идет речь? Ниже пару примеров

  • document.body.addEventListener('click', …) добавленный что-бы закрывать модальное окно по клику за его приделами. Останется висеть после ухода со страницы с модалкой, если не отписатьcя. Будет "кушать память" (ведь слушатель содержит ссылку на экземпляр компонента модалки) и сыпать ошибками в консоль.

  • setInterval() добавленный для периодических запросов на сервер или вывода текучего времени в компоненте Часы.

  • Подписка на поток событий в RxJS, изменения состояния в Redux или на любой другой global state.

Как рендерить по условию?

При разработке компонента часто требуется банальный if else подход к выбору того, что отобразить на странице в зависимости от некоего условия, к примеру boolean флага в this.state. Смотрим оф. доку: https://reactjs.org/docs/conditional-rendering.html

Как рендерить динамические списки?

Очень частый кейс - это отображение неких табличных данных или списков. С React это делается легко средствами самого JavaScript, но есть React requirements такие как обязательное наличие key property у каждого элемента списка. Смотрим оф. доку: https://reactjs.org/docs/lists-and-keys.html

Как сделать ввод пользователя через form и input элементы?

Поскольку HTML input элементы имеют свое личное состояние, то в мире React есть два подхода при работе с ними:

  • controlled components - это когда состояния поля ввода (input element) контролируется реактом через this.state. В большинстве случаев рекомендуют и используют этот подход.

  • uncontrolled components - обратная ситуация, чаще упоминается при плавной миграции уже существующих приложений

Оф. дока по этой теме: https://reactjs.org/docs/forms.html. Часто используют готовые библиотеки для работы с формами, которые содержат уже встроенные возможности по подключению фреймворков валидации таких как yup и т.п. К примеру, Formik - одна из известных библиотек.

Как формировать список CSS классов элемента?

В React нет умной функции генерации атрибута class HTML элемента. Для этого используем библиотеку classnames или же её более легковесную альтернативу clsx.

classNames(
  'foo',
  { bar: true, duck: false },
  'baz', 
  { quux: true }
); // => 'foo bar baz quux'

Что если изменение состояния одного компонента должно влиять на соседнего компонента?

К примеру, у нас есть компонент Parent, у которого есть child компоненты A и B. Когда меняется состояние компонента A, мы хотим чтобы B себя тоже изменил и перерисовал по своей логике. React сразу нам говорит: не пытайтесь синхронизировать this.state компонента A с this.state компонента B. Наоборот, выделите те общие данные и вынесети их в this.state общего компонента выше, т.е. "Lifting State Up". Тогда мы убираем это общее состояние из this.state обоих компонентов, они теперь будут получать это общее состояние через props. А для того, чтобы оба компонента A и B как и ранее могли продолжать влиять на это общее состояние, Parent передает им обоим свои callbackS через те же props. Вот их теперь и будут вызывать A & B вместо внутреннего this.setState(). Внимательно изучаем оф. доку: https://reactjs.org/docs/lifting-state-up.html

У меня есть схожие компоненты, как мне сделать их иерархию для code re-use как мы это обычно делаем в мире ООП?

React нам сразу говорит: нет, не делайте inheritance, лучше используйте composition. Оф. дока: https://reactjs.org/docs/composition-vs-inheritance.html

Какие есть общие советы по созданию и организации React компонентов?

Официальная дока дает неплохой пример, когда мы знаем как будет выглядеть JSON от сервера и получили мокап страницы от дизайнера. Здесь продемонстрирован пример размышлений: как лучше разбить на компоненты, как организовать state и все ли должно быть state'ом, и т.п. https://reactjs.org/docs/thinking-in-react.html

Для начинающих у нас есть вводные видео о построении компонентной структуры онлайн чата (запись с митапа).

Зачем и почему Redux?

Redux – один из инструментов, упрощающий разработку комплексных приложений (для простого приложения он будет избыточен), за счет ввода глобального состояния приложения и его использования как единого источника данных (aka "правды") для компонентов. Принято использовать в паре с библиотекой react-redux. Она позволяет создавать компоненты, которые не знают о существовании глобального состояния, но при этом получает из него обновленные данные и направляют в него события. Достигается это за счет техники higher-order component (HOC).

О случаях использования Redux, его возможностях и компонентах высшего порядка можно узнать из видео записей нашей серии встреч React Redux for Tower of Hanoi game.

TODO

Ресурсы

  • https://reactjs.org/docs - если время позволяет или хочется структурировать уже имеющиеся знания, то просто читаем от корки до корки официальную документацию

  • https://www.robinwieruch.de/react-libraries - проводит за руку по экосистеме React, какие есть библиотеки и подходы, инфа за Jan'2020

  • TBD

Last updated