Улучшите свою игру React: откройте для себя 12 проверенных стратегий для улучшения качества кода и производительности проекта.

React.js — мощная библиотека для создания пользовательских интерфейсов. Его простота и гибкость сделали его предпочтительным выбором для многих веб-разработчиков. Однако для написания эффективного и поддерживаемого кода React требуется нечто большее, чем просто базовое понимание библиотеки. В этой статье мы рассмотрим 12 советов, которые могут помочь.

1. Избегайте вложения компонентов без необходимости

При рефакторинге компонентов будьте осторожны с ненужной вложенностью. Иногда при извлечении дочернего компонента возникает соблазн определить его в родительском компоненте. Однако это может привести к проблемам с производительностью и непредсказуемому поведению.

Рассмотрим этот пример:

// Avoid unnecessary nesting
const Parent = () => {
  const handleClick = () => {
    // Handle click
  };

const Child = () => {
    return (
      <button onClick={handleClick}>
        Click me
      </button>
    );
  };

  return (
    <div>
      <Child />
    </div>
  );
};

В приведенном выше примере каждый раз, когда рендерится компонент Parent, он переопределяет компонент Child. Это может привести к ненужному повторному рендерингу и отрицательно сказаться на производительности. Чтобы этого не произошло, вынесите компонент Child за пределы компонента Parent и передайте нужную функцию как prop.

2. Организуйте компоненты в отдельные файлы

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

Вот пример файловой структуры:

- components
  - index.js
  - Button
    - index.js
    - Button.js
    - Button.test.js
    - Button.stories.js
    - Button.module.css
  - Header
    - index.js
    - Header.js
    - Header.test.js
    - Header.stories.js
    - Header.module.css
  - ...

Эта структура позволяет легко экспортировать компоненты, сохраняя при этом связанные файлы, такие как стили или тесты, в одном каталоге. Чтобы импортировать компонент, вы можете использовать индексный файл под component , который действует как бочка, сохраняя лаконичность импорта и предоставляя удобочитаемое имя файла. Например:

export { default as Button } from './Button';
export { default as Header } from './Header';

Вот статья, объясняющая, почему не использовать экспорт по умолчанию:



3. Предпочитайте фрагменты бесполезным элементам Div

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

Вот пример:

const ComponentWithFragments = () => {
  return (
    <>
      <h1>Title</h1>
      <p>Paragraph 1</p>
      <p>Paragraph 2</p>
    </>
  );
};

const ComponentWithEmptyElement = () => {
  return (
    <div>
      <h1>Title</h1>
      <p>Paragraph 1</p>
      <p>Paragraph 2</p>
    </div>
  );
};

И ComponentWithFragments, и ComponentWithEmptyElement дают одинаковый результат, но первый избегает добавления дополнительных div, которые могут улучшить стиль CSS и доступность.

4. Используйте методы оптимизации производительности

Оптимизация производительности имеет решающее значение при работе с компонентами React, особенно при работе с дорогостоящими вычислениями и рендерингом. Используя определенные методы, такие как useMemo, useCallback и React.memo, мы можем повысить эффективность наших компонентов.

Чтобы запомнить результат функции и вычислить его только при изменении зависимостей, можно использовать хук useMemo. Вот пример:

const ExpensiveComponent = ({ value }) => {
  const expensiveResult = useMemo(() => {
    // Perform expensive calculation based on value
    // Return the result
  }, [value]);
​
  return (
    <div>
      {/* Use the expensiveResult */}
    </div>
  );
};

Точно так же хук useCallback оптимизирует отрисовку функций обратного вызова. Запоминая обратный вызов, мы можем предотвратить ненужные повторные рендеры. Рассмотрим этот пример:

const Component = React.memo(({ prop, onClick }) => {
  // Component logic
​
  return (
    <button onClick={onClick}>Click me</button>
  );
});
​
const ParentComponent = () => {
  const handleClick = useCallback(() => {
    // Handle click logic
  }, [/* dependencies */]);
​
  return (
    <div>
      <Component prop={value} onClick={handleClick} />
    </div>
  );
};

