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

Тем более, что я много работал над мобильными приложениями, я решил попробовать React Native. Поскольку это было в начале / середине 2017 года, библиотека была относительно молодой. Раньше я никогда толком не работал с JavaScript, за исключением одной маленькой штуки на сайте «интегрировать гистограммы». В целом React хорош тем, что у него отличная кривая обучения - ему очень легко научиться. В этой статье я в общих чертах опишу свой опыт работы с React за последние годы, а также несколько небольших фрагментов кода, чтобы превратить его в статью кода.

Реагировать

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

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

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

Еще одна замечательная вещь в React - это его широко распространенные альтернативы. Вы изучаете концепцию свойств, состояния, жизненного цикла / рендеринга один раз, и вы можете создать любое крупное веб-приложение, которое только можете придумать. Для создания нативных мобильных приложений есть React Native, а для настольных приложений можно использовать Electron (так построен Slack). Таким образом, изученные концепции можно повторно использовать или даже совместно использовать код между проектами.

Теперь вы могли подумать, что React - это склеенная библиотека. Это не совсем так (или не обязательно плохо). React использует стиль JavaScript и XML (JSX). Короче говоря, это означает, что ваш HTML-код будет жить вместе с кодом JavaScript (в том же файле). Сначала это звучит странно, но когда вы к этому привыкнете, это будет круто. Таким образом, это в основном означает, что ваше представление, контроллер и модель могут находиться в одном файле / компоненте. Если ваше приложение остается небольшим, при таком подходе все может быть в порядке. Но если он становится все больше и больше и все больше людей начинают работать над одной и той же кодовой базой, все становится интереснее.

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

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

Настройте ЛИНТЕР перед началом работы в команде

Это то, что я определенно понял, когда работал с несколькими людьми в проекте JavaScript. На мой взгляд, помимо надлежащей документации, необходима хорошая настройка для предотвращения доступа к неопределенным переменным. Если вы используете TypeScript, а не JavaScript, большинство из этих вещей уже должно быть настроено.

Линтеры гарантируют, что написанный вами код всегда выглядит так, как вы этого хотите. Eslint - очень популярная утилита линтинга для JavaScript, которую вы настраиваете под нужды своего проекта. Самое замечательное, что после того, как вы это настроите, вы можете использовать плагины IDE, которые автоматически проверяют ваш код и дают вам подсказки / ошибки / предупреждения, если код не применяется к правилу. Также неплохо исправить ошибки линтера при сохранении файла. Если вы объедините процессы линтинга с git-hooks, вы можете быть уверены, что исходный код проекта соответствует определенному стандарту качества.

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

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

В JavaScript есть такие вещи, как Flow, которые работают как средство проверки статического типа для вашего проекта. Статическая проверка типов означает, что ошибки обнаруживаются при кодировании, но теоретически может все же пропустить ошибки, о которых вы не знаете (например, в глубоко вложенных объектах). В дополнение к этому существуют средства проверки типов во время выполнения, такие как PropTypes для React, которые обнаруживают еще несколько ошибок во время выполнения (но только для свойств!), Что интересно для тестирования.

В TypeScript, по сравнению с Flow, многое уже настроено заранее. Это не обязательно означает, что Flow лучше или хуже TypeScript, но я лучше познакомился с TypeScript. Это вынудило меня написать определение типа для большинства вещей, чего не было для Flow (возможно, я настроил его неправильно). Кроме того, Flow иногда потреблял много заряда батареи на моем MacBook, что полностью согласуется с этой отличной статьей.

Обзоры кода ›TypeScript

В общем, добавление типов в JavaScript - это хорошо, тем более, что он заранее выявляет некоторые ошибки и выполняет большую часть документации для вашего кода (см. Typedoc). Интересно, что если вы читали исследование о Печатать или не печатать: количественная оценка обнаруживаемых ошибок в JavaScript, которое я нашел в этой замечательной статье, около 80% ошибок не обнаруживаются TypeScript. Типовые ошибки составляют около 20% ошибок в вашем проекте (в среднем), остальное в основном связано с ошибками спецификации! Итак, в заключение, TypeScript (или других дополнений типа JavaScript) будет недостаточно, чтобы отловить все ошибки (или даже половину) в вашем коде.

