跳到主要内容

weakMapMemoize

lruMemoize 需要明确配置缓存大小大于1,并在内部使用LRU缓存。

weakMapMemoize 根据其被调用的参数(在这种情况下,是从你的输入选择器中提取的值)的身份,创建了一个基于 WeakMap 的缓存节点树。这使得 weakMapMemoize 具有实际上无限的缓存大小。只要对参数的引用仍然存在,缓存结果将被保留在内存中,然后在参数被垃圾收集时清除。

设计权衡

  • 优点:

    • 它具有实际上无限的缓存大小,但你无法控制值在缓存中保留多长时间,因为它基于垃圾收集和 WeakMap
  • 缺点:

使用案例

  • 这个记忆器可能最适合需要用许多不同的参数调用同一个选择器实例的情况,例如在列表项组件中使用的单个选择器实例,并用像这样的项目ID调用:
useSelector(state => selectSomeData(state, id))

在使用 weakMapMemoize 之前,你遇到了这样的问题:

weakMapMemoize/cacheSizeProblem.ts
import { createSelector } from 'reselect'

export interface RootState {
items: { id: number; category: string; name: string }[]
}

const state: RootState = {
items: [
{ id: 1, category: 'Electronics', name: 'Wireless Headphones' },
{ id: 2, category: 'Books', name: 'The Great Gatsby' },
{ id: 3, category: 'Home Appliances', name: 'Blender' },
{ id: 4, category: 'Stationery', name: 'Sticky Notes' }
]
}

const selectItemsByCategory = createSelector(
[
(state: RootState) => state.items,
(state: RootState, category: string) => category
],
(items, category) => items.filter(item => item.category === category)
)

selectItemsByCategory(state, 'Electronics') // 选择器运行
selectItemsByCategory(state, 'Electronics')
selectItemsByCategory(state, 'Stationery') // 选择器运行
selectItemsByCategory(state, 'Electronics') // 选择器再次运行!

在此之前,你可以通过多种不同的方式来解决这个问题:

  1. 使用 lruMemoize 设置 maxSize
weakMapMemoize/setMaxSize.ts
import { createSelector, lruMemoize } from 'reselect'
import type { RootState } from './cacheSizeProblem'

const selectItemsByCategory = createSelector(
[
(state: RootState) => state.items,
(state: RootState, category: string) => category
],
(items, category) => items.filter(item => item.category === category),
{
memoize: lruMemoize,
memoizeOptions: {
maxSize: 10
}
}
)

但这需要提前知道缓存大小。

  1. 使用 useMemo 创建唯一的选择器实例。
weakMapMemoize/withUseMemo.tsx
import type { FC } from 'react'
import { useMemo } from 'react'
import { useSelector } from 'react-redux'
import { createSelector } from 'reselect'
import type { RootState } from './cacheSizeProblem'

const makeSelectItemsByCategory = (category: string) =>
createSelector([(state: RootState) => state.items], items =>
items.filter(item => item.category === category)
)

interface Props {
category: string
}

const MyComponent: FC<Props> = ({ category }) => {
const selectItemsByCategory = useMemo(
() => makeSelectItemsByCategory(category),
[category]
)

const itemsByCategory = useSelector(selectItemsByCategory)

return (
<div>
{itemsByCategory.map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
)
}
  1. 使用 Re-reselect
import { createCachedSelector } from 're-reselect'

const selectItemsByCategory = createCachedSelector(
[
(state: RootState) => state.items,
(state: RootState, category: string) => category
],
(items, category) => items.filter(item => item.category === category)
)((state: RootState, category: string) => category)

从5.0.0版本开始,你可以使用 weakMapMemoize 来消除这个问题。

weakMapMemoize/cacheSizeSolution.ts
import { createSelector, weakMapMemoize } from 'reselect'
import type { RootState } from './cacheSizeProblem'

const state: RootState = {
items: [
{ id: 1, category: 'Electronics', name: 'Wireless Headphones' },
{ id: 2, category: 'Books', name: 'The Great Gatsby' },
{ id: 3, category: 'Home Appliances', name: 'Blender' },
{ id: 4, category: 'Stationery', name: 'Sticky Notes' }
]
}

const selectItemsByCategory = createSelector(
[
(state: RootState) => state.items,
(state: RootState, category: string) => category
],
(items, category) => items.filter(item => item.category === category),
{
memoize: weakMapMemoize,
argsMemoize: weakMapMemoize
}
)

selectItemsByCategory(state, 'Electronics') // 选择器运行
selectItemsByCategory(state, 'Electronics') // 缓存
selectItemsByCategory(state, 'Stationery') // 选择器运行
selectItemsByCategory(state, 'Electronics') // 仍然缓存!

这解决了在创建记忆选择器之前需要知道和设置缓存大小的问题。因为 weakMapMemoize 本质上提供了一个开箱即用的动态缓存大小。

参数

名称描述
func需要被记忆的函数。

返回

一个带有 .clearCache() 方法的记忆函数。

类型参数

名称描述
Func被记忆的函数的类型。

示例

使用 weakMapMemoizecreateSelector

weakMapMemoize/usingWithCreateSelector.ts
import { createSelector, weakMapMemoize } from 'reselect'
import type { RootState } from './cacheSizeProblem'

const state: RootState = {
items: [
{ id: 1, category: 'Electronics', name: 'Wireless Headphones' },
{ id: 2, category: 'Books', name: 'The Great Gatsby' },
{ id: 3, category: 'Home Appliances', name: 'Blender' },
{ id: 4, category: 'Stationery', name: 'Sticky Notes' }
]
}

const selectItemsByCategory = createSelector(
[
(state: RootState) => state.items,
(state: RootState, category: string) => category
],
(items, category) => items.filter(item => item.category === category),
{
memoize: weakMapMemoize,
argsMemoize: weakMapMemoize
}
)

selectItemsByCategory(state, 'Electronics') // 选择器运行
selectItemsByCategory(state, 'Electronics')
selectItemsByCategory(state, 'Stationery') // 选择器运行
selectItemsByCategory(state, 'Electronics')

使用 weakMapMemoizecreateSelectorCreator

weakMapMemoize/usingWithCreateSelectorCreator.ts
import { createSelectorCreator, weakMapMemoize } from 'reselect'
import type { RootState } from './cacheSizeProblem'

const state: RootState = {
items: [
{ id: 1, category: 'Electronics', name: 'Wireless Headphones' },
{ id: 2, category: 'Books', name: 'The Great Gatsby' },
{ id: 3, category: 'Home Appliances', name: 'Blender' },
{ id: 4, category: 'Stationery', name: 'Sticky Notes' }
]
}

const createSelectorWeakMap = createSelectorCreator({
memoize: weakMapMemoize,
argsMemoize: weakMapMemoize
})

const selectItemsByCategory = createSelectorWeakMap(
[
(state: RootState) => state.items,
(state: RootState, category: string) => category
],
(items, category) => items.filter(item => item.category === category)
)

selectItemsByCategory(state, 'Electronics') // 选择器运行
selectItemsByCategory(state, 'Electronics')
selectItemsByCategory(state, 'Stationery') // 选择器运行
selectItemsByCategory(state, 'Electronics')