Если вы разработчик React, работающий над менее чем тривиальным проектом, вы, вероятно, используете Redux для управления состоянием. Грубо говоря, все ваши подключенные компоненты прослушивают любые обновления store, глобального состояния приложения, а ваша функция mapStateToProps берет некоторый фрагмент этого состояния и возвращает его в качестве реквизита вашему подключенному компоненту.

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

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

Должен ли компонент перерисовываться каждый раз, когда магазин получает обновление, даже если он не заинтересован в этом конкретном фрагменте состояния? Нет, этого не может быть. Тогда как наша функция mapStateToProps понимает, когда она должна инициировать повторный рендеринг? Что ж, под капотом redux выполняет поверхностное сравнение состояния, возвращаемого из mapStateToProps, реализуя метод shouldComponentUpdate. Интересный. Тем не менее, это не совсем объясняет, почему наш пирог сходит с ума.

Вот упрощенная версия кода нашей круговой диаграммы:

import React from 'react';
import { connect } from 'react-redux';

import PieChartWidget from './components/PieChartWidget';

const mergeProps = (stateProps, dispatchProps, ownProps) => {
  return {...ownProps, ...dispatchProps, ...stateProps};
};

const mapStateToProps = (state, ownProps) => {
  const data = state.widgetData;
  const apiArgs = { ...ownProps.apiArgs }; //OOPS!!!!
  return {
    data,
    apiArgs
  };
};

const IssuesPieChart = ({
  apiArgs,
  data,
}) => (
  <DataLoader
    apiArgs={apiArgs}
    {...others}
  >
    <PieChartWidget
      data={data}
    />
  </Widget>
);

export default connect(mapStateToProps, mapWidgetDispatchToProps, mergeProps)(IssuesPieChart);

Что ж, оказывается, мы допустили честную ошибку в нашей функции mapStateToProps, которая копировала состояние с помощью оператора распространения .... Д’о. Обычные операции с массивами, такие как slice, concat, filter или ..., создают новую ссылку на объект или массив, который запускает обновление компонента, когда redux внутренне сравнивает старое и новое состояние.

Быстрое исправление здесь состояло в том, чтобы просто назначить наше состояние следующим образом:

const mapStateToProps = (state, ownProps) => {
  const data = state.widgetData;
  const apiArgs = ownProps.apiArgs;
 return {
    data,
    apiArgs
  };
};

Если ваш вариант использования более сложен и есть некоторая фильтрация или выбор части дерева состояний, которая вас интересует, вы можете проверить функции мемоизированного селектора, чтобы предотвратить дорогостоящие сравнения и ненужные повторные рендеринги: https://redux .js.org/recipes/computing-derived-data#creating-a-memoized-selector

JavaScript на простом английском языке

Понравилась эта статья? Если да, то получите больше похожего контента, подписавшись на наш канал YouTube!