使用带参数的重新选择选择器

Posted

技术标签:

【中文标题】使用带参数的重新选择选择器【英文标题】:Use reselect selector with parameters 【发布时间】:2017-03-10 12:28:56 【问题描述】:

如何将附加参数传递给组合选择器?我正在尝试

• 获取数据

• 过滤数据

• 将自定义值添加到我的数据集/按 myValue 分组数据

export const allData = state => state.dataTable
export const filterText = state => state.filter.get('text')

export const selectAllData = createSelector(
  allData,
  (data) => data
)

export const selectAllDataFiltered = createSelector(
  [ selectAllData, filterText ],
  (data, text) => 
    return data.filter(item => 
      return item.name === text
    )
  
)

export const selectWithValue = createSelector(
  [ selectAllDataFiltered ],
  (data, myValue) => 
    console.log(myValue)
    return data
  
)

let data = selectWithValue(state, 'myValue')

console.log(myValue) 返回undefined

【问题讨论】:

【参考方案1】:

更新日期:2021 年 3 月 36 日

来自 Reselector 的解决方案: See detail

// selector.js
import  createSelector  from 'reselect'
import memoize from 'lodash.memoize'

const expensiveSelector = createSelector(
  state => state.items,
  items => memoize(
    minValue => items.filter(item => item.value > minValue)
  )
)

// App.js
const expensiveFilter = expensiveSelector(state)
// Another way if you're using redux:
// const expensiveFilter = useSelector(expensiveSelector)

const slightlyExpensive = expensiveFilter(100)
const veryExpensive = expensiveFilter(1000000)

旧:

这是我的方法。用reselect创建一个参数和返回函数的函数。

export const selectWithValue = (CUSTOM_PARAMETER) => createSelector(
  selectAllDataFiltered,
  (data) => 
    console.log(CUSTOM_PARAMETER)
    return data[CUSTOM_PARAMETER]
  
)

const data = selectWithValue('myValue')(myState);

【讨论】:

不建议在更改 CUSTOM_PARAMETER 的组件中使用它;否则它将最终创建多个选择器副本,具体取决于它以后如何使用,这可能非常昂贵,因为它正在记忆重复的输入/输出和可能被其他任何东西引用的无限数量的排列。 (但是,在您的选择器创建者中可能会很好;只是认为这不是问题所在) 这里解释。 github.com/reduxjs/…【参考方案2】:

这在Accessing React Props in Selectors 下的重新选择文档中有所介绍:

import  createSelector  from 'reselect'

const getVisibilityFilter = (state, props) =>
  state.todoLists[props.listId].visibilityFilter

const getTodos = (state, props) =>
  state.todoLists[props.listId].todos

const makeGetVisibleTodos = () => 
  return createSelector(
    [ getVisibilityFilter, getTodos ],
    (visibilityFilter, todos) => 
      switch (visibilityFilter) 
        case 'SHOW_COMPLETED':
          return todos.filter(todo => todo.completed)
        case 'SHOW_ACTIVE':
          return todos.filter(todo => !todo.completed)
        default:
          return todos
      
    
  )


export default makeGetVisibleTodos
const makeMapStateToProps = () => 
  const getVisibleTodos = makeGetVisibleTodos()
  const mapStateToProps = (state, props) => 
    return 
      todos: getVisibleTodos(state, props)
    
  
  return mapStateToProps

在这种情况下,传递给选择器的 props 是传递给 React 组件的 props,但 props 可以来自任何地方:

const getVisibleTodos = makeGetVisibleTodos()

const todos = getVisibleTodos(state, listId: 55)

查看以下类型以进行重新选择:

export type ParametricSelector<S, P, R> = (state: S, props: P, ...args: any[]) => R;
export function createSelector<S, P, R1, T>(
  selectors: [ParametricSelector<S, P, R1>],
  combiner: (res: R1) => T,
): OutputParametricSelector<S, P, T, (res: R1) => T>;

我们可以看到 props 的类型没有限制(ParametricSelect 中的 P 类型),所以它不需要是 object

【讨论】:

【参考方案3】:

这里有一个最新的useSelector 钩子。