Проверки кода и разработка через тестирование (TDD, напишите тест перед написанием кода) и программирование пар событий могут решить до 80% распространенных ошибок. Еще одно интересное утверждение из этой статьи, которое я рекомендую прочитать, заключается в том, что час обзора кода экономит в среднем 33 часа обслуживания. У меня был аналогичный опыт, когда обзор (вашего собственного) кода вместе с колледжем привел к огромному количеству обнаруженных ошибок.

Структура проекта

Очень важно, чтобы вы решили, как должна выглядеть структура вашего проекта, прежде чем начинать проект (это имеет смысл). Особенно когда работаешь в команде. На мой взгляд, лучшие практики для этого следует где-то задокументировать, даже если это просто файл readme. Есть еще вещи, которые я когда-то задавал себе под сомнение:

  • Назовите файл Dashboard.js или index.js и просто поместите его в папку components / Dashboard?
  • Имейте логическую структуру файлов, такую ​​как src / components src / container src / config, или более поведенческую, например src / usercontrols src / adminelements src / utils
  • При написании тестов: тесты E2E обычно помещаются в корневую папку проекта / e2e или в аналогичную папку. Но как насчет модульного и интеграционного тестирования? Поместить их в папку __test__ в src / или в иерархии тестируемого элемента?

В общем, шаблонный код или примеры проектов могут помочь в выборе структуры проекта. Мне лично нравится называть файлы в качестве имени компонента, как в Java.

Тестовая среда

В целом, более тщательное тестирование дает вам больше уверенности в том, что в вашей системе меньше ошибок. Существуют разные уровни тестирования, начиная от модульных и интеграционных тестов до сквозных (E2E) и приемочных тестов. Я написал статью о тестировании E2E в React Native. В любом случае, я хотел бы поделиться здесь своим опытом тестирования.

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

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

Конечно, большинство ошибок невозможно предотвратить. Допустим, вы уменьшили версию сборки minSDK для своего Android-приложения, поскольку новая библиотека требует / рекомендует это. Но вы забыли, что ваше приложение должно иметь определенную версию minSDK, что приводит к сборке, которая не будет работать на всех устройствах. Этого можно было избежать, разрешив проверять ваши изменения другому человеку или даже вместе (экспертная оценка). Запросы на вытягивание являются здесь очень распространенной техникой.

Непрерывная интеграция и непрерывная доставка

В большинстве случаев разработчикам приходится выполнять повторяющиеся задачи для выпуска. Это часто включает в себя такие вещи, как запуск тестов / линтеров, комплектация, приемочные тесты и загрузка. Несомненно, это требует времени и нервов, но может быть сведено к минимуму с помощью внешних служб CI / CD. Конечно, вы можете получить DevOps-инженера, который настроит это на вас, но часто это требует много времени и опыта. Исключением может быть то, что вы обрабатываете конфиденциальные данные (ваши клиенты доверяют вам) и, следовательно, должны делать это самостоятельно.

Redux

Когда проект станет больше, вы можете (должны) добавить библиотеку, которая обеспечивает однонаправленный поток данных для вашего приложения. Один из очень известных паттернов для этого - Flux. Поскольку это всего лишь шаблон, вы можете выбирать из различных реализаций библиотеки, например Redux или MobX. Я лично работал с Redux только в прошлом, и это действительно здорово, когда вы привыкли к основным концепциям.

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

Кроме того, тестирование станет проще с помощью redux по сравнению с самостоятельным управлением состоянием. Это связано с тем, что существуют определенные процессы, для которых состояние изменяется, в том смысле, что только определенные методы (действия) могут изменять состояние. Состояние является частью вашего хранилища redux и может быть разделено на разные части. Допустим, вы хотите разделить app / settings и app / userProfile. Это имеет смысл, тем более что один редуктор имеет доступ только к этому конкретному подсостоянию.

