🎈 perf(购物页面): 优化购物页面的性能

This commit is contained in:
xuan 2022-11-24 19:00:07 +08:00
parent bf80f95ead
commit 34abf8fc7f
9 changed files with 191 additions and 81 deletions

View File

@ -49,6 +49,7 @@
"@tarojs/taro-h5": "^3.5.5", "@tarojs/taro-h5": "^3.5.5",
"big.js": "^6.2.1", "big.js": "^6.2.1",
"dayjs": "^1.11.3", "dayjs": "^1.11.3",
"immer": "^9.0.16",
"qs": "^6.10.3", "qs": "^6.10.3",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",

View File

@ -236,5 +236,5 @@
] ]
} }
}, },
"libVersion": "2.26.2" "libVersion": "2.27.3"
} }

View File

@ -11,7 +11,6 @@ interface PropsType {
} }
const InputX: FC<PropsType> = (props) => { const InputX: FC<PropsType> = (props) => {
const {customClassName, customStyle,customInputStyle, customInputClassName,...inputProps} = props const {customClassName, customStyle,customInputStyle, customInputClassName,...inputProps} = props
console.log('props', props);
return ( return (
<View className={customClassName} style={customStyle}> <View className={customClassName} style={customStyle}>
<CustomWrapper> <CustomWrapper>

View File

@ -10,21 +10,30 @@ import { EnumSaleMode } from '@/common/Enumerate'
import { selectList } from '../../config' import { selectList } from '../../config'
import { AdjestShoppingCartApi } from '@/api/shopping/index' import { AdjestShoppingCartApi } from '@/api/shopping/index'
import { Goods, ShoppingDispatchType, ShoppingStateContextValue, useShoppingDispatch, useShoppingState } from '../../context' import { Goods, ShoppingDispatchType, ShoppingStateContextValue, useShoppingDispatch, useShoppingState } from '../../context'
import { ShoppingStore } from '../../context/shoppingStore' import { events, ShoppingStore } from '../../context/shoppingStore'
import LabAndImg from '@/components/LabAndImg' import LabAndImg from '@/components/LabAndImg'
import { usePropsValue } from '@/use/useCommon'
type PropsType = { type PropsType = {
state?: { state?: {
multipleSelection: Goods[]
Observer: ShoppingStore Observer: ShoppingStore
} }
purchaserId: number purchaserId: number
itemData: Record<string, any> & object itemData: Record<string, any> & object
checked?: boolean
onChange?: (isChecked: boolean, goodsId: number) => void
} }
const ColorKindItem: FC<PropsType> = props => { const ColorKindItem: FC<PropsType> = props => {
const { state, purchaserId, itemData } = props console.log('rerender component ColorKindItem', props);
const dispatch = useShoppingDispatch() const { state, purchaserId, itemData, checked = false } = props
const [isChecked, setCheck] = usePropsValue({
value: checked,
defaultValue: checked,
onChange: (value) => {
props.onChange?.(value, itemData.id)
}
})
//格式化数量 //格式化数量
const formatCount = itemData => { const formatCount = itemData => {
@ -32,6 +41,7 @@ const ColorKindItem: FC<PropsType> = props => {
} }
useEffect(() => { useEffect(() => {
console.log('itemData==>',itemData);
setCount(formatCount(itemData)) setCount(formatCount(itemData))
}, [itemData.roll, itemData.length]) }, [itemData.roll, itemData.length])
@ -48,43 +58,27 @@ const ColorKindItem: FC<PropsType> = props => {
} }
//格式化单位 //格式化单位
const formatUnit = itemData => { const formatUnit = itemData => {
return itemData.sale_mode == EnumSaleMode.Bulk ? '条' : '米' return itemData.sale_mode == EnumSaleMode.Bulk ? '条' : '米'
} }
// 选中 // 选中
const handleSelect = () => { const handleSelect = () => {
dispatch({ const payload = {
type: ShoppingDispatchType.UPDATE_CHANGED_CHECKBOX, id: itemData?.id,
data: { estimate_amount: itemData.estimate_amount,
purchaserId: purchaserId, product_code: itemData.product_code,
multipleSelection: { product_color_code: itemData.product_color_code,
...state?.multipleSelection, sale_mode: itemData.sale_mode,
[itemData.id]: { count: itemData.sale_mode === EnumSaleMode.Bulk ? itemData.roll : Number(formatMeterDiv(itemData.length)),
id: itemData?.id, }
estimate_amount: itemData.estimate_amount, events.trigger('updatePurchaserMultipleSelection', purchaserId, payload, 'add', itemData?.id)
product_code: itemData.product_code, setCheck(true)
product_color_code: itemData.product_color_code,
sale_mode: itemData.sale_mode,
count: itemData.sale_mode === EnumSaleMode.Bulk ? itemData.roll : Number(formatMeterDiv(itemData.length)),
}
},
},
})
} }
// 未选中 // 未选中
const handleClose = () => { const handleClose = () => {
const temp = {...state?.multipleSelection} events.trigger('updatePurchaserMultipleSelection', purchaserId, null, 'delete', itemData.id)
delete temp?.[itemData.id] setCheck(false)
console.log('handleClose==>',temp);
dispatch({
type: ShoppingDispatchType.UPDATE_CHANGED_CHECKBOX,
data: {
purchaserId: purchaserId,
multipleSelection: temp,
},
})
} }
const { fetchData } = AdjestShoppingCartApi() const { fetchData } = AdjestShoppingCartApi()
@ -99,7 +93,7 @@ const ColorKindItem: FC<PropsType> = props => {
itemData.roll = num itemData.roll = num
targetColor.roll = num targetColor.roll = num
} else { } else {
itemData.length = num itemData.length = formatMeterMul(num)
targetColor.length = formatMeterMul(num) targetColor.length = formatMeterMul(num)
} }
const res = await fetchData({ const res = await fetchData({
@ -118,10 +112,10 @@ const ColorKindItem: FC<PropsType> = props => {
return ( return (
<MCheckbox <MCheckbox
status={state?.multipleSelection?.hasOwnProperty(itemData.id) || false} status={isChecked}
onSelect={handleSelect} onSelect={handleSelect}
onClose={handleClose} onClose={handleClose}
customClassName={classnames(styles.checkbox, state?.multipleSelection?.hasOwnProperty(itemData.id) ? styles.selected : '')} customClassName={classnames(styles.checkbox, isChecked ? styles.selected : '')}
customTextClass={styles.colorKindItem}> customTextClass={styles.colorKindItem}>
<View className={styles['colorKindItem__left']}> <View className={styles['colorKindItem__left']}>
<LabAndImg value={labAndImgObj(itemData)} /> <LabAndImg value={labAndImgObj(itemData)} />
@ -157,7 +151,26 @@ const ColorKindItem: FC<PropsType> = props => {
// State 分割组件 思路就是把 context 直接通过 props 的形式传给组件,这样的话就解决了 context 强制刷新 memo 的问题了 // State 分割组件 思路就是把 context 直接通过 props 的形式传给组件,这样的话就解决了 context 强制刷新 memo 的问题了
// 那么当 context 内的 value 被更新的时候react 只会强制渲染 Wrapper // 那么当 context 内的 value 被更新的时候react 只会强制渲染 Wrapper
const withStateSlice = (comp, slice) => { const withStateSlice = (comp, slice) => {
const MemoComp = memo(comp) const MemoComp = memo(comp, (prevProps, nextProps) => {
let needMemo = true
if (prevProps.itemData !== nextProps.itemData) {
console.log('itemData 有变化');
needMemo = false
}
if (prevProps.state.Observer !== nextProps.state.Observer) {
console.log('Observer 有变化');
needMemo = false
}
if (prevProps.checked !== nextProps.checked) {
console.log('checked 有变化', prevProps.checked, nextProps.checked);
needMemo = false
}
if (prevProps.purchaserId !== nextProps.purchaserId) {
console.log('purchaserId 有变化');
needMemo = false
}
return needMemo
})
const Wrapper = (props, ref) => { const Wrapper = (props, ref) => {
const state = useShoppingState() const state = useShoppingState()
return <MemoComp ref={ref} state={slice(state, props)} {...props} /> return <MemoComp ref={ref} state={slice(state, props)} {...props} />
@ -167,7 +180,6 @@ const withStateSlice = (comp, slice) => {
const ColorKindItemWithStateSlice = withStateSlice(ColorKindItem, (state: ShoppingStateContextValue, props: PropsType) => { const ColorKindItemWithStateSlice = withStateSlice(ColorKindItem, (state: ShoppingStateContextValue, props: PropsType) => {
return { return {
multipleSelection: state.colorStore[props.purchaserId]['multipleSelection'],
Observer: state.Observer, Observer: state.Observer,
} }
}) })

View File

@ -1,3 +1,4 @@
import produce from 'immer'
import { FC, ReactNode, useEffect, useMemo, useReducer, useRef } from 'react' import { FC, ReactNode, useEffect, useMemo, useReducer, useRef } from 'react'
import { import {
GoodsMeta, GoodsMeta,
@ -7,6 +8,7 @@ import {
ShoppingDispatchType, ShoppingDispatchType,
shoppingReducer, shoppingReducer,
ShoppingStateContext, ShoppingStateContext,
throwError,
} from '../../context' } from '../../context'
import { ColorStore, ShoppingStateContextValue } from '../../context' import { ColorStore, ShoppingStateContextValue } from '../../context'
import { ShoppingStore } from '../../context/shoppingStore' import { ShoppingStore } from '../../context/shoppingStore'

View File

@ -1,5 +1,5 @@
import { Text, View } from '@tarojs/components' import { Text, View } from '@tarojs/components'
import { FC, forwardRef, memo, useEffect, useMemo, useRef, useState, useTransition } from 'react' import { FC, forwardRef, memo, useCallback, useEffect, useMemo, useRef, useState, useTransition } from 'react'
import styles from './index.module.scss' import styles from './index.module.scss'
import classnames from 'classnames' import classnames from 'classnames'
import { formatMeterDiv } from '@/common/format' import { formatMeterDiv } from '@/common/format'
@ -15,8 +15,10 @@ import { alert, isEmptyObject } from '@/common/common'
import classNames from 'classnames' import classNames from 'classnames'
import LoadingCard from '@/components/loadingCard' import LoadingCard from '@/components/loadingCard'
import { ShoppingCartListApi } from '@/api' import { ShoppingCartListApi } from '@/api'
import { ShoppingStore } from '../../context/shoppingStore' import { events, ShoppingStore } from '../../context/shoppingStore'
import { usePropsValue } from '@/use/useCommon' import { usePropsValue } from '@/use/useCommon'
import produce, { setAutoFreeze } from 'immer'
import { nextTick } from '@tarojs/taro'
interface ButtonPropsType { interface ButtonPropsType {
isActive: boolean isActive: boolean
@ -24,6 +26,7 @@ interface ButtonPropsType {
children?: React.ReactNode children?: React.ReactNode
customStyle?: React.CSSProperties customStyle?: React.CSSProperties
} }
type OperationType = 'add' | 'delete'
// 订单类型 // 订单类型
const SaleModeButton: FC<ButtonPropsType> = props => { const SaleModeButton: FC<ButtonPropsType> = props => {
const { onClick, children, isActive = false, customStyle } = props const { onClick, children, isActive = false, customStyle } = props
@ -62,7 +65,7 @@ type PropsType = {
const ShoppingCartItem: FC<PropsType> = props => { const ShoppingCartItem: FC<PropsType> = props => {
const { state } = props const { state } = props
console.log('props ShoppingCartItem', props) console.log('rerender component ShoppingCartItem', props)
const [itemData, setItemData] = usePropsValue({ const [itemData, setItemData] = usePropsValue({
value: props.itemData, value: props.itemData,
@ -143,9 +146,45 @@ const ShoppingCartItem: FC<PropsType> = props => {
const [isPending, startTransition] = useTransition() const [isPending, startTransition] = useTransition()
const { fetchData } = ShoppingCartListApi() const { fetchData } = ShoppingCartListApi()
// 更新当前客户的多选项
const updatePurchaserMultipleSelection = (purchaserId, payload, operationType: OperationType, goodsId: number) => {
if(operationType === 'add'){
dispatch({
type: ShoppingDispatchType.UPDATE_CHANGED_CHECKBOX,
data: {
purchaserId: purchaserId,
multipleSelection: produce(multipleSelection, (multipleSelectionDraft) =>{
if(!multipleSelectionDraft){
return {
[goodsId]: payload
}
}else{
multipleSelectionDraft[goodsId] = payload
}
}),
},
})
}else if(operationType === 'delete'){
const temp = {...multipleSelection}
delete temp?.[goodsId]
dispatch({
type: ShoppingDispatchType.UPDATE_CHANGED_CHECKBOX,
data: {
purchaserId: purchaserId,
multipleSelection: temp,
},
})
}
}
// 发布订阅
useEffect(()=>{
events.on('updatePurchaserMultipleSelection', updatePurchaserMultipleSelection)
return () => {
events.off('updatePurchaserMultipleSelection', updatePurchaserMultipleSelection)
}
}, [updatePurchaserMultipleSelection])
// 发布订阅 // 发布订阅
useEffect(() => { useEffect(() => {
console.log('update multipleSelection',multipleSelection); console.log('update multipleSelection',multipleSelection);
const unsubscribe = state?.Observer?.subscribe(async id => { const unsubscribe = state?.Observer?.subscribe(async id => {
@ -290,25 +329,60 @@ interface GoodsListPropType {
} }
const GoodsList = memo<GoodsListPropType>(props => { const GoodsList = memo<GoodsListPropType>(props => {
const { itemData, selected, isPending, startTransition, multipleSelection } = props const { itemData, selected, isPending, startTransition, multipleSelection } = props
const prevMultipleSelection = useRef(multipleSelection)
const currentSelected = useRef<EnumSaleMode | null>(null) const currentSelected = useRef<EnumSaleMode | null>(null)
const dispatch = useShoppingDispatch() const dispatch = useShoppingDispatch()
const [component, setComponent] = useState<JSX.Element | null>(null) const [component, setComponent] = useState<JSX.Element | null>(null)
// 使用 produce 更新特定的 ColorKindItem
const updateSpecifiedComponent = () => {
let newId
if(multipleSelection && prevMultipleSelection.current !== multipleSelection){
for(let key in multipleSelection){
if(!prevMultipleSelection.current?.hasOwnProperty(key)){
newId = key
break
}
}
}
console.log('multipleSelection==+>',multipleSelection);
console.log('currentSelected', currentSelected.current, selected);
console.log('component',component);
if(component){
if(itemData?.[BackEndSaleModeListFieldMap[selected]].length !== 0){
setComponent(produce(component, (draft) => {
console.log('prev',component);
const index = (draft as unknown as any[]).findIndex((item)=> item.key === newId)
console.log('index',index)
if(index !== -1) {
const item = itemData?.[BackEndSaleModeListFieldMap[selected]].find(item => {
return item.id === Number(newId)
})
console.log('item', item, newId);
draft![index] = <ColorKindItem checked={multipleSelection?.hasOwnProperty(newId)} purchaserId={itemData?.purchaser_id} key={newId} itemData={item}></ColorKindItem>
}
}))
}else{
setComponent(<View className={styles.noList}></View>)
}
}
prevMultipleSelection.current = multipleSelection
}
// 更新 GoodsList 组件 // 更新 GoodsList 组件
const updateComponent = () => { const updateComponent = () => {
setComponent( setComponent(
itemData?.[BackEndSaleModeListFieldMap[selected]].length !== 0 ? ( () => itemData?.[BackEndSaleModeListFieldMap[selected]].length !== 0 ? (
itemData?.[BackEndSaleModeListFieldMap[selected]].map(item => { itemData?.[BackEndSaleModeListFieldMap[selected]].map(item => {
console.log('item===>', item) console.log('item===>', item, multipleSelection?.hasOwnProperty(item.id))
return <ColorKindItem purchaserId={itemData.purchaser_id} key={item.id} itemData={item}></ColorKindItem> return <ColorKindItem checked={multipleSelection?.hasOwnProperty(item.id)} purchaserId={itemData.purchaser_id} key={item.id} itemData={item}></ColorKindItem>
}) })
) : ( ) : (
<View className={styles.noList}></View> <View className={styles.noList}></View>
), )
) )
prevMultipleSelection.current = multipleSelection
} }
useEffect(() => { useEffect(() => {
@ -337,8 +411,8 @@ const GoodsList = memo<GoodsListPropType>(props => {
}) })
updateComponent() updateComponent()
} else { } else {
// 重新把当前的选中状态赋值给ref 作为下一次比较的旧状态
currentSelected.current = selected currentSelected.current = selected
// 重新把当前的选中状态赋值给ref 作为下一次比较的旧状态
dispatch({ dispatch({
type: ShoppingDispatchType.UPDATE_CHANGED_CHECKBOX, type: ShoppingDispatchType.UPDATE_CHANGED_CHECKBOX,
data: { data: {
@ -368,28 +442,32 @@ const GoodsList = memo<GoodsListPropType>(props => {
const withStateSlice = (comp, slice) => { const withStateSlice = (comp, slice) => {
const MemoComp = memo(comp, (prevProps, nextProps) => { const MemoComp = memo(comp, (prevProps, nextProps) => {
let needMemo = true let needMemo = true
// const prevCheckedPurchaserId = prevProps.state.currentCheckedPurchaserId
// const nextCheckedPurchaserId = nextProps.state.currentCheckedPurchaserId
// const purchaser_id = prevProps.itemData.purchaser_id
// const prevMultipleSelection = prevProps.state.colorStore[purchaser_id].multipleSelection
// const nextMultipleSelection = nextProps.state.colorStore[purchaser_id].multipleSelection
if (JSON.stringify(prevProps.itemData) !== JSON.stringify(nextProps.itemData)) { if (JSON.stringify(prevProps.itemData) !== JSON.stringify(nextProps.itemData)) {
needMemo = false needMemo = false
} }
if(prevProps.itemData.purchaser_name === 'Hhjh'){ if(prevProps.itemData.purchaser_name === 'JENNIE'){
console.log('------withStateSlice props-------'); console.log('------withStateSlice props-------');
console.log('withStateSlice props prevProps', prevProps); console.log('withStateSlice props prevProps', prevProps);
console.log('withStateSlice props prevProps comparison itemData', prevProps.itemData === nextProps.itemData);
console.log('withStateSlice props nextProps', nextProps); console.log('withStateSlice props nextProps', nextProps);
console.log('withStateSlice props prevProps comparison multipleSelection', prevProps.state.multipleSelection === nextProps.state.multipleSelection);
console.log('withStateSlice props prevProps comparison currentCheckedPurchaserId', prevProps.state.currentCheckedPurchaserId === nextProps.state.currentCheckedPurchaserId);
console.log('withStateSlice props prevProps comparison Observer', prevProps.state.Observer === nextProps.state.Observer);
console.log('withStateSlice props prevProps comparison state', prevProps.state === nextProps.state);
console.log('------withStateSlice props-------'); console.log('------withStateSlice props-------');
} }
if (JSON.stringify(prevProps.state) !== JSON.stringify(nextProps.state)) { if (prevProps.state.Observer !== nextProps.state.Observer) {
console.log('MultipleSelection 有变化'); console.log('Observer 有变化');
needMemo = false
}
if (prevProps.state.multipleSelection !== nextProps.state.multipleSelection) {
console.log('multipleSelection 有变化');
needMemo = false
}
if (prevProps.state.currentCheckedPurchaserId !== nextProps.state.currentCheckedPurchaserId) {
console.log('currentCheckedPurchaserId 有变化');
needMemo = false needMemo = false
} }
// if (JSON.stringify(prevCheckedPurchaserId) !== JSON.stringify(nextCheckedPurchaserId)) {
// console.log('checkedPurchaserId 有变化');
// needMemo = false
// }
return needMemo return needMemo
}) })
const Wrapper = (props, ref) => { const Wrapper = (props, ref) => {

View File

@ -1,4 +1,5 @@
import { EnumSaleMode } from '@/common/Enumerate' import { EnumSaleMode } from '@/common/Enumerate'
import produce from 'immer'
import React, { Dispatch } from 'react' import React, { Dispatch } from 'react'
import { useContext } from 'react' import { useContext } from 'react'
import { ShoppingStore } from './shoppingStore' import { ShoppingStore } from './shoppingStore'
@ -88,44 +89,54 @@ export type ShoppingAction = {
data?: any data?: any
} }
export function shoppingReducer(state: ShoppingStateContextValue, action: ShoppingAction) { export const shoppingReducer = produce((draft: ShoppingStateContextValue, action: ShoppingAction)=>{
const { type, data } = action const { type, data } = action
switch (type) { switch (type) {
case ShoppingDispatchType.UPDATE_MANAGE_STATUS: { case ShoppingDispatchType.UPDATE_MANAGE_STATUS: {
return { ...state, isManageStatus: data } draft.isManageStatus = data
break
} }
case ShoppingDispatchType.UPDATE_MULTIPLE_SELECTION_STATUS: { case ShoppingDispatchType.UPDATE_MULTIPLE_SELECTION_STATUS: {
return { ...state, isMultipleSelection: data } draft.isMultipleSelection = data
break
} }
case ShoppingDispatchType.UPDATE_CURRENT_CHECKED_PURCHASERID: { case ShoppingDispatchType.UPDATE_CURRENT_CHECKED_PURCHASERID: {
return { ...state, currentCheckedPurchaserId: data } draft.currentCheckedPurchaserId = data
break
} }
case ShoppingDispatchType.UPDATE_CURRENT_CHECKED_SALEMODE: { case ShoppingDispatchType.UPDATE_CURRENT_CHECKED_SALEMODE: {
return { ...state, currentCheckedSaleMode: data } draft.currentCheckedSaleMode = data
break
} }
case ShoppingDispatchType.UPDATE_COLOR_STORE: { case ShoppingDispatchType.UPDATE_COLOR_STORE: {
return { ...state, colorStore: data } draft.colorStore = data
break
} }
case ShoppingDispatchType.UPDATE_SELECTED_AMOUNT: { case ShoppingDispatchType.UPDATE_SELECTED_AMOUNT: {
return { ...state, selectedAmount: data } draft.selectedAmount = data
break
} }
case ShoppingDispatchType.UPDATE_CHANGED_CHECKBOX: { case ShoppingDispatchType.UPDATE_CHANGED_CHECKBOX: {
return { if(!draft.colorStore[data.purchaserId]){
...state, draft.colorStore[data.purchaserId] = {
colorStore: { purchaserId: data.purchaserId,
...state.colorStore, goodsKind: data.goodsKind,
[data.purchaserId as number]: { multipleSelection: data.multipleSelection
purchaserId: data.purchaserId, }
goodsKind: { ...state.colorStore[data.purchaserId]?.goodsKind, ...data.goodsKind }, }else{
multipleSelection: data.multipleSelection ? data.multipleSelection : state.colorStore[data.purchaserId]?.multipleSelection, draft.colorStore[data.purchaserId] = produce(draft.colorStore[data.purchaserId], purchaserDraft => {
}, purchaserDraft.purchaserId = data.purchaserId
}, purchaserDraft.goodsKind = { ...purchaserDraft.goodsKind, ...data.goodsKind }
purchaserDraft.multipleSelection = data.multipleSelection ? data.multipleSelection : purchaserDraft?.multipleSelection
})
} }
break
} }
default: default:
throwError() throwError()
} }
} })
export const ShoppingStateContext = React.createContext<ShoppingStateContextValue | null>(null) export const ShoppingStateContext = React.createContext<ShoppingStateContextValue | null>(null)
@ -161,6 +172,6 @@ export interface InternalShoppingCartAction extends ShoppingCartAction {
__INTERNAL__: React.MutableRefObject<ShoppingCartAction | null> __INTERNAL__: React.MutableRefObject<ShoppingCartAction | null>
} }
function throwError(): never { export function throwError(): never {
throw new Error('没有这个action.type') throw new Error('没有这个action.type')
} }

View File

@ -1,7 +1,9 @@
// 用于优化数据流 结合发布订阅 更新组件内部状态可以组件自己处理 // 用于优化数据流 结合发布订阅 更新组件内部状态可以组件自己处理
import { Events } from "@tarojs/taro"
import { ColorStore } from "." import { ColorStore } from "."
export const events = new Events()
export type SubscribeCallback = (changedGoods: any) => void export type SubscribeCallback = (changedGoods: any) => void

View File

@ -7558,6 +7558,11 @@ image-size@~0.5.0:
resolved "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz" resolved "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz"
integrity sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ== integrity sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==
immer@^9.0.16:
version "9.0.16"
resolved "https://registry.npmmirror.com/immer/-/immer-9.0.16.tgz#8e7caab80118c2b54b37ad43e05758cdefad0198"
integrity sha512-qenGE7CstVm1NrHQbMh8YaSzTZTFNP3zPqr3YU0S0UY441j4bJTg4A2Hh5KAhwgaiU6ZZ1Ar6y/2f4TblnMReQ==
immutable@^4.0.0: immutable@^4.0.0:
version "4.1.0" version "4.1.0"
resolved "https://registry.npmjs.org/immutable/-/immutable-4.1.0.tgz" resolved "https://registry.npmjs.org/immutable/-/immutable-4.1.0.tgz"