React - одна из самых популярных JS-фреймворков, и я хочу поделиться с вами своим первым опытом.

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

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

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

Добро пожаловать в мой MVP

В мае прошлого года меня пригласили присоединиться к небольшой команде старших программистов в проекте по созданию процессора онлайн-платежей с использованием ряда передовых технологий для крупного игрока в сфере платежных шлюзов в Латинской Америке.

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

Наша последняя поставка включала быструю интеграцию с Prestashop (который я, кстати, не кодировал, потому что интеграция должна была быть простой, помните?)

Мои самые важные требования были немного жесткими:

  • Наш пользовательский интерфейс должен работать в любом браузере на компьютере или мобильном устройстве. Пользователи из Латинской Америки не очень часто обновляют программное обеспечение, поэтому, даже если «любой» - такое сильное слово , это означает, что мы должны перейти на Windows XP и IE. 9.
  • Время загрузки должно быть минимальным. Медленные соединения всегда являются источником проблем на этом рынке, поэтому мы должны свести размер пакета к минимуму.
  • Мы должны сделать это правильно. Сказать, что вы создаете «минимально жизнеспособный продукт», не означает, что вам может сойти с рук дешевый код, наугад скомпонованный с помощью какого-то скрипта. Мы можем вырезать функции, но мы должны предоставлять готовый корпоративный код предсказуемым образом.
  • Мы должны сделать это быстро. Нам изначально дали три месяца. Для всего решения, а не только для пользовательского интерфейса. Звучит немного безумно, и, конечно же, наш проект отстал от графика. Но мы также сделали намного больше того, что от нас ожидалось, так что все остались довольны.

Почему я выбрал React и Redux?

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

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

Создание эскизов и прототипов

Кодирование решения иногда является самой простой частью проекта. Другое дело - знать, что нужно кодировать. В этом проекте оказалось, что даже когда я использовал недавно изученный набор инструментов и библиотек, моей самой большой проблемой было собрать все заинтересованные стороны на одной странице в отношении функций и внешнего вида пользовательского интерфейса. Некоторых из них смутил тот факт, что пользовательский интерфейс должен был вести себя хорошо на мобильных устройствах, но это было решение HTML5 / CSS3, не родное. В конце концов, мне удалось обеспечить удовлетворительное взаимодействие с большинством устройств, но я не думаю, что в настоящее время вы можете получить первоклассный пользовательский интерфейс на каждой платформе, используя единую базу кода.

Учитывая очень ограниченную площадь экрана мобильных устройств, мое первоначальное предложение было очень минималистичным. Я старался свести элементы пользовательского интерфейса к минимуму, чтобы сосредоточить внимание пользователя на его немедленном выборе. Однако мое мнение было сразу отклонено, потому что мои клиенты хотели привнести некоторые характеристики прежнего решения в новое. Чтобы придумать окончательный дизайн, потребовалось много итераций.

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

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

Хотя это хороший инструмент, и конечные результаты могут выглядеть довольно внушительно, я думаю, что мог бы пропустить Framer для этого конкретного проекта, потому что мои потребности были довольно простыми, и было бы быстрее просто пойти дальше и начать писать HTML и Javascript. . Я думаю, что Framer представляет наибольшую ценность при создании прототипов сложных взаимодействий с собственным пользовательским интерфейсом, и я должен сказать, что даже если я понимаю, что они хотели каким-то образом быть дружелюбными к дизайнерам, меня оттолкнул их выбор Coffeescript за свой язык сценариев. Теперь, когда команда Framer подумывает отказаться от него в пользу обычного Javascript, многие люди вроде меня должны быть счастливее.

Настройка проекта

Есть много способов начать проект React, но я бы посоветовал вам не начинать с нуля, используя npm для объединения всех ваших библиотек. Это займет много времени и может привести к ошибкам. Сегодня вы, вероятно, могли бы выбрать Create React App, очень хороший стартовый проект, поддерживаемый теми же людьми, которые создают React. Однако для этого проекта я выбрал React Slingshot. Он создает очень полную настройку, которая объединяет несколько инструментов, которые сделают вашу разработку легкой и приятной:

  • Вавилон. Поддержка современных функций языка Javascript, также называемого EcmaScript 6 или для краткости ES6, присутствует не во всех браузерах единообразно. Если вы хотите его использовать - и поверьте мне: вы это делаете, то вам определенно нужен транспилятор. Этот инструмент принимает современный код и выводит код, который будет работать практически в любом браузере. Теперь вы можете писать код, используя ваши любимые языковые конструкции: функции стрелок, деструктуризация, отдых / распространение, асинхронные функции. и многое другое. Babel также поддерживает JSX React, который представляет собой синтаксис XML, используемый для объявления внешнего вида ваших компонентов, и это просто синтаксический сахар по сравнению с композицией функций:
