javascript

Частые трудности работы с React.js

  • четверг, 25 января 2018 г. в 03:13:30
https://habrahabr.ru/post/347458/
  • ReactJS
  • JavaScript


Привет, Хабр! Представляю вашему вниманию перевод статьи Samer Buna «React.js Frequently Faced Problems».
Не паникуйте. Вы правы: существуют мириады веб-технологий, архитектур и фреймворков, и множество разрабатывается прямо сейчас. Помните: каждый, кто стал профессиональным веб-разработчиком начинал как и вы. Они изучали языки, библиотеки по одному, раз за разом, пока не прокачали свои навыки и механизмы работы

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

На самом деле, если вы сконцентрируетесь на одной вещи, например на создании сайта на простейшем HTML/CSS/JavaScript без библиотек, а потом добавите фреймворк по типу React’а, ваш путь от новичка до профессионала пройдет гораздо удобнее и приятнее.
И правда, новичкам бывает сложно разобраться с простыми вещами, на которые опытный разработчик даже не обращает внимание. Эта статья призвана разобрать популярные ошибки и затруднения, с которыми сталкиваются большинство изучающих React.

1 — Название компонента со строчной буквы


Компонент React должен иметь имя, начинающееся с заглавной буквы. Если это не так, то компонент будет обработан как встроенный: div или span.

Например:

class greeting extends React.Component { 
  // ...
}

Попытайтесь передать фреймворку (отрендерить) <greeting /> — React выдаст ошибку:

Warning: The tag <greeting> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.

Что и говорит нам: “Начинайте с заглавной буквы, сложно что ли?”.

*И никогда не называйте компонент button, img и другими тегами. React просто проигнорирует их и отрендерит как голый HTML-тег.*

image

2 — Использование одиночных кавычек вместо обратных апострофов


Строки создаются обратными апострофами (`...`) (ориг. “Back-ticks”), и они отличаются от обычных одиночных кавычек (‘...’).

На большинстве клавиатур обратные апострофы можно поставить, нажав на клавишу выше Tab.

Именно их мы используем, когда нам нужно вставить в строку динамичное выражение, не используя разрыв строки и последующую конкатенацию.

`В эту строку можно без труда вставить переменную или выражение`
‘А здесь может быть только текст’

Давайте представим ситуацию: нужно вывести сообщение о том, сколько сейчас времени “Время: …”

// Получаем время с учетом часовых поясов
 const time = new Date().toLocaleTimeString();

// Когда используем  обычные кавычки, нужно
// прибегать к конкатенации
'Time is ' + time

// Когда используем обратные апострофы, нужно
// вставить время в ${}
`Time is ${time}`

Также, внутри обратных апострофов можно делать перенос строки.

const template = `Я
МОГУ
ПИСАТЬ
В НЕСКОЛЬКО
ЛИНИЙ
`;

С обычными одинарными кавычками вы такого не провернете.

3 — Использование React.PropTypes


Объект PropTypes был удален из React’а. Его просто нет, и нужно привыкнуть. Зато теперь:

  • Добавляем новый модуль prop-types: npm install prop-types
  • Импортируем в нужный файл: import PropTypes from ‘prop-types’

Теперь работает. Пример: PropTypes.string.

Если до сих пор в коде есть строчка с React.PropTypes, то вы получите ошибкой
по голове:

TypeError: Cannot read property 'string' of undefined

4 — Использование версий, описанных в туториале


Когда просматриваете или прочитываете уроки программирования и следуете примерам оттуда, убедитесь, что у вас установлена соответствующая уроку версия. Обычно применение последних версий модулей безопасно, но туториал бывает устаревшим. Вследствие вы обязательно столкнетесь с ошибками. И наоборот, если в туториале используется React 16, не поленитесь заменить вашу 15-ую версию.

Особенно это бывает важно для Node.js. Ошибки со старыми версиями там на каждом углу. Например, в Node 7.x убрали Object.values, так что проще будет обновиться, чем исправлять свой код на Node 6.x

5 — Путаница между функциями и классами


Сможете ли вы сказать, где ошибка в этом коде?

