Pull to refresh

Топ 5 ошибок в моих ReactJS приложениях

Reading time 6 min
Views 11K
Больше 4х лет назад я влюбился в ReactJS и с тех пор все Front End приложения разрабатываю при помощи этого замечательного инструмента. За это время я и команды, в которых мне повезло поработать, наделали кучу ошибок, многие из которых были благополучно исправлены. Множество оптимальных решений было найдено в тяжелых и дорогостоящих экспериментах.

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

Как я работал над ReacjJS приложением

1. Stateful компоненты (классы) хуже hook-ов


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

Это, конечно, не ошибка сама по себе, но подход классов по сравнению с хуками гораздо более подвержен ошибкам, о чем уже написано немало статей:

  1. Самые распространенные ошибки в вашем React коде, которые вы (возможно) делаете
  2. The 7 Most Common Mistakes that React Developers Make

2. Использование анонимных функций в качестве props


Если первая ошибка еще может быть воспринята, как дань моде, то знание второй, уверяю, спасет от бессонных ночей и головной боли. Ведь именно она заставялет приложение работать настолько неадекватно, что его пользователи могут навсегда разочароваться в ReactJS. А мы ведь хотим, чтоб пользователи его любили, так же, как и мы с вами, правда? Для избежания этой ошибки можно пользоваться очень простым правилом — никогда, НИКОГДА не передавать в качестве пропса компоненту анонимную функцию.

export default function MyOtherComponent() {
  return (
    <MyComponent getValue={i => i.value} /> {/* НИКОГДА не пишите так */}
  );
}

Более изощренный вариант этой ошибки может выглядеть как-то так (не читайте, если не знакомы с Redux):

import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
import MyComponent from './MyComponent';
import { fetchProjectData, projectId } from "./store/projects"

const mapStateToProps = createStructuredSelector({
  projectId,
});

const mapDispatchToProps = {
  fetchProjectData,
};

const mergeProps = (
  { projectId, ...restState }: any,
  { fetchProjectData, ...restDispatch }: any,
  { memberId, ...restOwn }: any
) => ({
  ...restState,
  ...restDispatch,
  ...restOwn,
  fetchProjectData: () => fetchProjectData(projectId),
});

export default connect(
  mapStateToProps,
  mapDispatchToProps,
  mergeProps
)(MyComponent);

В обоих вариантах, в конечно итоге, в props компонента попадает анонимная функция. Это плохо потому, что при каждом рендере родительского элемента, эта фукнция будет ссылаться на новый объект в памяти, а, значит, не будет равна сама себе предыдущей, и ваш компонент благополучно будет перерендерен без надобности. Это так сильно может тормозить производительность вашего приложения, что вы сами начнете плеваться и разочаровываться в React, но все дело в АНОНИМНЫХ ФУНКЦИЯХ в props-ах. Просто не делайте так никогда — и будьте счастливы.

Проблема еще заключается в том, что, часто такая ошибка не делает ничего плохого. Код просто работает себе — и все. И ничего заметно плохого не происходит. Ровно до того момента, пока вы в очередной раз не запихнете туда анонимный вызов получения данных с сервера (второй пример) — тут то вы поймете всю серьезность проблемы. Накопление таких анонимных пропсов, в результате, замедлит ваше приложение до уровня опыта 1995 года, когда для загрузки страницы нам приходилось просить соседей освободить телефонную линию.

Еще пара слов, как же написать правильно. Вот как:

const getValue = i => i.value;

return default function MyOtherComponent() {
  return (
    <MyComponent getValue={getValue} />
  );
}

import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
import MyComponent from './MyComponent';
import { fetchProjectData, projectId } from "./store/projects"

const mapStateToProps = createStructuredSelector({
  projectId,
});

const mapDispatchToProps = {
  fetchProjectData,
};

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(MyComponent);

// и далее в компоненте
import React, { useEffect } from 'react';

export default function MyComponent({ fetchProjectData, projectId }) {
  useEffect(() => {
    fetchProjectData(projectId);
  }, [fetchProjectData, projectId]);

  return (
    <div>{/* Какой-то компонент здесь*/}</div>
  );
}

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

3. Несколько экземпляров React в приложении


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

Пожалуйста, не пытайтесь запихнуть на одну страницу больше одного экземпляра React приложения. На самом деле, в документации React нет запрета на такой подход, я даже встречал рекомендации поступать именно так в некоторых статьях (и, конечно же, сам делал в своих приложениях подобное), НО оптимизация такого подхода и согласование всех частей приложения, в этом случае начинает занимать больше половины всего рабочего времени. Этого легко можно избежать: например, если вам нужно реагировать на какие-то события в legacy коде в вашем новом React-приложении, — вы можете использовать событийную модель. Например вот так:

import React, { useCallback, useEffect } from 'react';
export default function MyComponent() {
  const reactHandlerOfLegacyEvent = useCallback((event) => {/* event handler */}, []);
  
  useEffect(() => {
    document.addEventListener("myLegacyEvent", reactHandlerOfLegacyEvent);
    return () => {
      document.removeEventListener("myLegacyEvent", reactHandlerOfLegacyEvent);
    };
  }, [reactHandlerOfLegacyEvent]);

  return ({/* здесь какой-то компонент */});
}

4. Написание собственных библиотек, вместо существующих с открытым исходным кодом


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

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

5. Боязнь использовать чужой код


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

Но я так же научился понимать других разработчиков, их мысли и проблемы, с которыми они столкнулись. Видеть количество времени и сил, которые были потрачены (и не зря) на решение проблем. В 3х случаях из 5-ти, когда я берусь «улучшийть» чужой код — у меня в результате получается почти то же самое, что и было. Просто потому что на старте задачи ты, обычно, не видишь всех проблем, которые подстерегают тебя в будущем. Так что, сейчас я уважаю чужой код, каким бы странным и «устаревшим» он мне не казался. Что и вам советую.

Спасибо


Спасибо Вам за прочтение этой статьи и всем тем, с кем мне повезло работать вместе. Спасибо нам, за то, что мы делаем наш мир интереснее и двигаем его вперед. Пусть не всегда правильно, не всегда умело, но двигаем.

Пишите о проблемах, с которыми сталкивались вы в комментариях. Возможно, у вас есть решения описанных в этой статье проблем, которые я упустил (уверен, что это так). Всем успехов и хорошего настроения!
Tags:
Hubs:
+15
Comments 57
Comments Comments 57

Articles