Я думаю, что более важно то, как происходит повторный выбор, почему его следует использовать. Основными причинами являются компоновка и запоминание:
Композиционность
Еще один способ сказать, что вы пишете селектор один раз и повторно используете его в других более детальных селекторах. Допустим, у меня есть состояние, подобное этому: {data:{people:[person,person ...]}
Тогда я могу написать filterPerson следующим образом:
const selectData = state => state.data;
const selectDataEntity = createSelector(
selectData,//re use selectData
(_, entity) => entity,
(data, entity) => data[entity]
);
const filterDataEntity = createSelector(
selectDataEntity,//re use selectDataEntity
(a, b, filter) => filter,
(entities, filter) => entities.filter(filter)
);
Если я переместу данные в state.apiResult
, тогда мне нужно только изменить selectData
, это максимизируетповторное использование кода и сводит к минимуму дублирование реализации.
Запоминание
Запоминание означает, что при многократном вызове функции с одинаковыми аргументами функция будет выполнена только один раз,Чистые функции возвращают один и тот же результат с одинаковыми аргументами независимо от того, сколько раз они были вызваны или когда они вызываются.
Это означает, что вам не нужно выполнять функцию при повторном вызове с теми же параметрами, поскольку вы уже знаете результат.
Запоминание можно использовать, чтобы не вызывать дорогие функции (как фильтрация большого массива). В React запоминание важно, потому что чистые компоненты будут визуализироваться при смене реквизита:
const mapStateToProps = state => {
//already assuming where data is and people is not
// a constant
return {
USPeople: state.data.people.filter(person=>person.address.countey === US)
}
}
Даже если state.data.people
не изменил, функция фильтра каждый раз будет возвращать новый массив.
Как это работает
Ниже приведена перезапись createSelector с некоторыми комментариями. Удален некоторый код, который проверял бы параметры безопасности и позволял бы вам вызывать createSelector с массивом функций. Пожалуйста, прокомментируйте, если есть что-то, что вам трудно понять.
const memoize = fn => {
let lastResult,
//initial last arguments is not going to be the same
// as anything you will pass to the function the first time
lastArguments = [{}];
return (...currentArgs) => {//returning memoized function
//check if currently passed arguments are the same as
// arguments passed last time
const sameArgs =
currentArgs.length === lastArguments.length &&
lastArguments.reduce(
(result, lastArg, index) =>
result && Object.is(lastArg, currentArgs[index]),
true
);
if (sameArgs) {
//current arguments are same as last so just
// return the last result and don't execute function
return lastResult;
}
//current arguments are not the same as last time
// or function called for the first time, execute the
// function and set last result
lastResult = fn.apply(null, currentArgs);
//set last args to current args
lastArguments = currentArgs;
//return result
return lastResult;
};
};
const createSelector = (...functions) => {
//get the last function by popping it off of functions
// this mutates functions so functions does not have the
// last function on it anymore
// also memoize the last function
const lastFunction = memoize(functions.pop());
//return a selector function
return (...args) => {
//execute all the functions (last was already removed)
const argsForLastFunction = functions.map(fn =>
fn.apply(null, args)
);
//return the result of a call to lastFunction with the
// result of the other functions as arguments
return lastFunction.apply(null, argsForLastFunction);
};
};
//selector to get data from state
const selectData = state => state.data;
//select a particular entity from state data
// has 2 arguments: state and entity where entity
// is a string (like 'people')
const selectDataEntity = createSelector(
selectData,
(_, entity) => entity,
(data, entity) => data[entity]
);
//select an entity from state data and filter it
// has 3 arguments: state, entity and filterFunction
// entity is string (like 'people') filter is a function like:
// person=>person.address.country === US
const filterDataEntity = createSelector(
selectDataEntity,
(a, b, filter) => filter,
(entities, filter) => entities.filter(filter)
);
//some constants
const US = 'US';
const PEOPLE = 'people';
//the state (like state from redux in connect or useSelector)
const state = {
data: {
people: [
{ address: { country: 'US' } },
{ address: { country: 'CA' } },
],
},
};
//the filter function to get people from the US
const filterPeopleUS = person =>
person.address.country === US;
//get people from the US first time
const peopleInUS1 = filterDataEntity(
state,
PEOPLE,
filterPeopleUS
);
//get people from the US second time
const peopleInUS2 = filterDataEntity(
state,
PEOPLE,
filterPeopleUS
);
console.log('people in the US:', peopleInUS1);
console.log(
'first and second time is same:',
peopleInUS1 === peopleInUS2
);