Промежуточное ПО Redux

Промежуточное ПО Redux настраивается «между» вашим приложением и хранилищем redux для выполнения действий. Одна вещь может заключаться в том, чтобы сохранить части вашего магазина, чтобы ваше приложение могло работать в автономном режиме. Еще одна вещь, которая определенно имеет смысл, - это промежуточное программное обеспечение регистратора (redux-logger). С его помощью вы можете увидеть, какое действие влияет на ваш магазин, включая временные метки и различия состояний до / после отправки действия.

Теперь, когда управление состоянием приложения обработано, как насчет обработки побочных эффектов, таких как простая выборка профиля пользователя после успешной аутентификации на сервере? Это можно сделать с помощью промежуточного программного обеспечения redux, такого как redux-saga или redux-thunk. Короче говоря, in дает вам возможность получать данные, просматривать данные из хранилища redux и запускать действия redux для изменения хранилища / состояния вашего приложения. Все это, конечно, можно сделать и в компонентах приложения, но рано или поздно ваш код превратится в беспорядок и больше не будет тестироваться.

В заключение: вы не можете (и не должны) выполнять вызовы на сервер или аналогичный в редукторе redux. Действие redux отправляется редуктором redux, который затем вносит изменения в свое хранилище, не дожидаясь секунд обработки запроса сервера. Вот для чего нужно промежуточное ПО.

Асинхронный / ожидание

Многие разработчики JavaScript любят синтаксис async / await, который значительно улучшает читаемость операторов Promise.then (). Использование async / await может оказаться весьма полезным, особенно если у вас есть вложенные операторы then (). Я не хочу объяснять, как работает async / await, есть много отличных статей, посвященных этому.

const fetchFood = new Promise((resolve, reject) => { 
  setTimeout(() => resolve('food'), 1000); 
});
const fetchWater = new Promise((resolve, reject) => { 
  setTimeout(() => resolve('water'), 300); 
});
const fetchBread = new Promise((resolve, reject) => { 
  setTimeout(() => resolve('bread'), 500); 
});
const main = async () => {
  console.time('time');
  const food = await fetchFood;
  const water = await fetchWater;
  const bread = await fetchBread;
  console.timeEnd('time'); // ~1000ms
  
  // equivalent
  // const [food, water, bread] = await Promise.all([fetchFood,   fetchWater, fetchBread]);
}
main();

Интересно то, что выполнение этих трех обещаний занимает всего около 1 секунды, это самый большой тайм-аут. Конечно, это связано не с async / await, а с обещаниями. Недавно я узнал, что вы можете использовать Promise.all, который разрешается, когда выполняются все принятые обещания.

Оператор остального спреда

Остальные операторы распространения позволяют расширять объекты и массивы или проще: объединять их. Операция создаст новый объект или массив:

const one = [1, 2];
const two = [3, 4];
console.log([...one, ...two]); // [ 1, 2, 3, 4 ]
console.log([...one, ...two, 5, 6]); // [ 1, 2, 3, 4, 5, 6 ]
console.log([...one] === one); // false

Другой пример - расширение объектов. Здесь важно не забывать о глубоких вложенных объектах, иначе они будут переопределены:

const someObject = {
  magicValue: 42,
};
const hugeObject = {
  ...someObject,
  someValue: 37,
  deepObject: {
    foo: [42, 43],
  }
}
const overridenObject = {
  ...hugeObject,
  someValue: 24,
  deepObject: {
    someArray: [91, 21], // foo[] will be overridden
  }
}
const notOverridenObject = {
  ...hugeObject,
  anotherValue: 37,
  deepObject: {
    ...hugeObject.deepObject, // extend nested object
    someArray: [42, 43],
  }
}

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

const someSumFunction = (...args) => {
  return args.reduce((sum, value) => sum + value)
}
console.log(someSumFunction(1, 2, 3));