class Numbers extends React.Component {
  const arrayOfNumbers = _.range(1, 10);
  // ...
}

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

Это и правда запутывает, потому что {} фигурные скобки в синтаксе класса выглядят как привычная блочная структура, но это не так.

Если хотите свободы в действиях, применяйте компоненты, основанные на функциях:

const Number = (props) => {
  const arrayOfNumbers = _.range(1, 10);
  // ...
};

6 — Числа как строки


Вы можете передать свойства с помощью строки:

<Greeting name="World" />

Если нужно передать число, то НЕ пишите их в кавычках:

// Не делайте так!
<Greeting counter="7" />

Вместо этого вставьте их в фигурные скобки:

<Greeting counter={7} />

Используя {7} внутри компонента Greeting, вы сможете достать ваше число с помощью this.props.counter и можно совершенно безопасно проводить с ним математические операции. Случай неправильного применения “7” может привести к непредсказуемым последствиям.

7 — Два приложения на одном порте


Чтобы запустить веб-сервер, вам нужен хост (например 127.0.0.1) и порт (например 8080), чтобы заставить сервер прослушивать запросы по правильному http-адресу.

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

Попробуйте запустить тот же сервер из другого терминала. Скорее всего вы получите ошибку “Используется”:

Error: listen EADDRINUSE 127.0.0.1:8080

Иногда получается, что порт используется в фоновом режиме. Вы его не видите, но он мешает жить. В таких ситуациях бывает выгодно необходимо “убить” тот, который занимает драгоценный порт:

lsof -i :8080

8 — Переменные окружения


Некоторые проекты зависят от существования переменных окружения (ориг. “Environmental variables”). Если запустить приложение без необходимых переменных, то оно попробует запуститься, используя значения undefined (не определено), и, конечно же, это приведет к критическим ошибкам.

К примеру, если проект подключен к базе данных MongoDB, то переменные окружения типа process.env.Mongo_URI пригодятся, чтобы безопасно подключаться к базе. Также их можно быстро менять в зависимости от обстановки (мало ли нужно подключить другую БД).

Чтобы запустить проект с MongoDB локально, сначала нужно экспортировать переменную MONGO_URI. Обязательно, если база работает, скажем, на порте 27017, сделайте это до того, как запускать приложение:

export MONGO_URI = "mongodb://localhost:27017/mydb"

9 — Фигурные {} или круглые () скобки


Вместо

return {
  something();
};

Пишите

return (
  something();
);

Первый пример попробует (и у него не получится) вернуть объект, в то время как второй сумеет вызвать функцию something() и вернуть то, что функция должна вернуть.

С тех пор как <тег> в синтаксисе JSX будет вызывать функцию, эта проблема применима к любому JSX коду.

Также она применима к стрелочным функциям (ориг. “Arrow functions”). Вместо

const Greeting = () => {
  <div>
    Hello World
  </div>
};

Пишите

const Greeting = () => (
  <div>
    Hello World
  </div>
);

*Просто запомните: в ES6 фигурные скобки для стрелочных функций не нужны!*

10 — Необорачивание объектов в круглые скобки


Закончим тему из предыдущего пункта. Нетрудно запутаться, обращаясь к стрелочным функциям для того же, чтобы вернуть обычный объект.

Вместо

const myAction = () => { type: 'DO_IT' };

Пишите

const myAction = () => ({ type: 'DO_IT'});

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

Для закрепления рассмотрим похожий готовый пример:

this.setState(prevState => ({ answer: 12 }));

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


React.Component, a не React.component. componentDidMount, а не ComponentDidMount. Вообще-то ReactDOM, но не ReactDom.

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

Часто ошибки возникают при обращении к свойствам:

<Greeting userName="Max" />
//Внутри компонента пишите
props.userName

Например, если вместо props.userName вписать props.username или props.UserName, то вернется значение undefined.

12 — Ошибки в применении объекта состояния


В компонентах-классах можно объявить локальный объект state, а позже получить его значение через this:

