Как использовать селекторы Reselect внутри редуктора Redux

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

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

Надуманный пример:

/* Selectors */
const getTodos = state => state.todos;

const getUncompletedTodos = createSelector(
    [ getTodos ],
    todos => todos.filter(t => !t.completed)
);

/* Reducer */
const todosReducer = (state, action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return [
        ...state,
        {
          id: action.id,
          text: action.text,
          completed: false
        }
      ];
    case 'REMOVE_COMPLETED_TODOS':
      return getUncompletedTodos(state); // <-- this won't work
  }
}

person burtyish    schedule 07.08.2017    source источник


Ответы (2)


Ваш селектор работает с объектом корневого состояния.

Чтобы подделать это, вы могли бы сделать

 return getUncompletedTodos({todos: state});

Но ИМХО лучше было бы повторно использовать функцию фильтрации

/* Selectors */
const getTodos = state => state.todos;

const filterCompleted = todos => todos.filter(t => !t.completed)

const getUncompletedTodos = createSelector(
    [ getTodos ],
    filterCompleted
);

// inside reducer
case 'REMOVE_COMPLETED_TODOS':
    return filterCompleted(state);
person Yury Tarabanko    schedule 07.08.2017
comment
Почему бы вам не воспользоваться мемоизацией в селекторе getUncompletedTodos? - person user2602152; 01.11.2017
comment
@user2602152 user2602152 Вы вызываете редуктор для изменения состояния. Мемоизация здесь как всегда отсутствует в кеше. - person Yury Tarabanko; 01.11.2017
comment
Как бы вы предложили подойти к более сложному селектору? Допустим, я хочу получить доступ к определенному элементу в определенной задаче (например: getCurrentTodo, getTaskListInCurrentTodo). В этом случае использование вашего второго предложения потребует от вас вызова обеих функций для доступа к элементу. Другими словами, вы не можете повторно использовать комбинации селекторов без использования упомянутого вами метода «подделки»? - person Daynil; 15.07.2018
comment
@Daynil трудно сказать, не видя твоего кода. Вообще говоря, reselect — это простая мемоизация, которая работает, если: функция впоследствии вызывается несколько раз с одними и теми же аргументами и достаточно тяжела, чтобы перевешивать дополнительные операции, добавленные библиотекой. Если ваш случай соответствует критериям, он может выиграть от запоминания. - person Yury Tarabanko; 16.07.2018
comment
Я не думаю, что это хорошая идея. Цитата: Вы вызываете редуктор для изменения состояния. Мемоизация здесь как всегда отсутствует в кеше. Это не так. Потому что в тот момент, когда я вызываю селектор, мутация НЕ была выполнена. Так что я должен быть в состоянии воспользоваться memoization. - person Tyler Long; 15.10.2018

Ответ Юрия работает, но не использует запоминание (см. комментарии). Если вы этого хотите, решением будет написать селектор только для того среза состояния, которое ему нужно.

Селектор станет:

const getUncompletedTodos = createSelector(
    [todos => todos], // Not sure if there's a way to skip this redundancy and still take advantage of memoization with reselect.
    todos => todos.filter(t => !t.completed)
);

В редукторе вы просто использовали бы его так:

case 'REMOVE_COMPLETED_TODOS':
    return getUncompletedTodos(state);

Однако при использовании селектора в корневом состоянии где-то еще вы используете его следующим образом:

getUncompletedTodos(state.todos)

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

person Wouter Florijn    schedule 15.03.2021