🐞 fix(购物页面): 修复多选流程产生的bug

This commit is contained in:
xuan 2022-10-20 12:48:24 +08:00
parent cd676c4e8b
commit f9ac8c4f34
8 changed files with 138 additions and 85 deletions

View File

@ -1,6 +1,7 @@
import { usePropsValue } from '@/use/useCommon'
import { View } from '@tarojs/components'
import classnames from 'classnames'
import React, { forwardRef, useEffect, useImperativeHandle, useState } from 'react'
import React, { forwardRef, SetStateAction, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react'
import IconFont from '../iconfont/iconfont'
import styles from './index.module.scss'
@ -37,7 +38,25 @@ export default forwardRef((props: params, ref) => {
customTextClass = '',
hiddenCheckboxIcon = false,
} = props
const [selected, SetSelected] = useState(false)
console.log('Rerender component: checkbox', status)
const [selected, setSelected] = usePropsValue({
value: status,
defaultValue: false
})
// const selectedRef = useRef(status)
// if (status !== undefined){
// selectedRef.current = status
// }
// const [, setForceUpdate] = useState({})
// const setSelected = useCallback((v: SetStateAction<boolean>) => {
// const nextValue = typeof v === 'function' ? (v as (prevValue: boolean) => boolean)(selectedRef.current) : v
// console.log('对比',nextValue, selectedRef.current)
// if (nextValue === selectedRef.current) return
// selectedRef.current = nextValue
// console.log('强制刷新', nextValue, selectedRef.current)
// setForceUpdate({})
// }, [])
const onSelectEven = () => {
if (disabled) return false
let res = !selected
@ -46,11 +65,12 @@ export default forwardRef((props: params, ref) => {
} else {
onClose?.()
}
SetSelected(res)
console.log('res', res)
setSelected(res)
}
const handleClickChildren = (event) => {
if (!triggerLabel){
const handleClickChildren = event => {
if (!triggerLabel) {
return event.stopPropagation()
}
}
@ -76,9 +96,9 @@ export default forwardRef((props: params, ref) => {
useImperativeHandle(ref, () => ({
onSelectEven,
}))
useEffect(() => {
SetSelected(status)
}, [status])
// useEffect(() => {
// setSelected(status)
// }, [status])
return (
<View className={classnames(customClassName, styles.checkbox)} style={customStyles} onClick={onSelectEven}>
{!hiddenCheckboxIcon && (

View File

@ -1,33 +1,40 @@
import { formatPriceDiv } from '@/common/format'
import NormalButton from '@/components/normalButton'
import { View, Text } from '@tarojs/components'
import { memo, useState } from 'react'
import { memo, SetStateAction, useMemo, useRef, useState } from 'react'
import styles from './index.module.scss'
import MCheckbox from '@/components/checkbox'
import { usePropsValue } from '@/use/useCommon'
import { ShoppingDispatchType, useShoppingDispatch } from '../../context'
type PropsType = {
onDelete?: Function
onSelectCheckbox?: (bool: boolean) => void
disabled?: boolean
isSelectAll?: boolean
}
export default memo<PropsType>(props => {
const { onDelete, onSelectCheckbox, disabled = false } = props
export default (props: PropsType) => {
const { onDelete, onSelectCheckbox, disabled = false, isSelectAll: selectAll = false } = props
const dispatch = useShoppingDispatch()
console.log('isSelectAll==>', selectAll)
const handleDelete = () => {
onDelete && onDelete()
}
const selectCallBack = () => {
onSelectCheckbox && onSelectCheckbox(true)
setSelectAll(true)
dispatch({ type: ShoppingDispatchType.UPDATE_MULTIPLE_SELECTION, data: true })
}
const closeCallBack = () => {
setSelectAll(false)
dispatch({ type: ShoppingDispatchType.UPDATE_MULTIPLE_SELECTION, data: false })
onSelectCheckbox && onSelectCheckbox(false)
}
const [isSelectAll, setSelectAll] = useState(false)
const [isSelectAll, setSelectAll] = usePropsValue({
value: selectAll,
defaultValue: false,
})
return (
<View className={styles.bottomBar}>
@ -50,4 +57,4 @@ export default memo<PropsType>(props => {
</View>
</View>
)
})
}

View File

@ -17,8 +17,6 @@ type PropsType = {
orderType: EnumSaleMode
}
// 注意:如果在表单控件内部使用 useWatch由于是直接从Context中取值memo也会直接失去作用。
let ColorKindItem: FC<PropsType> = props => {
console.log('Rerender component: ColorKindItem')
const { state: multipleSelection, purchaserId, itemData, orderType = EnumSaleMode.Bulk } = props
@ -111,7 +109,8 @@ let ColorKindItem: FC<PropsType> = props => {
</MCheckbox>
)
}
// State 分割组件 思路就是把 context直接通过props的形式传给组件这样的话就解决了context强制刷新memo的问题了
// State 分割组件 思路就是把 context 直接通过 props 的形式传给组件,这样的话就解决了 context 强制刷新 memo 的问题了
// 那么当 context 内的 value 被更新的时候react 只会强制渲染 Wrapper
const withStateSlice = (comp, slice) => {
const MemoComp = memo(comp)
const Wrapper = (props, ref) => {

View File

@ -1,6 +1,13 @@
import { Dispatch, FC, ReactNode, useEffect, useMemo, useReducer, useRef, useState } from 'react'
import { ShoppingAction, ShoppingCartAction, ShoppingDispatchContext, ShoppingDispatchContextValue, ShoppingDispatchType, shoppingReducer, ShoppingStateContext } from '../../context'
import { GoodsMeta, ColorStore, ShoppingStateContextValue } from '../../context'
import { FC, ReactNode, useEffect, useReducer, useRef } from 'react'
import {
ShoppingAction,
ShoppingDispatchContext,
ShoppingDispatchContextValue,
ShoppingDispatchType,
shoppingReducer,
ShoppingStateContext,
} from '../../context'
import { ColorStore, ShoppingStateContextValue } from '../../context'
export type TriggerCheckboxOptions = {
colorStore: ColorStore
@ -13,6 +20,7 @@ type InitialState = {
currentCheckedPurchaserId: number
currentCheckedSaleMode: number
isManageStatus: boolean
isMultipleSelection: boolean
selectedAmount: number
}
@ -33,17 +41,16 @@ export const ShoppingProvider: FC<ShoppingCartPropsType> = props => {
currentCheckedPurchaserId: -1,
currentCheckedSaleMode: 0,
isManageStatus: false,
isMultipleSelection: false,
selectedAmount: 0,
})
// 这里要在 useEffect 也就是刷新 state 后再调用,否则如果在 onFieldsChangeRef 修改值会覆盖掉上次修改
useEffect(() => {
onTriggerCheckboxRef.current?.({
colorStore: state.colorStore,
currentCheckedPurchaserId: state.currentCheckedPurchaserId,
setSelectedAmount: (amount) => dispatch({ type: ShoppingDispatchType.UPDATE_SELECTED_AMOUNT,data: amount }),
setSelectedAmount: amount => dispatch({ type: ShoppingDispatchType.UPDATE_SELECTED_AMOUNT, data: amount }),
})
}, [state.colorStore, state.currentCheckedPurchaserId])

View File

@ -76,10 +76,9 @@ let ShoppingCartItem: FC<PropsType> = props => {
dispatch({ type: ShoppingDispatchType.UPDATE_CURRENT_CHECKED_SALEMODE, data: type })
// 重置预估金额
dispatch({ type: ShoppingDispatchType.UPDATE_SELECTED_AMOUNT, data: 0 })
dispatch({ type: ShoppingDispatchType.UPDATE_MULTIPLE_SELECTION, data: false })
}
// const { currentCheckedPurchaserId, colorStore } = useShoppingState()
console.log('Rerender component: shoppingCartItem')
const handleClickLayout = () => {
@ -89,6 +88,7 @@ let ShoppingCartItem: FC<PropsType> = props => {
dispatch({ type: ShoppingDispatchType.UPDATE_SELECTED_AMOUNT, data: 0 })
dispatch({ type: ShoppingDispatchType.UPDATE_CURRENT_CHECKED_SALEMODE, data: selected })
dispatch({ type: ShoppingDispatchType.UPDATE_CURRENT_CHECKED_PURCHASERID, data: itemData?.purchaser_id as number })
dispatch({ type: ShoppingDispatchType.UPDATE_MULTIPLE_SELECTION, data: false })
}
// 统计已选面料
@ -259,7 +259,8 @@ const GoodsList = memo<GoodsListPropType>(props => {
</>
)
})
// State 分割组件 思路就是把 context直接通过props的形式传给组件这样的话就解决了context强制刷新memo的问题了
// State 分割组件 思路就是把 context 直接通过 props 的形式传给组件,这样的话就解决了 context 强制刷新 memo 的问题了
// 那么当 context 内的 value 被更新的时候react 只会强制渲染 Wrapper
const withStateSlice = (comp, slice) => {
const MemoComp = memo(comp, (prevProps, nextProps)=>{
let needMemo = true

View File

@ -1,4 +1,5 @@
import React, { Dispatch, useRef } from 'react'
import { EnumSaleMode } from '@/common/Enumerate'
import React, { Dispatch } from 'react'
import { useContext } from 'react'
/**
@ -31,7 +32,7 @@ export type Goods = {
product_color_code: number // 颜色编号
estimate_amount: number // 预估金额
count: number // 已选的条数或米数
sale_mode: number
sale_mode: EnumSaleMode
}
// 分组
export interface GoodsMeta {
@ -45,29 +46,32 @@ export interface GoodsMeta {
}
export interface ShoppingStateContextValue {
isManageStatus: boolean
isManageStatus: boolean // 管理按钮的状态
isMultipleSelection: boolean // 全选按钮的状态
currentCheckedPurchaserId: number // 当前高亮的客户ID
currentCheckedSaleMode: number
currentCheckedSaleMode: EnumSaleMode
colorStore: ColorStore
selectedAmount: number
}
export enum ShoppingDispatchType {
UPDATE_MANAGE_STATUS = 'UPDATE_MANAGE_STATUS',
UPDATE_MULTIPLE_SELECTION = 'UPDATE_MULTIPLE_SELECTION',
UPDATE_CURRENT_CHECKED_PURCHASERID = 'UPDATE_CURRENT_CHECKED_PURCHASERID',
UPDATE_CURRENT_CHECKED_SALEMODE = 'UPDATE_CURRENT_CHECKED_SALEMODE',
UPDATE_COLOR_STORE = 'UPDATE_COLOR_STORE',
UPDATE_SELECTED_AMOUNT = 'UPDATE_SELECTED_AMOUNT',
UPDATE_CHANGED_CHECKBOX = 'UPDATE_CHANGED_CHECKBOX',
}
// UPDATE_MultipleSelection
export interface ShoppingDispatchContextValue {
[ShoppingDispatchType.UPDATE_MANAGE_STATUS]: (isManageStatus: boolean) => void
[ShoppingDispatchType.UPDATE_CURRENT_CHECKED_PURCHASERID]: (purchaserId: number) => void
[ShoppingDispatchType.UPDATE_CURRENT_CHECKED_SALEMODE]: (saleMode: number) => void
[ShoppingDispatchType.UPDATE_MANAGE_STATUS]: (isManageStatus: ShoppingStateContextValue['isManageStatus']) => void
[ShoppingDispatchType.UPDATE_MULTIPLE_SELECTION]: (isMultipleSelection: ShoppingStateContextValue['isMultipleSelection']) => void
[ShoppingDispatchType.UPDATE_CURRENT_CHECKED_PURCHASERID]: (purchaserId: ShoppingStateContextValue['currentCheckedPurchaserId']) => void
[ShoppingDispatchType.UPDATE_CURRENT_CHECKED_SALEMODE]: (saleMode: ShoppingStateContextValue['currentCheckedSaleMode']) => void
[ShoppingDispatchType.UPDATE_COLOR_STORE]: (colorStore: React.SetStateAction<ColorStore>) => void
[ShoppingDispatchType.UPDATE_SELECTED_AMOUNT]: (amount: React.SetStateAction<number>) => void
[ShoppingDispatchType.UPDATE_CHANGED_CHECKBOX]: (amount: React.SetStateAction<number>) => void
[ShoppingDispatchType.UPDATE_SELECTED_AMOUNT]: (amount: React.SetStateAction<ShoppingStateContextValue['selectedAmount']>) => void
[ShoppingDispatchType.UPDATE_CHANGED_CHECKBOX]: (amount: React.SetStateAction<ShoppingStateContextValue['selectedAmount']>) => void
}
@ -82,6 +86,9 @@ export function shoppingReducer(state: ShoppingStateContextValue, action: Shoppi
case ShoppingDispatchType.UPDATE_MANAGE_STATUS: {
return { ...state, isManageStatus: data }
}
case ShoppingDispatchType.UPDATE_MULTIPLE_SELECTION: {
return { ...state, isMultipleSelection: data }
}
case ShoppingDispatchType.UPDATE_CURRENT_CHECKED_PURCHASERID: {
return { ...state, currentCheckedPurchaserId: data }
}

View File

@ -46,12 +46,15 @@ interface InternalContainer {}
const ShoppingCartContainer: FC<InternalContainer> = () => {
let isFirst = useRef(true)
const { isManageStatus, selectedAmount, currentCheckedPurchaserId, currentCheckedSaleMode, colorStore } = useShoppingState()
const { isMultipleSelection, isManageStatus, selectedAmount, currentCheckedPurchaserId, currentCheckedSaleMode, colorStore } = useShoppingState()
const dispatch = useShoppingDispatch()
// 管理
const onStartToManage = () => {
if(isManageStatus){
handleSelectAllCheckbox(false) // 取消全选
}
dispatch({ type: ShoppingDispatchType.UPDATE_MANAGE_STATUS, data: !isManageStatus })
}
@ -230,6 +233,7 @@ const ShoppingCartContainer: FC<InternalContainer> = () => {
{isManageStatus ? (
<BottomEditBar
disabled={currentCheckedPurchaserId < 0}
isSelectAll={isMultipleSelection}
onDelete={handleDelete}
onSelectCheckbox={isAll => handleSelectAllCheckbox(isAll)}></BottomEditBar>
) : (

View File

@ -1,5 +1,5 @@
import dayjs from 'dayjs'
import { useEffect, useRef, useState } from 'react'
import { useEffect, useRef, useState, SetStateAction, useMemo, useCallback } from 'react'
//倒计时hook
export const useTimeCountDown = () => {
@ -33,7 +33,7 @@ export const useTimeCountDown = () => {
setTimeStatus(() => 1)
if (startData >= endDate) {
clearInterval(timeObj.current)
setShowTime((e) => ({ ...e, DD: '00', HH: '00', MM: '00', SS: '00' }))
setShowTime(e => ({ ...e, DD: '00', HH: '00', MM: '00', SS: '00' }))
setTimeStatus(() => 2)
return false
}
@ -51,7 +51,7 @@ export const useTimeCountDown = () => {
var MM = ('00' + mm).slice(-2)
var SS = ('00' + ss).slice(-2)
// console.log('endTime::', `${DD}-${HH}-${MM}-${SS}`)
setShowTime((e) => ({ ...e, DD, HH, MM, SS }))
setShowTime(e => ({ ...e, DD, HH, MM, SS }))
}
return {
showTime,
@ -93,7 +93,7 @@ type Options = {
}
const defaultOptions = {
deep: false
deep: false,
}
/**
* memo
@ -107,13 +107,13 @@ export const useNeedMemoCallback = (options: Options = defaultOptions) => {
// console.log('memo==>', prevProps, nextProps)
let needMemoized = true
// 引用类型
// function array object
// function array object
// 基本类型
// number boolean string number symbol null undefined
// 循环props对象
const handleDiff = () => {
for (let [key, value] of Object.entries(prevProps)){
for (let [key, value] of Object.entries(prevProps)) {
if (!Object.is(value, nextProps[key])) {
// console.log('函数不相等',value === nextProps[key])
// console.log('asdfasdf',value)
@ -121,48 +121,56 @@ export const useNeedMemoCallback = (options: Options = defaultOptions) => {
break
}
}
// Object.entries(prevProps).forEach(([key, value]) => {
// if (!nextProps.hasOwnProperty(key)) {
// needMemoized = false
// // 新增的属性
// if (Object.prototype.toString.call(value) === '[object Object]') {
// parent[key] = {}
// } else if (Object.prototype.toString.call(value) === '[object Array]') {
// parent[key] = []
// }
// }
// if (typeof data[key] === 'object') {
// o[key] = internalSetData(parent[key], data[key])
// } else {
// // 核心处理逻辑
// // key == data第一层 如果要做到data第二层呢所以需要递归
// if (parent[key] != data[key]) {
// o[key] = data[key]
// changed = true
// }
// }
// })
// Object.entries(prevProps).forEach(([key, value]) => {
// if (!nextProps.hasOwnProperty(key)) {
// needMemoized = false
// // 新增的属性
// if (Object.prototype.toString.call(value) === '[object Object]') {
// parent[key] = {}
// } else if (Object.prototype.toString.call(value) === '[object Array]') {
// parent[key] = []
// }
// }
// if (typeof data[key] === 'object') {
// o[key] = internalSetData(parent[key], data[key])
// } else {
// // 核心处理逻辑
// // key == data第一层 如果要做到data第二层呢所以需要递归
// if (parent[key] != data[key]) {
// o[key] = data[key]
// changed = true
// }
// }
// })
}
handleDiff()
return needMemoized
}
}
const needMemo = useNeedMemoCallback()
const hobby = {
play1: 1,
play2: 2
}
const bool = needMemo(
{
name: '小明',
age: 18,
hobby: hobby,
},
{
name: '小明',
age: 18,
hobby: hobby,
},
)
// console.log('bool==>', bool)
type UsePropsValueOptions<T> = {
value?: T
defaultValue: T
onChange?: (v: T) => void
}
export function usePropsValue<T>(options: UsePropsValueOptions<T>) {
const { value, defaultValue, onChange } = options
const [, setForceUpdate] = useState({})
const stateRef = useRef<T>(value !== undefined ? value : defaultValue)
if (value !== undefined) {
stateRef.current = value
}
const setState = useCallback((v: SetStateAction<T>, forceTrigger: boolean = false) => {
// `forceTrigger` means trigger `onChange` even if `v` is the same as `stateRef.current`
const nextValue = typeof v === 'function' ? (v as (prevState: T) => T)(stateRef.current) : v
if (!forceTrigger && nextValue === stateRef.current) return
stateRef.current = nextValue
setForceUpdate({})
return onChange?.(nextValue)
}, [])
return [stateRef.current, setState] as const
}