Выберите взаимозависимые данные в хранилище избыточности с повторным выбором createSelector

Итак, я использую redux и reselect инструмент createSelector для запоминания моих селекторов в mapStateToProps, и это здорово.

Теперь у меня есть нормализованные данные в моем хранилище и контейнер, которому нужны взаимозависимые данные из хранилища.

Я делаю это на данный момент, и это работает:

const getItemId = props => parseInt(props.match.params.itemId, 10)

const containerSelector = createSelector(getItemId, itemId =>
  createSelector(
    [getUser, getItem(itemId)],
    (user, item) =>
      createSelector(
        [
          getFoo(item.fooId),
          getBar(item.barId)
        ],
        (foo, bar) => ({ user, item, foo, bar })
      )
  )
)

const mapStateToProps = (state, ownProps) =>
  containerSelector(ownProps)(state)(state)

export default connect(mapStateToProps)(Container)

Вопрос: есть ли лучший способ сделать это?

  1. более читабельно?
  2. Лучшая практика?
  3. Более производительный? (это перфоманс?)

Изменить:

Благодаря ответу Тхо Ву, я смог упростить его и запомнить, например:

import { getUser, getItemById, getFooById } from 'selectors'

// Note: getStuffById are selector factories like: 
//   const getStuffById = id => state => state.stuff.objects[id] 

const itemIdSelector = (_, props) => parseInt(props.match.params.itemId, 10)

const itemSelector = (state, props) => {
   const itemId = itemIdSelector(state, props)
   return getItemById(itemId)(state)
}

const fooSelector = (state, props) => {
   const item = itemSelector(state, props)
   return item ? getFooById(item.fooId)(state) : null
}

const mapStateToProps = createStructuredSelector({ 
  user: getUser, 
  itemId: itemIdSelector, 
  item: itemSelector,
  foo: fooSelector
})

Но я все еще смешанный об этом? А может уже хорошо?


person Pandaiolo    schedule 05.05.2018    source источник
comment
Меня очень смущают getItemById и getFooById, которые кажутся фабриками селекторов (функции, которые возвращают новый, частично настроенный селектор), используемые как обычный селектор. Если это так, было бы еще возможности для улучшения.   -  person Andrea Carraro    schedule 09.05.2018
comment
Да, верно, фабрики селекторов на основе объекта id — добавил пояснительный комментарий в пример кода.   -  person Pandaiolo    schedule 10.05.2018


Ответы (1)


Из документации reselect:

Когда селектор подключается к компоненту с помощью connect, реквизиты компонента передаются в качестве второго аргумента селектору.

Таким образом, первый слой createSelector, используемый для получения itemId, на самом деле не нужен, его можно заменить следующим:

const getItem = (state, props) => {
   const itemId = parseInt(props.match.params.itemId, 10)
   return getItem(itemId)
}

Во-вторых, createSelector имеет следующий атрибут:

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

Это означает, что ваш пользовательский селектор будет пересчитываться при каждом изменении user, itemId, item, foo, bar. Про остальные input-селекторы сказать сложно, но я не вижу никакой связи между user и остальными. Таким образом, вы должны создать отдельный селектор только для user.

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

Что я обычно делаю, так это сглаживаю их следующим образом:

const getItem = (state, props) => {
   const itemId = parseInt(props.match.params.itemId, 10)
   return getItem(itemId)
}

const userSelector = createSelector(getUser, user => user)
const itemSelector = createSelector(getItem, item => item)

foo и bar сложнее, потому что в вашем коде селекторы ищут изменения в item, item.fooId и foo тоже пересчитывают. Нам все еще нужно вложить селекторы, но требуется разделение foo и bar:

const fooFromItemSelector = createSelector(itemSelector, item => getFoo(item.fooId))
const fooSelector = createSelector(fooFromItemSelector, foo => foo)

и то же самое касается bar.

Наконец, вы захотите сгруппировать их вместе, чтобы создать containerSelector:

import { createStructuredSelector } from 'reselect'

const containerSelector = createStructuredSelector({
    user: userSelector,
    item: itemSelector,
    foo: fooSelector,
    bar: barSelector
})
person blaz    schedule 05.05.2018
comment
Спасибо за обстоятельный ответ! Имеет смысл. У меня была проблема с некоторыми перепутанными геттерами/селекторами, у которых была неправильная подпись getStuff = id => state => state.stuff[id], и это привело меня к путанице в том, как использовать createSelector. Я перевернул и переименовал в getStuffSelector = state => id => state.stuff[id], чтобы я мог использовать его в createSelector таким образом, который очень близок к вашему ответу. Что вы думаете? - person Pandaiolo; 06.05.2018
comment
@Pandaiolo Думаю, это должно быть хорошо. - person blaz; 06.05.2018
comment
Итак, я думаю, что шаблон идентификации с const itemSelector = createSelector(getItem, item => item) не работает. Это в основном возвращает селектор селектора, потому что getItem возвращает не данные, а селектор. Мне удалось заставить его работать, вернув вместо этого getItem(itemId)(state), но тогда использование itemSelector бесполезно, так как вы можете просто использовать getItem в createStructuredSelector, и оно будет запомнено. Мысли? - person Pandaiolo; 07.05.2018
comment
(и решение в моем комментарии выше также не сработало, потому что оно запомнило функцию селектора, которая никогда не меняется, какое бы базовое значение оно ни получало позже) - person Pandaiolo; 07.05.2018