[Из песочницы] Топ 5 ошибок в моих ReactJS приложениях
Больше 4х лет назад я влюбился в ReactJS и с тех пор все Fron End приложения разрабатываю при помощи этого замечательного инструмента. За это время я и команды, в которых мне повезло поработать, наделали кучу ошибок, многие из которых были благополучно исправлены. Множество оптимальных решений было найдено в тяжелых и дорогостоящих экспериментах.
Сегодня я хочу поделиться наиболее критичными и болезненными ошибками, которые допускают чаще других. Перед написанием этой статьи, я, конечно же, проштудировал интернет в поисках похожих статей, и с удивлением обнаружил, что большинство из них устарели и рассказывают о вещах, которые мало актуальны в 2019-м году. Так что, я постарался собрать список самый актуальных проблем на текущий момент.
1. Stateful компоненты (классы) хуже hook-ов
Пожалуй, начать стоит с наиболее нашумевшей фичи ReactJS, которая появилась в версии 16.8+ Вопреки некоторым убеждениям, эта фича была выстрадана ошибками предыдущих поколений разработчиков и решает множество проблем. Если вы все еще используете компоненты-классы вместо хуков в 2019-ом, то вы совершаете большую ошибку и просто еще не поняли, в чем их преимущество. Я не буду в этой статье подробно это объяснять, посмотрите лучше это замечательное видео Дена Абрамова, но я просто не мог начать эту статью иначе
Это, конечно, не ошибка сама по себе, но подход классов по сравнению с хуками гораздо более подвержен ошибкам, о чем уже написано немало статей:
- Самые распространенные ошибки в вашем React коде, которые вы (возможно) делаете
- The 7 Most Common Mistakes that React Developers Make
2. Использование анонимных функций в качестве props
Если первая ошибка еще может быть воспринята, как дань моде, то знание второй, уверяю, спасет от бессонных ночей и головной боли. Ведь именно она заставялет приложение работать настолько неадекватно, что его пользователи могут навсегда разочароваться в ReactJS. А мы ведь хотим, чтоб пользователи его любили, так же, как и мы с вами, правда? Для избежания этой ошибки можно пользоваться очень простым правилом — никогда, НИКОГДА не передавать в качестве пропса компоненту анонимную функцию.
export default function MyOtherComponent() {
return (
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 (
);
}
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 (
{/* Какой-то компонент здесь*/}
);
}
// код выше можно целиком записать при помощи хуков, но я не стал приводить этот пример, т.к. считаю его так же проблемным. Возможно, когда-то я напишу почему, но не в этой статье.
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-ти, когда я берусь «улучшийть» чужой код — у меня в результате получается почти то же самое, что и было. Просто потому что на старте задачи ты, обычно, не видишь всех проблем, которые подстерегают тебя в будущем. Так что, сейчас я уважаю чужой код, каким бы странным и «устаревшим» он мне не казался. Что и вам советую.
Спасибо
Спасибо Вам за прочтение этой статьи и всем тем, с кем мне повезло работать вместе. Спасибо нам, за то, что мы делаем наш мир интереснее и двигаем его вперед. Пусть не всегда правильно, не всегда умело, но двигаем.
Пишите о проблемах, с которыми сталкивались вы в комментариях. Возможно, у вас есть решения описанных в этой статье проблем, которые я упустил (уверен, что это так). Всем успехов и хорошего настроения!