class Greeting extends React.Component {
  state = {
    name: "Мир",
  };
  render() {
    return `Привет, ${this.state.name}`
  }
}

Программа выведет “Привет, Мир”.

Вместо state можно использовать любое название объекта. Например:

class Greeting extends React.Component {
  user = {
    name: "Мир",
  };
  render() {
    return `Привет, ${this.user.name}`
  }
}

Выведет то же самое.

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

13 — Каша в <тег/> и </тег>


Не забывайте ставить прямой слеш /, когда закрываете тег. И да, иногда вам понадобиться <тег/>, а иногда </тег>.

В HTML есть самозакрывающиеся теги (ориг. “self-closing tag”). Это элементы без “детей”. К примеру img относится к таким:

<img src="..." />
// Вы не пишете <img></img>

В тег div можно вложить сколько угодно других, поэтому надо как открывать, так и закрывать его:

<div>
  Дети здесь...
</div>

Это же правило применимо к компонентам React.

<Greeting>Hello!</Greeting>
// Обратите внимание, где стоит слеш

Однако, если у него нет “детей”, вы все равно можете открывать/закрывать его, а также можно использовать самозакрывающийся тег:

// 2 верных пути
<Greeting></Greeting>
<Greeting />

Пример неверного применения:

// Вот это неправильно
<Greeting><Greeting />

От того вы должны увидеть ошибку:

Syntax error: Unterminated JSX contents

14 — Ожидание, что import/export “просто заработают”


Возможность import/export официально поддерживается JavaScript с 2015 года. Тем не менее, это единственное из ES2015, что не в полной форме поддерживается всеми браузерами и самой поздней версией Node.

Часто проекты на React создают с Webpack и Babel. Они позволяют компилировать ваш код в то, что будут понимать все браузеры. Поэтому использовать import/export лучше вместе с Webpack и Babel.

Чтобы заставить Node понимать import/export, а это иногда нужно, например в тех случаях, когда вы используете их на фронте (frontend) и имеете SSR (Server-Side Rendering — рендеринг на сервере, изоморфное приложение). Для этого вам понадобится присутствие Babel’я на самом Node. Для удобства разработки советую подключить что-то из pm2, nodemon, babel-watch.

15 — Проблемы с Bind


Вы можете объявить методы класса в компонентах React, а затем включать их в метод render. К примеру:
class Greeting extends React.Component {
  whoIsThis() {
    console.dir(this); // "this" вызывается из whoIsThis
    return "World"
  }
  render() {
    return `Hello ${this.whoIsThis()}`
  }
}
ReactDOM.render(<Greeting />, mountNode);

Я использовал метод whoIsThis внутри render’a с помощью this.whoIsThis, потому что именно в таком случае this обращается к элементу DOM (то есть к самому компоненту).

React пытается убедиться в этом. Однако, JavaScript даже не старается автоматически связать (ориг. “bind”) this и метод whoIsThis.

Протестируем это на примерах. Строка console.dir в whoIsThis выведет объект Greeting, потому что метод был вызван прямо из render’а:

image

Однако, когда вы используете метод класса в том месте, которое будет вызвано не сразу, например в обработчике событий (ориг. “Event handler”), то console.dir не выведет того, что ожидалось:

image

В примере выше React вызывает метод whoIsThis только после нажатия на строку, что не даст вам доступа к компоненту изнутри. Именно поэтому вы получите undefined.

Это предисловие о том, что часто проблема возникает, когда из метода класса пытаются получить this.props или this.state. Просто не будет работать. Для решения существует много способов. Можно обернуть метод в функцию или использовать .bind, чтобы заставить метод запоминать того, кто его вызывает. Также можно делать bind в конструкторе класса, а не в render’е.

Все же лучшим решением будет использование возможностей из ECMAScript (это stage-3) для классов через Babel, а в обработчиках писать стрелочные функции:

class Greeting extends React.Component {
  whoIsThis = () => {
    console.dir(this);
  }
  render() {
    return (
     <div onClick={this.whoIsThis}>
        Hello World
      </div>
    );
  }
}

Работать это будет как положено:

image

На этом все. Спасибо за чтение.