// This looks like HTML but it's actually in a Javascript source 
// file. Look at the variable declaration:
var profile = <div>
  <img src="avatar.png" className="profile" />
  <h3>{[user.firstName, user.lastName].join(' ')}</h3>
</div>;
// Now, look at the syntax transformation output by Babel:
var profile = React.createElement("div", null,
  React.createElement("img", { src: "avatar.png", className: "profile" }),
  React.createElement("h3", null, [user.firstName, user.lastName].join(" "))
);
  • Redux. Это одно из новейших предложений из окопов функционального программирования: контейнер с предсказуемым состоянием для приложений JavaScript. С помощью этой библиотеки состояние вашего приложения моделируется в виде дерева и управляется централизованно. Вы можете изменить его очень упорядоченным образом, разместив действия, которые обрабатываются редукторами, которые на самом деле являются просто функциями. Взгляните на этот чрезмерно упрощенный код из официального репозитория:
// this is a reducer
function counter(state = 0, action) {
  switch (action.type) {
  case 'INCREMENT':
    return state + 1
  case 'DECREMENT':
    return state - 1
  default:
    return state
  }
}

// Create a Redux store holding the state of your app.
// Its API is { subscribe, dispatch, getState }.
let store = createStore(counter)
// The only way to mutate the internal state is to dispatch an action.
// The actions can be serialized, logged or stored and later replayed.
store.dispatch({ type: 'INCREMENT' })
// 1
store.dispatch({ type: 'INCREMENT' })
// 2
store.dispatch({ type: 'DECREMENT' })
// 1
  • SASS / PostCSS. Я больше не занимаюсь обычным CSS. SASS для CSS - это то же самое, что Babel для Javascript: он предоставляет множество хороших конструкций, которые помогут вам писать лучший код. PostCSS - это расширяемая структура, которая дает вам еще больше возможностей, таких как префиксы поставщиков.
  • ESLint. Этот инструмент поможет вам создать качественный код, проверяя каждый исходный файл на соответствие предпочтительному набору правил, чтобы убедиться, что вы соблюдаете хорошие стандарты кодирования. И это очень настраиваемый.

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

Система сборки

До этого проекта моя система сборки была реализована в скрипте Gulp. Gulp похож на другие инструменты в том смысле, что вы пишете ряд задач и объединяете их все вместе, чтобы построить свое программное обеспечение. Это крайне необходимо, и в зависимости от шагов, необходимых для создания конечных результатов, иногда сценарий может стать громоздким. Фактически, системы сборки обычно являются источником опасений и проблем, особенно при больших установках. Да, я помню системы Java, с которыми работал в 2000-х!

Введите Webpack. Говорят, что это сборщик модулей, который берет модули с зависимостями и генерирует статические ресурсы, представляющие эти модули, но я уверен, что вы почувствуете, что это описание не соответствует действительности. Во-первых, предположим, что подход Webpack к созданию ваших выходных ресурсов более декларативен, чем подход Gulp и других инструментов. Вам не придется писать много специальных сценариев для обработки специфики вашего проекта, потому что Webpack имеет множество функций и его легко настраивать. Конечно: это тоже немного сложно, но как только вы ознакомитесь с документацией, все будет в порядке. Давайте посмотрим, что Webpack может для вас сделать:

  • Он понимает последнюю версию Javascript: просто используйте import в своем основном файле, и все ваши зависимости будут автоматически включены в ваш пакет.
  • Он также может «импортировать» изображения, значки и прочее. Звучит немного безумно, но работает очень хорошо.
  • Он поддерживает разделение кода. Это много, потому что вы можете сохранить свой основной пакет минимальным и загружать дополнительный код динамически, когда вам это нужно. Ваши пользователи будут довольны небольшим временем загрузки! Как ты это используешь? Что ж, просто используйте System.import для извлечения дополнительных модулей, и все снова произойдет автоматически. Как вы понимаете, вы можете сразу же обработать случай, когда ваш модуль не загрузился.
