在这段 React 代码中,封装 useSyncState 的目的是为了简化状态管理和同步状态更新的过程。
useSyncState 是一个自定义 Hook,它基于 React 的 useState Hook 实现。它的主要作用是封装了对状态的读取和更新,并且在更新状态时会自动同步更新一个可变的引用。这样做的目的是为了解决 React 中状态更新的异步问题,以及确保状态在组件渲染过程中的一致性。
在 React 中,使用 useState 来管理组件的状态,当调用 setState 来更新状态时,React 会对状态进行合并和批量更新,这样可能会导致在同一个渲染周期内读取到的状态并不是最新的。而 useSyncState 则通过引入一个可变的 current 引用来确保状态的同步更新,从而避免了这个问题。
举例说明,在这段代码中,有两个地方使用了 useSyncState:
const [syncTreeList, treeList, setTreeList] = useSyncState<insideCategoryInfoType[]>([...]:这里使用useSyncState来创建了syncTreeList和setTreeList,它们是用于管理treeList状态的替代品。当调用setTreeList来更新状态时,syncTreeList会自动更新为最新的状态。const [syncChecked, checked, setChecked] = useSyncState<categoryInfoType[]>([]):这里同样使用useSyncState来创建了syncChecked和setChecked,用于管理checked状态。当调用setChecked来更新状态时,syncChecked也会自动更新为最新的状态。
通过封装 useSyncState,代码简化了对状态的读取和更新,同时保证了状态的同步更新,确保了状态的一致性。这样使得组件的状态管理更加可靠和高效。
import { useMemo, useState } from 'react'
import { TreeManage } from '@jz/base-providers'
import { produce } from 'immer'
import { clone, forEach, map, pullAt, remove, set, size, some, startsWith } from 'lodash-es'
import { useSyncState } from '@jz/base-react-hooks'
import { request, API } from '@/providers'
import { genTreeFromList } from '../providers'
import { categoryInfoType, insideCategoryInfoType, propsType } from '../category-selector.types'
export default function useCategory() {
const [syncTreeList, treeList, setTreeList] = useSyncState<insideCategoryInfoType[]>([
{
categoryId: 'all',
name: '全部类目'
}
])
const [loading, setLoading] = useState(true)
const [isSearch, setIsSearch] = useState(false)
const [searchList, setSearchList] = useState<insideCategoryInfoType[]>([])
const [syncChecked, checked, setChecked] = useSyncState<categoryInfoType[]>([])
const checkedKeys = useMemo(() => map(checked, o => o.categoryId), [checked])
function $setChecked(checkedKeys: insideCategoryInfoType['categoryId'][]) {
const treeManage = new TreeManage(syncTreeList.current, {
fieldNames: { key: 'categoryId' }
})
// 根据路径进行排序,保证父级在最上面
// ------------------------------------------------------------------------
const checkedInfo = map(checkedKeys, k => {
const path = treeManage.findIndexPath(k as never)!
const node = treeManage.findNode(k as never)!
return { path, key: k, node }
})?.sort((a, b) => size(a.path) - size(b.path))
// 对排序后的数组进行去重,规则: 勾选了父级则剔除子集,否则不剔除
// ------------------------------------------------------------------------
checkedInfo?.[0]?.key === 'all' && pullAt(checkedInfo, 0)
const newCheckedInfo: typeof checkedInfo = []
forEach(checkedInfo, info => {
const hasParent = some(newCheckedInfo, newInfo => {
return startsWith(info.path.join('-'), `${newInfo.path.join('-')}-`)
})
if (!hasParent) {
newCheckedInfo.push(info)
}
})
setChecked(map(newCheckedInfo, o => o.node) as categoryInfoType[])
}
function $remove(categoryId: insideCategoryInfoType['categoryId']) {
const newChecked = clone(checked)
remove(newChecked, o => o.categoryId === categoryId)
setChecked(newChecked)
}
async function fetchList(params?: { parent?: number; name?: string }) {
setIsSearch(!!params?.name)
params?.parent || setLoading(true)
try {
const { data } = await request.post(`${API.ABA}/basCategory/list`, { ...params })
if (params?.name) {
setSearchList(data)
} else {
const treeManage = new TreeManage(treeList, {
fieldNames: {
key: 'categoryId'
}
})
const indexPath = treeManage.findIndexPath((params?.parent as never) || 'all')!
setTreeList(
produce(draft => {
set(draft, `${TreeManage.indexPathToLodashPath(indexPath)}.children`, data)
})
)
}
} finally {
setLoading(false)
}
}
async function fetchListByIds(ids: propsType['minimumCategoryIds']) {
setLoading(true)
try {
const { data } = await request.post(`${API.ABA}/basCategory/listParent`, ids)
setTreeList(
produce(draft => {
draft[0].children = genTreeFromList(data)
})
)
} finally {
setLoading(false)
}
}
function reset() {
setTreeList([{ categoryId: 'all', name: '全部类目' }])
setLoading(true)
setIsSearch(false)
setSearchList([])
setChecked([])
}
return {
fetchList,
fetchListByIds,
loading,
treeList,
isSearch,
searchList,
syncChecked,
checked,
checkedKeys,
setChecked: $setChecked,
remove: $remove,
reset
}
}