在基于对象的缩减器上使用带有日期字段的重新选择

Using reselect with a date field on an object-based reducer

我有一个简单的任务列表应用程序。其中一个屏幕是 "Today & Overdue" 列表。

任务减速器看起来像:

{
   "data": {
      123: {
         "id": 123,
         "summary": "blah blah",
         "dueDate": "2020-03-12",
         "completed": true
      },
      456: {
         "id": 456,
         "summary": "some other task",
         "dueDate": "2020-03-12",
         "completed": false
      }
   },
   "byId": [123, 456]
}

我的 list reducer 看起来像:

{
   "data": {
      919: {
         "id": 919,
         "name": "Today & Overdue"
      },
      818: {
         "id": 818,
         "summary": "My Cool List"
      }
   },
   "byId": [919, 818]
}

在 "Today & Overdue" 列表中,我需要获取 dueDate 今天或更早的所有任务。我尝试使用重新选择来优化列表屏幕的性能,方法是:

# Get end of day today
const now = moment();
const endOfDay = Date.parse(now.endOf("day").utc(true).utcOffset(0).format());


const getTasksTodayOrOlder = (state) => Object.values(state.tasks.data).filter(task => Date.parse(task.dueDate) <= endOfDay);

但似乎只要任务数据中的某个字段发生更改(即已完成或摘要),getTasksTodayOrOlder 就会重新生成选择器。

这是在 tasks reducer 上保留缓存的唯一方法; byDueDate 之类的东西来跟踪一组截止日期数组。


{
   "data": ...,
   "byId": ...,
   "byDueDate": {
       "2020-03-19": [112,123,141, ...],
       "2020-03-20": [922, 939, ...],
   }
}

日期缓存似乎开销很大,可能会不同步。

推荐的处理重新选择的方法是:

"regenerates the selector"是什么意思?你在哪里使用这个选择器?例如,如果您通过钩子(useSelector)在 React 功能组件中使用它,那么:

// selectors

const now = moment();
const endOfDay = Date.parse(now.endOf("day").utc(true).utcOffset(0).format());

const getTasks = (state) => state.tasks.data;

const getTasksTodayOrOlder = createSelector(getTasks, tasks => 
  Object.values(tasks)
    .filter(task => Date.parse(task.dueDate) <= endOfDay)
    .map(({ id, dueDate }) => {id, dueDate});

// component

import { shallowEqual, useSelector } from 'react-redux';
.
.
.
const tasksTodayOrOlderList = useSelector(getTasksTodayOrOlder, shallowEqual);

任何时候 state.tasks.data 中的某些内容发生变化,getTasksTodayOrOlder 都会重新计算,但如果 tasksTodayOrOlderList 的先前状态与 [=12] 的当前输出一样浅,您将不会重新渲染=] 选择器(对象内的所有值都相等),因为我们将第二个参数 shallowEqual 传递给我们的 useSelector 函数。我使用 map 从我们的数据对象中删除了不必要的属性 "tracking"。

而且我们需要将我们的选择器一分为二,因为我们只需要在 state.tasks.data 发生变化时重新计算,而不是在状态的任何部分发生变化时重新计算。

此外,我认为您应该使用 endOfDay 作为选择器的参数值,因为它是动态的。

如果选择器的输出是使用 Object.keys、Object.values 或 Array.prototype.filter 的计算数组,那么您可以通过以下方式记忆它:

const { createSelector, defaultMemoize } = Reselect;

const state = [
  { val: 1 },
  { val: 2 },
  { val: 3 },
  { val: 4 },
  { val: 5 },
  { val: 6 },
  { val: 7 },
];
//pass an array to memArray like [a,b], as long as a and b are the same
//  you will get the same array back even if the arrays themselves
//  are not the same like when you use filter, Object.values or Object.keys
const memArray = (() => {
  const mem = defaultMemoize((...args) => args);
  //pass the array that this function receives to a memoized function
  //  as separate arguments so if [a,b] is received it'll call
  //  memoized(a,b)
  return arr => mem(...arr);
})();//this is called an IIFE

const selectHigher = createSelector(
  state => state,
  (_, min) => min,
  (data, min) =>
    memArray(
      Object.values(data).filter(({ val }) => val > min)
    )
);
const one = selectHigher(state, 5);
const twoState = [...state, { val: 0 }];
const two = selectHigher(twoState, 5);
console.log('two is:',two);
console.log('one and two are equal', one === two);
const threeState = [...state, { val: 8 }];
const three = selectHigher(threeState, 5);
console.log('three is:',three);
console.log('three and two are equal', three === two);
<script src="https://cdnjs.cloudflare.com/ajax/libs/reselect/4.0.0/reselect.min.js"></script>