function onClick() {
    System.import("./module").then(module => {
        module.default;
    }).catch(err => {
        console.log("Chunk loading failed");
    });
}
  • Он поддерживает горячую замену модуля (HMR). Это здорово, когда вы разрабатываете, потому что ваш измененный код перезагружается автоматически (по крайней мере, в большинстве случаев). Вам не нужно перезагружать систему, чтобы увидеть изменения. И с React Slingshot это тоже было настроено. Это просто сработало.

Я просто не выбираю несколько хороших функций, о которых, я знаю, вы будете рады услышать, но Webpack - это гораздо больше. Я рекомендую вам попробовать, когда появится ваш следующий проект Javascript.

Я хочу увидеть код прямо сейчас!

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

Типичная система React начинается с простого HTML-файла, который загружает ваш основной пакет и настраивает область, в которой ваш основной компонент будет отображать себя:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta charset="UTF-8">
  <title>This is just an example</title>
</head>
<body>
  <div id="app"></div>
  <script src="bundle.js"></script>
</body>

Взгляните на div с идентификатором приложения, а затем посмотрите на основной файл Javascript:

import React from 'react';
import {render} from 'react-dom';
import { Provider } from 'react-redux';
import configureStore from './store/configureStore';
const store = configureStore();
render(<Provider store={store}><App/></Provider>, document.getElementById('app'));

Этот render() вызов создаст экземпляр вашего основного компонента и вставит его прямо там, где вы просили. Это все, что вам нужно для начальной загрузки приложения React + Redux. Файл configureSore может выглядеть примерно так:

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../reducers';
export default function configureStore(initialState) {
  return createStore(
    rootReducer, initialState, applyMiddleware(thunk)
  );
}

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

import { combineReducers } from 'redux'
import todos from './todos'
import counter from './counter'
import someOtherReducer from './someOtherReducer'
export default combineReducers({
  todos,
  counter,
  someOtherReducer
});

Как выглядит компонент React?

Что ж, вы уже видели типичный код React:

render(<Provider store={store}><App/></Provider>, document.getElementById('app'));

Здесь вы можете оценить типичный синтаксис HTML внутри JS-вызова: Provider - это компонент, который объединяет ваш основной App компонент, предоставляя ему доступ к настройке Redux. Компонент, написанный с использованием синтаксиса класса ES6, может выглядеть так просто:

class App extends React.Component {
  render() {
    return (
      <div className="app">
        <h1>Welcome to {this.props.name}</h1>
      </div>
    );
  }
}

Когда ваш компонент создается, он получает ряд свойств, и вы получаете доступ к ним с помощью объекта this.props.

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

render(props) {
  return (
    <div className="app">
      <h1>Welcome to {props.name}</h1>
    </div>
  );
}

Как видите, такой компонент - это просто функция, которая принимает свои свойства в качестве аргумента, а затем возвращает тот же код JSX, который вы бы вернули из метода render().

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

Разговор с сервером

Если вы внимательно прочтете код, вы заметите ссылку на модуль с именем redux-thunk.. Это интересно: обычно ваши действия Redux передаются как простые функции вашим компонентам React, и они вызываются или отправляются , в результате взаимодействия с пользователем, нажатия кнопки или чего-то еще, а затем ваш редуктор обновляет состояние вашего приложения и сигнализирует о реакции на новое состояние, чтобы ваши компоненты могли обновиться, чтобы отразить его.

Но что происходит, когда вам нужно отправить действие в другом случае, например, как вы запрашиваете данные с сервера и как вы их обрабатываете, когда они поступают? Что ж, Redux поддерживает функцию, называемую промежуточное ПО: сторонние точки расширения между отправкой действия и моментом его достижения редуктором.

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

Хотите увидеть пример? Это вымышленное действие, которое просто запрашивает данные о продажах из определенного региона страны:

export function requestSalesData(region) {
  return { type: types.REQUEST_SALES_DATA, region };
}

Хорошо ... теперь ваш пользовательский интерфейс может запрашивать данные: ваш редуктор получит этот объект, который вы вернете, с указанием вашего намерения и даже региона, который вас интересует, и ... что? Редукторы не должны обрабатывать обмен данными с сервером и с сервера, они просто получают данные и применяют их к текущему состоянию приложения для создания следующего, свежего состояния.