重要的是从输入选择器中获取参数。输入选择器的第二个参数是我们如何获取它。

这是选择器的外观,

const selectNumOfTodosWithIsDoneValue = createSelector(
  (state) => state.todos,
  (_, isDone) => isDone, // this is the parameter we need
  (todos, isDone) => todos.filter((todo) => todo.isDone === isDone).length
)

下面是我们使用 useSelector 钩子提取值的方法,

export const TodoCounterForIsDoneValue = ( isDone ) => 
  const NumOfTodosWithIsDoneValue = useSelector((state) =>
    selectNumOfTodosWithIsDoneValue(state, isDone)
  )

  return <div>NumOfTodosWithIsDoneValue</div>

另外,尽量保持第二个参数(isDone)为primitive values(字符串、数字等)。 因为,reselect,只有在输入选择器值改变时才运行输出选择器。 这种变化是通过浅比较来检查的,对于像 Object 和 Array 这样的参考值,这总是错误的。

参考资料:

    https://react-redux.js.org/next/api/hooks#using-memoizing-selectors https://flufd.github.io/reselect-with-multiple-parameters/ https://blog.isquaredsoftware.com/2017/12/idiomatic-redux-using-reselect-selectors/

【讨论】:

【参考方案4】:

另一种选择:

const parameterizedSelector = (state, someParam) => createSelector(
  [otherSelector],
  (otherSelectorResult) => someParam + otherSelectorResult
);

然后用like

const mapStateToProps = state => (
  parameterizedSelectorResult: parameterizedSelector(state, 'hello')
);

虽然我不确定这种情况下的记忆/性能,但它确实有效。

【讨论】:

这种方法的问题是您在每次调用中都创建了选择器。因此,记忆将根本不起作用。【参考方案5】:

从选择器返回一个函数怎么样? getFilteredToDos 就是一个例子

// redux part
const state = 
  todos: [
     state: 'done',     text: 'foo' ,
     state: 'time out', text: 'bar' ,
  ],
;

// selector for todos
const getToDos = createSelector(
  getState,
  (state) => state.todos,
);

// selector for filtered todos
const getFilteredToDos = createSelector(
  getToDos,
  (todos) => (todoState) => todos.filter((toDo) => toDo.state === todoState);
);

// and in component
const mapStateToProps = (state, ownProps) => (
  ...ownProps,
  doneToDos: getFilteredToDos()('done')
);

【讨论】:

不过,这可能会影响记忆。 ...我实际上不认为是这样(与下面的答案不同)。每个过滤器没有记忆,但getToDos 有记忆。这是在不支持参数化选择器时重新选择所能达到的范围。【参考方案6】:

您的问题的答案在此处的常见问题解答中进行了详细说明:https://github.com/reactjs/reselect#q-how-do-i-create-a-selector-that-takes-an-argument

简而言之,reselect 不支持传递给选择器的任意参数。推荐的方法是将相同的数据存储在 Redux 状态中,而不是传递参数。

【讨论】:

不过,有时这是不可能的。在这种情况下,您可以使您的选择器成为接受参数并返回选择器的工厂。然后,您可以在 mapStateToProps 工厂中“创建”您的选择器,并使用您的参数范围内的选择器实例。我看到这种方法的唯一缺点是您必须在参数更改时手动重新创建选择器。 并假设您正在存储针对待办事项列表使用的搜索查询:getTodosSelector 和 getQuerySelector,因为即使您搜索相同的关键字两次,查询也会被视为已更改,因此永远不会命中缓存. @PhilippSpo - 这不会破坏记忆选择器的能力吗? @EliranMalka 只有在相关状态/道具实际发生变化时才重新创建选择器。否则,是的,在每次渲染中重新创建选择器时,它会破坏记忆。

以上是关于使用带参数的重新选择选择器的主要内容,如果未能解决你的问题,请参考以下文章

swift 带参数的选择器

重新选择 - 如何确定哪个参数发生了变化?

带参数的Mixin

如何使用 yield select 管理接受参数的选择器?

带间隔的 SKSpriteNode 选择器

重新选择错误:选择器创建者希望所有输入选择器都是函数