diff --git a/src/components/checkbox/index.tsx b/src/components/checkbox/index.tsx index d4206e1..9f5a690 100644 --- a/src/components/checkbox/index.tsx +++ b/src/components/checkbox/index.tsx @@ -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) => { + // 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 ( {!hiddenCheckboxIcon && ( diff --git a/src/pages/shopping/components/bottomEditBar/index.tsx b/src/pages/shopping/components/bottomEditBar/index.tsx index 13edf57..7f57da0 100644 --- a/src/pages/shopping/components/bottomEditBar/index.tsx +++ b/src/pages/shopping/components/bottomEditBar/index.tsx @@ -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(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 ( @@ -50,4 +57,4 @@ export default memo(props => { ) -}) +} diff --git a/src/pages/shopping/components/colorKindItem/index.tsx b/src/pages/shopping/components/colorKindItem/index.tsx index efb0af8..8c8d85d 100644 --- a/src/pages/shopping/components/colorKindItem/index.tsx +++ b/src/pages/shopping/components/colorKindItem/index.tsx @@ -17,8 +17,6 @@ type PropsType = { orderType: EnumSaleMode } -// 注意:如果在表单控件内部使用 useWatch,由于是直接从Context中取值,memo也会直接失去作用。 - let ColorKindItem: FC = props => { console.log('Rerender component: ColorKindItem') const { state: multipleSelection, purchaserId, itemData, orderType = EnumSaleMode.Bulk } = props @@ -111,7 +109,8 @@ let ColorKindItem: FC = props => { ) } -// 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) => { diff --git a/src/pages/shopping/components/shoppingCart/index.tsx b/src/pages/shopping/components/shoppingCart/index.tsx index f5b67e3..be53cdf 100644 --- a/src/pages/shopping/components/shoppingCart/index.tsx +++ b/src/pages/shopping/components/shoppingCart/index.tsx @@ -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 = 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]) diff --git a/src/pages/shopping/components/shoppingCartItem/index.tsx b/src/pages/shopping/components/shoppingCartItem/index.tsx index 08b6bda..247f2dd 100644 --- a/src/pages/shopping/components/shoppingCartItem/index.tsx +++ b/src/pages/shopping/components/shoppingCartItem/index.tsx @@ -76,10 +76,9 @@ let ShoppingCartItem: FC = 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 = 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(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 diff --git a/src/pages/shopping/context/index.ts b/src/pages/shopping/context/index.ts index e030c17..d04e673 100644 --- a/src/pages/shopping/context/index.ts +++ b/src/pages/shopping/context/index.ts @@ -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) => void - [ShoppingDispatchType.UPDATE_SELECTED_AMOUNT]: (amount: React.SetStateAction) => void - [ShoppingDispatchType.UPDATE_CHANGED_CHECKBOX]: (amount: React.SetStateAction) => void + [ShoppingDispatchType.UPDATE_SELECTED_AMOUNT]: (amount: React.SetStateAction) => void + [ShoppingDispatchType.UPDATE_CHANGED_CHECKBOX]: (amount: React.SetStateAction) => 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 } } diff --git a/src/pages/shopping/index.tsx b/src/pages/shopping/index.tsx index d74bbb8..ced5159 100644 --- a/src/pages/shopping/index.tsx +++ b/src/pages/shopping/index.tsx @@ -46,12 +46,15 @@ interface InternalContainer {} const ShoppingCartContainer: FC = () => { 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 = () => { {isManageStatus ? ( handleSelectAllCheckbox(isAll)}> ) : ( diff --git a/src/use/useCommon.ts b/src/use/useCommon.ts index 273ee13..8995b5c 100644 --- a/src/use/useCommon.ts +++ b/src/use/useCommon.ts @@ -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 = { + value?: T + defaultValue: T + onChange?: (v: T) => void +} + +export function usePropsValue(options: UsePropsValueOptions) { + const { value, defaultValue, onChange } = options + + const [, setForceUpdate] = useState({}) + + const stateRef = useRef(value !== undefined ? value : defaultValue) + if (value !== undefined) { + stateRef.current = value + } + + const setState = useCallback((v: SetStateAction, 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 +}