Где и как вы обрабатываете фактическую загрузку с сервера? С Redux Thunk ваше действие может возвращать функцию вместо простого объекта, поэтому теперь мы можем написать действие, которое выглядит следующим образом:

export function requestSalesData(region) {
  return (dispatch) => {
    return fetch(`https://yourserver.com/sales/${region}`), {
      method: 'GET',
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json',
        'Authorization': 'Bearer ' + token // JSON Web Token! :)
      })
      .then(response => response.json())
      .then(json => dispatch(updatePaymentReceivers(json)));
  }
}

Теперь мы можем вернуть функцию вместо простого объекта. И поскольку эта функция получает диспетчер, мы можем удерживать ее, пока не получим фактические данные! Только обратите внимание на последний then звонок.

Я использую Fetch ​​, чтобы общаться с сервером, потому что это удобно и просто, и когда-нибудь это будет доступно во всех браузерах. Я использую то, что мы называем polyfill, чтобы предоставить его, когда браузер этого не делает. Как видите, Fetch поддерживает обещания, которые упрощают асинхронные вызовы. Вы просто ждете поступления данных и then() обрабатываете их.

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

Хотите узнать больше об этом проекте?

В этой статье почти не рассматривается пользовательский интерфейс, но мы использовали ряд интересных новых технологий, таких как C ++ 11, Docker и Kubernetes. Если вы хотите узнать больше, есть еще пара статей, написанных нашим экспертом по C ++:





Тестирование

Обычно я пишу свои модульные тесты перед написанием компонентов, которые собираюсь тестировать. Когда мне нужен компонент с дополнительными возможностями, я сначала пишу тесты, чтобы проверить их. Когда тесты не проходят, я модифицирую компонент, чтобы он имел желаемые функции, пока тесты не станут зелеными. Эта дисциплина называется TDD или Test Driven Development и избавила меня от множества неприятностей. Даже если поначалу это кажется вам неестественным, я рекомендую придерживаться этого правила.

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

Для этого проекта я использовал Мокко с Чай. Это довольно изящное сочетание, выразительное и мощное. Я столкнулся с небольшими проблемами при тестировании обещаний, а затем с некоторыми ложными срабатываниями ... В конце концов, я все разобрал и остался очень доволен.

Еще один вид тестов, который очень важен, особенно когда ваш код нужно протестировать на практически любой платформе, - это автоматизированные функциональные тесты. Для этого я написал набор тестов, используя Selenium и Webdriver.io, и мы оплатили услуги Browserstack для запуска нашего тесты в каждой нужной нам комбинации операционной системы и браузера. Я обнаружил и устранил ошибки, которые присутствовали только в некоторых комбинациях, но не в других, я просто запускал все свои тесты автоматически, когда я вносил соответствующие изменения, и проверял, все ли в порядке ... Это было потрясающе.

Заключительные слова

Это MVP, поэтому рано или поздно я внесу ряд улучшений. Например, использование React Thunk, хотя и простое, содержит много логики там, где это не всегда удобно. Есть еще одно очень привлекательное промежуточное ПО, называемое Redux Saga, которое, на мой взгляд, представляет собой более совершенную модель. С его помощью вы можете упростить свои действия и редукторы, и у вас есть специальные компоненты, называемые саги, которые активируются при отправке некоторых действий, имея возможность выполнять внутренние операции и другие вещи, публикуя последующие действия. самих себя. Аккуратный.

Мог бы я снова использовать React? Конечно. Я даже очень скоро попробую React Native. Это не значит, что меня не интересует какой-либо другой фреймворк, но я думаю, что React опережает другие, особенно более крупные и сложные, такие как Angular. Я попробовал Angular 1, и он мне не понравился. Angular 2 кажется лучше, но все же я думаю, что другие, менее традиционные подходы для работы на стороне клиента лучше. Просто проверьте, что делают люди, с помощью Vue.js или Inferno.

Есть несколько вариантов Redux, другие контейнеры состояний, которые люди используют с радостью. Отметьте MobX, если вам кажется, что Redux - это слишком много для вас. И если у вас есть сложные запросы к вашему бэкэнду, обязательно отметьте Relay и GraphQL.

Вы, наверное, догадались, что комбинация React + Redux - это отход от традиционных архитектур MVC / MVVM / любых других архитектур в сторону функциональной / неизменяемой области. Я с радостью это приветствую. Думаю, нам это было нужно, но как вы думаете?

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