Вы находите свой componentWillReceiveProps наполненным логикой? Вы знаете, что это не очень хорошо, но не знаете, где еще реагировать на изменения в реквизите? Знакомо это:

componentWillReceiveProps(newProps) {
 const coolProp =  newProps.coolProp;
 const coolPropChanged = coolProp !== this.props.coolProp;
 const otherProp =  newProps.coolProp;
 const otherPropChanged = otherProp !== this.props.otherProp;
 // Set state when these props change, to know when to display a ui element
 if (coolProp && coolPropChanged && otherProp && otherPropChanged) {
  this.setState({showAlert: true});
 }
}

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

// CoolContainer
import CoolDisplay from ./components/CoolDisplay;
const mapStateToProps = (state, ownProps) => ({
  showAlert: ownProps.coolProp && ownProps.otherProp,
  ...state,
});

const CoolContainer = connect(
 mapStateToProps,
 {
   myCoolMethod,
 },
)(CoolDisplay);

export default CoolContainer;
// Meanwhile, in CoolDisplay...
const CoolDisplay = (props) => {
  return (<div>{props.showAlert && <span>Alerted!</span>}</div>);
};

Намного лучше! Зачем смотреть дальше? Для чего-то настолько простого, это помогает. Но когда вы начинаете часто пересчитывать свой дисплей с постоянно меняющимися входными данными и обнаруживаете, что снова и снова выполняете одни и те же вычисления в нескольких контейнерах, это решение уже не кажется таким идеальным.

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

Как и все остальные функции в react / redux, Reselect - это отдельная библиотека. Установить с помощью npm обычным способом. npm i --save reselect. Reselect предоставляет методы (селекторы) для вычисления логики отображения (реквизиты, передаваемые компонентам дисплея) из состояния и родительских реквизитов и пересчитываются только при изменении одного из входов.

Чтобы показать суть этого, нам нужен немного более сложный пример. Предположим, мы хотим показать другое подмножество данных из запроса, который мы храним в хранилище redux для нескольких вкладок на странице.

// CoolContainer
import CoolDisplay from ./components/CoolDisplay;
import {currentDataSelector} from ./selectors;
const mapStateToProps = (state, ownProps) => ({
  dataForThisTab: currentDataSelector(state, ownProps),
  ...state,
});

const CoolContainer = connect(
 mapStateToProps,
 {
   myCoolMethod,
 },
)(CoolDisplay);

export default CoolContainer;
// Selectors
import { createSelector } from 'reselect';
import { groupBy } from 'lodash';
const requestData = state => state.myRequest.data;
// Returns active tab by react router url
const tabSelector = (state, props) => props.location.pathname.split('/').pop();
// Takes array of data objects and maps it to an object with each type as the key
const dividedDataSelector = createSelector([requestData], (allData) => groupBy(allData, 'type'));
// Returns value of current tab key from dividedData
export const currentDataSelector = createSelector([dividedDataSelector, tabSelector], (dividedData, currentTab) => dividedData[currentTab]);

Что тут происходит? Во-первых, это четыре разных селектора. Первые два, requestData и tabSelector, просто захватывают фрагменты состояния или свойств. Для этих двух нет необходимости использовать createSelector, поскольку мы не собираемся их кэшировать.

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

Настоящая магия происходит с currentDataSelector, где мы также используем createSelector для кэширования наших результатов. Мы используем currentDataSelector в контейнере , поэтому он вызывается каждый раз, когда меняются два параметра, state и ownProps. в mapStateToProps.

Поскольку CurrentDataSelector состоит из двух других селекторов, он вызывает каждый из этих селекторов с измененными параметрами, а затем вычисляет, какой фрагмент данных следует вернуть из значений, возвращаемых этими двумя селекторами. По-настоящему круто то, что один параметр, путь от нашего маршрутизатора, может изменяться снова и снова и получать правильный фрагмент данных для этой вкладки, без повторного разделения данных на вкладку. DivisionDataSelector будет вызываться снова только в случае изменения state.myRequest.data. Если у вас есть довольно длинный массив, это большой выигрыш в производительности.

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

Поскольку ваши данные должны быть изменены, чтобы настроить отображение еще более гибкими способами, вы можете «сложить» селекторы один над другим, чтобы получить именно тот фильтр или расчет, который вы хотите в каждом компоненте. Однако имейте в виду разницу между формированием вашего магазина для отображения и его изменением для отслеживания состояния приложения. Если вам нужно отслеживать вносимые вами изменения, поместите логику в свои редукторы.

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