How to create a custom equality function with reselect and Typescript?

a 夏天 提交于 2020-06-27 16:22:13

问题


A standard reselect selector invalidates its memoized value and recomputes it if the input selectors fail a strict equality check:

export const selectEmailsFromComments = createSelector(
  selectComments, // returns an array of comments
  comments => commentsToEmails(comments)
)

Since the comments are an array and not a primitive value, and redux reducers tend to create a new piece of state to avoid side effects, the above seems to never actually memoize because the comments array returned by selectComments will always have a different reference.

To resolve this, one can create a custom selector creator, e.g. to introduce shallow equality checks:

const createShallowEqualSelector = createSelectorCreator(
  defaultMemoize,
  shallowEqual
)

export const selectEmailsFromComments = createShallowEqualSelector(
  selectComments, // returns an array of comments
  comments => commentsToEmails(comments)
)

This works if the comments indeed are simple objects and we want to recompute the emails whenever any of the comments' props changed.

But what if we only want to recompute the emails if e.g. the number of comments changed? How could we implement a custom equality check? I would expect the following to work:

type ComparisonFunction<B extends object = {}> = (prev: B, next: B, index: number) => boolean

const createCustomEqualSelector = <B extends object = {}>(
  equalFn: ComparisonFunction<B>
) => createSelectorCreator<ComparisonFunction<B>>(defaultMemoize, equalFn)

const commentsEqualFn = (a: IComment[], b: IComment[], index: number) =>
  a.length === b.length

export const selectEmailsFromComments = createCustomEqualSelector(
  commentsEqualFn
)(
  selectComments, // returns an array of comments
  comments => commentsToEmails(comments)
)

However this returns the following Typescript error for defaultMemoize:

(alias) function defaultMemoize<F extends Function>(func: F, equalityCheck?: (<T>(a: T, b: T, index: number) => boolean) | undefined): F
import defaultMemoize
Argument of type '<F extends Function>(func: F, equalityCheck?: (<T>(a: T, b: T, index: number) => boolean) | undefined) => F' is not assignable to parameter of type '<F extends Function>(func: F, option1: ComparisonFunction<B>) => F'.
  Types of parameters 'equalityCheck' and 'option1' are incompatible.
    Type 'ComparisonFunction<B>' is not assignable to type '<T>(a: T, b: T, index: number) => boolean'.
      Types of parameters 'prev' and 'a' are incompatible.
        Type 'T' is not assignable to type 'B'.ts(2345)

How would I resolve this type error for a custom reselect createSelector equality function?


回答1:


The following worked for me but I'm not sure if it's the best way.

import {
  createSelectorCreator,
  defaultMemoize,
} from 'reselect';

type IComment = { id: number };
type State = { comments: IComment[] };
type CompareFn = <T>(a: T, b: T, index: number) => boolean;
const createCustomEqualSelector = (equalFn: CompareFn) =>
  createSelectorCreator(defaultMemoize, equalFn);
const selectComments = (state: State) => state.comments;
const commentsEqualFn: CompareFn = (a, b, index) =>
  //need to cast it or get an error:
  //  Property 'length' does not exist on type 'T'
  ((a as unknown) as IComment[]).length ===
  ((b as unknown) as IComment[]).length;

export const selectEmailsFromComments = createCustomEqualSelector(
  commentsEqualFn
)(
  selectComments, // returns an array of comments
  (comments) => {
    console.log('calculating comments:', comments);
    return comments;
  }
);
selectEmailsFromComments({ comments: [{ id: 1 }] })
//this will log previous value because array length didn't change
console.log('memoized', selectEmailsFromComments({ comments: [{ id: 2 }] }))


来源:https://stackoverflow.com/questions/62480158/how-to-create-a-custom-equality-function-with-reselect-and-typescript

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!