Другой метод оптимизации использует React.memo, который запоминает результат компонента на основе равенства свойств. Это может предотвратить ненужные повторные рендеры, когда свойства не изменились. Вот пример:

const MemoizedComponent = React.memo(Component);

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

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

5. Будьте осторожны с глобальным управлением состоянием

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

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

6. Правильно используйте key Prop в списках

При рендеринге списков компонентов в React важно предоставить уникальное свойство key для каждого элемента. Свойство key помогает React эффективно обновлять список при добавлении, удалении или изменении порядка элементов.

Убедитесь, что реквизит key уникален в списке и стабилен в разных рендерах. Избегайте использования индекса в качестве key, если порядок списка может измениться, так как это может привести к неправильному отображению и проблемам с производительностью.

const ListComponent = ({ items }) => {
  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
};

Предоставляя уникальную и стабильную опору key, React может оптимизировать рендеринг списка и обновлять только необходимые компоненты, повышая производительность.

7. Уменьшите размер пакета с помощью разделения кода

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

Если вы используете такие инструменты, как Next.js или Create React App, разделение кода уже выполняется по умолчанию. Однако для динамического импорта компонентов React вы можете использовать функцию отложенной загрузки, предоставляемую React. Это позволяет загружать компоненты асинхронно и отображать резервный пользовательский интерфейс во время загрузки компонента.

Вот пример:

import { lazy, Suspense } from 'react';

const LazyComponent = lazy(() => import('./LazyComponent'));
const App = () => {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <LazyComponent />
      </Suspense>
    </div>
  );
};

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

8. Эффективно обрабатывать ошибки

Обработка ошибок — важнейший аспект создания надежных приложений React. Хотя встроенные границы ошибок React обеспечивают базовые возможности обработки ошибок, вы можете столкнуться со сценариями, требующими более продвинутых функций. Библиотека react-error-boundary может эффективно обрабатывать такие случаи.

Одним из общих требований является предоставление механизма повторных попыток при возникновении ошибки. С помощью react-error-boundary вы можете легко реализовать эту функцию, используя компонент ErrorBoundary и пользовательский резервный компонент пользовательского интерфейса. Вот пример:

import { ErrorBoundary, FallbackProps } from 'react-error-boundary';
​
function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {

  const handleRetry = () => {
    // Perform the retry logic here
    resetErrorBoundary();
  };
​
  return (
    <div>
      <p>Something went wrong:</p>
      <p>{error.message}</p>
      <button onClick={handleRetry}>Retry</button>
    </div>
  );
}
​
function App() {
  return (
    <ErrorBoundary FallbackComponent={ErrorFallback}>
      {/* Components to be covered by the error boundary */}
      <ComponentA />
      <ComponentB />
    </ErrorBoundary>
  );
}

По умолчанию границы ошибок не охватывают все типы ошибок, такие как обработчики событий или асинхронный код. Однако хук useErrorHandler, предоставляемый react-error-boundary, позволяет нам выборочно выдавать ошибки в определенных местах и ​​последовательно захватывать и обрабатывать их границей ошибки. Вот пример:

import { useErrorHandler } from 'react-error-boundary';
​
function MyComponent() {
  const handleError = useErrorHandler();
​
  const handleAsyncOperation = async () => {
    try {
      await doAsyncOperation();
    } catch (error) {
      // Call handleError to rethrow the error within the React lifecycle
      handleError(error);
    }
  };
​
  return (
    <button onClick={handleAsyncOperation}>Perform Async</button>
  );
}

Используя react-error-boundary и связанные с ним компоненты и перехватчики, вы можете более эффективно обрабатывать ошибки, обеспечивать лучший пользовательский интерфейс и гарантировать, что ошибки перехватываются и обрабатываются в вашем приложении React.

9. Рассмотрите пользовательские хуки для бизнес-логики

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

Вот пример:

const useCustomHook = () => {
  // Custom hook logic and state management
return {
    // Return relevant values and functions
  };
};
const ComponentWithCustomHook = () => {
  const { value, handleClick } = useCustomHook();
  return (
    <div>
      {/* Use the custom hook values and functions */}
    </div>
  );
};

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

