🎈 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",
"big.js": "^6.2.1",
"dayjs": "^1.11.3",
"immer": "^9.0.16",
"qs": "^6.10.3",
"react": "^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 {customClassName, customStyle,customInputStyle, customInputClassName,...inputProps} = props
console.log('props', props);
return (
<View className={customClassName} style={customStyle}>
<CustomWrapper>

View File

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

View File

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

View File

@ -1,5 +1,5 @@
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 classnames from 'classnames'
import { formatMeterDiv } from '@/common/format'
@ -15,8 +15,10 @@ import { alert, isEmptyObject } from '@/common/common'
import classNames from 'classnames'
import LoadingCard from '@/components/loadingCard'
import { ShoppingCartListApi } from '@/api'
import { ShoppingStore } from '../../context/shoppingStore'
import { events, ShoppingStore } from '../../context/shoppingStore'
import { usePropsValue } from '@/use/useCommon'
import produce, { setAutoFreeze } from 'immer'
import { nextTick } from '@tarojs/taro'
interface ButtonPropsType {
isActive: boolean
@ -24,6 +26,7 @@ interface ButtonPropsType {
children?: React.ReactNode
customStyle?: React.CSSProperties
}
type OperationType = 'add' | 'delete'
// 订单类型
const SaleModeButton: FC<ButtonPropsType> = props => {
const { onClick, children, isActive = false, customStyle } = props
@ -62,7 +65,7 @@ type PropsType = {
const ShoppingCartItem: FC<PropsType> = props => {
const { state } = props
console.log('props ShoppingCartItem', props)
console.log('rerender component ShoppingCartItem', props)
const [itemData, setItemData] = usePropsValue({
value: props.itemData,
@ -143,9 +146,45 @@ const ShoppingCartItem: FC<PropsType> = props => {
const [isPending, startTransition] = useTransition()
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(() => {
console.log('update multipleSelection',multipleSelection);
const unsubscribe = state?.Observer?.subscribe(async id => {
@ -290,25 +329,60 @@ interface GoodsListPropType {
}
const GoodsList = memo<GoodsListPropType>(props => {
const { itemData, selected, isPending, startTransition, multipleSelection } = props
const prevMultipleSelection = useRef(multipleSelection)
const currentSelected = useRef<EnumSaleMode | null>(null)
const dispatch = useShoppingDispatch()
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 组件
const updateComponent = () => {
setComponent(
itemData?.[BackEndSaleModeListFieldMap[selected]].length !== 0 ? (
() => itemData?.[BackEndSaleModeListFieldMap[selected]].length !== 0 ? (
itemData?.[BackEndSaleModeListFieldMap[selected]].map(item => {
console.log('item===>', item)
return <ColorKindItem purchaserId={itemData.purchaser_id} key={item.id} itemData={item}></ColorKindItem>
console.log('item===>', item, multipleSelection?.hasOwnProperty(item.id))
return <ColorKindItem checked={multipleSelection?.hasOwnProperty(item.id)} purchaserId={itemData.purchaser_id} key={item.id} itemData={item}></ColorKindItem>
})
) : (
<View className={styles.noList}></View>
),
)
)
prevMultipleSelection.current = multipleSelection
}
useEffect(() => {
@ -337,8 +411,8 @@ const GoodsList = memo<GoodsListPropType>(props => {
})
updateComponent()
} else {
// 重新把当前的选中状态赋值给ref 作为下一次比较的旧状态
currentSelected.current = selected
// 重新把当前的选中状态赋值给ref 作为下一次比较的旧状态
dispatch({
type: ShoppingDispatchType.UPDATE_CHANGED_CHECKBOX,
data: {
@ -368,28 +442,32 @@ const GoodsList = memo<GoodsListPropType>(props => {
const withStateSlice = (comp, slice) => {
const MemoComp = memo(comp, (prevProps, nextProps) => {
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)) {
needMemo = false
}
if(prevProps.itemData.purchaser_name === 'Hhjh'){
if(prevProps.itemData.purchaser_name === 'JENNIE'){
console.log('------withStateSlice props-------');
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 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-------');
}
if (JSON.stringify(prevProps.state) !== JSON.stringify(nextProps.state)) {
console.log('MultipleSelection 有变化');
if (prevProps.state.Observer !== nextProps.state.Observer) {
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
}
// if (JSON.stringify(prevCheckedPurchaserId) !== JSON.stringify(nextCheckedPurchaserId)) {
// console.log('checkedPurchaserId 有变化');
// needMemo = false
// }
return needMemo
})
const Wrapper = (props, ref) => {

View File

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

View File

@ -1,7 +1,9 @@
// 用于优化数据流 结合发布订阅 更新组件内部状态可以组件自己处理
import { Events } from "@tarojs/taro"
import { ColorStore } from "."
export const events = new Events()
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"
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:
version "4.1.0"
resolved "https://registry.npmjs.org/immutable/-/immutable-4.1.0.tgz"