Этот метод просто принимает параметры, которые будут преобразованы в массив, и возвращает сумму его значений.

Реагировать на производительность

В React есть жизненный цикл компонентов, который очень важно понимать, если вы хотите получить от своего приложения максимальную производительность. У React native есть некоторые особые моменты, которые следует учитывать, потому что ему, очевидно, необходимо взаимодействовать с собственной стороной (iOS или Android) через мост сообщений, поэтому все как бы сериализуется из JavaScript в нотацию объектов JavaScript (JSON) и интерпретируется родная сторона. Но я не буду здесь вдаваться в подробности.

Жизненный цикл компонента React очень хорошо документирован здесь и описывает различные события, происходящие вокруг DOM и компонента.

Самый важный метод - это render (), который делает именно то, что написано. Он вызывается при изменении свойств или состояния. Таким образом, он только оценивает (не изменяет) значения this.props и this.state и возвращает элемент, который будет использоваться в DOM.

Эта статья описывает многие вещи, которые необходимо знать о производительности React (и Redux). Одна из самых распространенных вещей, с которыми я также сталкивался, - это методы привязки. React использует shallowCompare (в своем методе render ()). Это означает, что каждый ключ проверяется на строгое равенство. Если вы последуете этому, если даже просто ссылка объекта изменится, объекты не будут равны. Так обстоит дело с функциями и массивами, поэтому они каждый раз создают новые ссылки:

const oneFunction = someInput => { return someInput; }
const theSameFunction = theSameInput => { return theSameInput; }
console.log(oneFunction === theSameFunction); // false
console.log([42, 43] === [42, 43]); // false

Вы можете видеть, что функции и массивы никогда не равны, поскольку всегда будет создаваться новый, поэтому внутренняя ссылка отличается. И здесь не имеет значения, выполняете ли вы строгую проверку на равенство, как я, или слабую (==). Это потому, что мы не понижаем какой-либо тип, как если бы мы, например, проверили (42 == ’42’). Immutable.js может быть очень полезным в этих случаях.

На самом деле причина этого «создания нового объекта» в том, что вы должны быть осторожны, когда делаете это. Если вы создаете новые функции каждый раз, когда вызывается метод render () (что, я думаю, потенциально может происходить 60 раз в секунду), это может снизить производительность. Вот почему вы привязываете функции к классу / компоненту в конструкторе, чтобы этого не произошло. Если вы попытаетесь получить доступ к состоянию или реквизитам из функции, которая не связана с this, это приведет к ошибке (очевидно, потому что this неизвестен).

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

Помимо предотвращения создания новых объектов или вызовов функций в методе render (), вы также можете предотвратить вызов этого метода с помощью shouldComponentUpdate. Еще один важный момент - это понимание метода setState. Наиболее важно то, что он не сразу изменяет состояние компонента, а скорее дает ему запрос, который может быть отложен, пакетирован или что-то еще с помощью React. Вы по-прежнему можете реагировать на изменения вашего состояния с помощью обратного вызова setState или componentDidUpdate.

React Native и производительность

Как я уже упоминал, в этой статье я не буду вдаваться в подробности о React Native. React Native в целом великолепен, и я уже создал на нем много приложений. Многие жалуются на его ошибки, которые выскакивают при работе со встроенными модулями или обновлении чего-либо. Я тоже это испытал, но через какое-то время вы знаете, как с этим работать, и становится легче. Это помогает, когда вы работали над несколькими разными собственными модулями в react native.

Еще одна вещь, на которую люди могут жаловаться, - это производительность в react native. Такие приложения, как игры или просто требовательные к вычислениям, могут быть не самыми производительными. Причина в том, что в react native есть мост очереди сообщений, который используется для всех видов взаимодействия между нативной стороной и стороной JavaScript. Также рекомендуется сначала разработать свое приложение для Android, поскольку iOS в основном проще с точки зрения интеграции модулей и производительности.

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

Первоначально опубликовано на сайте mariusreimer.com 10 марта 2019 г.