10. Используйте новый API: StartTransition, useTransition API и useDeferredValue API.

React 18 представляет новые API-интерфейсы, которые используют параллельный рендеринг для повышения производительности и удобства работы ваших приложений. Давайте рассмотрим три важных API:

API стартового перехода

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

import { startTransition } from 'react';
​
function MyComponent() {
  const handleClick = () => {
    startTransition(() => {
      // Update state or perform other transitions
    });
  };
​
  return (
    <button onClick={handleClick}>Perform Transition</button>
  );
}

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

API-интерфейс useTransition

Хук useTransition позволяет отслеживать и обновлять ожидающие переходы состояний. Он предоставляет флаг isPending, который вы можете использовать для отображения отзывов о загрузке для ваших пользователей. Вот пример:

import { useTransition } from 'react';
​
function MyComponent() {
  const [isPending, startTransition] = useTransition();
​
  return (
    <div>
      {isPending && <Spinner />} {/* Show loading spinner while transition is pending */}
      <button onClick={startTransition}>Start Transition</button>
    </div>
  );
}

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

API использования деферредвалуе

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

import { useState, useDeferredValue } from 'react';
​
function MyComponent() {
  const [input, setInput] = useState('');
  const deferredValue = useDeferredValue(input, { timeoutMs: 2000 });
​
  return (
    <div>
      <input value={input} onChange={(e) => setInput(e.target.value)} />
      <List text={deferredValue} />
    </div>
  );
}

В приведенном выше коде хук useDeferredValue используется для отсрочки обновления компонента List максимум на 2000 миллисекунд. Это гарантирует, что пользовательский интерфейс останется отзывчивым, даже если обновление List займет больше времени.

11. Рендеринг на стороне сервера с приостановкой

Рендеринг на стороне сервера (SSR) позволяет создавать HTML-код на сервере и отправлять клиенту полностью обработанную HTML-страницу. React 18 вносит улучшения в SSR за счет использования компонента Suspense:

Потоковая передача HTML на сервер

С помощью Suspense вы можете обернуть части своей страницы и указать резервный контент для отображения, когда компоненты не готовы или требуется некоторое время для загрузки. Это позволяет React продолжать потоковую передачу HTML для остальной части страницы, вот пример:

<Page>
  <Header />
  <Suspense fallback={<LoadingIndicator />}>
    <ProfileDetails />
  </Suspense>
</Page>

В этом фрагменте кода компонент ProfileDetails заключен в границу приостановки, и предоставляется резервный компонент (LoadingIndicator). React немедленно начнет отображать заголовок и продолжит потоковую передачу HTML. Когда HTML для компонента ProfileDetails станет доступным на сервере, он будет добавлен в поток и вставлен в нужное место.

Выборочное увлажнение

Приостановка также обеспечивает выборочную гидратацию, а это означает, что React может отдавать приоритет гидратации частей приложения, с которыми пользователь взаимодействует в первую очередь, а не ждать, пока гидратируется вся страница. Вот эквивалентный пример:

<Page>
  <Header />
  <Suspense fallback={<LoadingIndicator />}>
    <ProfileDetails />
    <PhotoGallery />
  </Suspense>
</Page>

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

12. Напишите модульные тесты для ваших компонентов

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

Рассмотрите возможность использования библиотек тестирования, таких как Jest и React Testing Library, для написания модульных тестов для ваших компонентов. Тестируйте важные функции, крайние случаи и взаимодействие компонентов, чтобы охватить широкий спектр сценариев.

import { render, screen } from '@testing-library/react';
import Component from './Component';

test('renders component correctly', () => {
  render(<Component />);
  expect(screen.getByText('Text')).toBeInTheDocument();
});

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

Заключение

Надеюсь, эти советы помогут вам создавать лучшие приложения React. Пожалуйста, дайте мне знать, если у вас есть дополнения или предложения.

Спасибо за чтение! Любите эти истории? Поддержите меня членством в ежемесячных подборках статей. Или присоединяйтесь к Medium по моей ссылке.

Дополнительные материалы на PlainEnglish.io.

Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter, LinkedIn, YouTube и Discord .