目的:增加代码的可复用性,实现功能复用
1 计数器
先来个简单的
interface Options {
min?: number;
max?: number;
}
// 返回参数: 当前值 {加法,减法,设置方法,重置方法}
const useCounter = (initialValue: number = 0, options: Options ={}) => {
const [count, setCount] = useState(initialValue)
const { min, max } = options
const setValue = (value) => {
if(typeof value === 'number') {
if (typeof min === 'number' && value <= min) {
setCount(min)
} else if (typeof max === 'number' && value >= max) {
setCount(max)
} else {
setCount(value)
}
} else {
console.error('typeof value should be number!')
}
}
const inc = useCallback((num: number = 1) => {
setValue(count+num)
},[count])
const dec = useCallback((num: number = 1) => {
setValue(count-num)
}, [count])
const set = useCallback((num: number)=>{
setValue(num)
},[count])
const reset = () => {
setValue(initialValue)
}
return [count, {inc, dec, set, reset}]
}
2 useUpdate
用于强制重新渲染
function useUpdate () {
const [,setState] = useState({})
return useCallback(() => setState({}), [])
}
4 useLastest
用于获取最新的值
function useLastest(value) {
const ref = useRef(value)
ref.current = value
return ref
}
5 useCreation
useMemo或useRef的替代品
import type { DependenctList } from 'react'
function depsAreSame(oldDeps, deps) {
if (oldDeps === deps) return true
for (let i=0; i< oldDeps.length; i++) {
if(!Object.is(oldDeps[i], deps[i])) return false
}
return true
}
function useCreation<T>(factory: () => T, deps: DependencyList) {
const { current } = useRef({
deps,
obj: undefined,
initialized: false
})
if (current.initialized === false || !depsAreSame(current.deps, deps)) {
//依赖发生变化
current.deps = deps
current.obj = factory()
current.initialized = true
}
return current.object as T
}
5 useUpdateEffect
等同于useEffect,但是忽略首次执行,只在依赖变化时执行
const createUpdateEffect = (hook) => {
return (effect, deps) => {
const isMounted - useRef(false)
hook(() => {
return () => {
isMounted.current = false
}
}, [])
hook(() => {
if (!isMounted.current) {
isMounted.current = true
} else {
return effect()
}
}, deps)
}
}
const useUpdateEffect = createUpdateEffect(useEffect)
6 subscribe
订阅页面的可见状态变化事件
type Listener = () => void
//订阅者
const listeners: Listener[] = []
function subscribe(listener: Listener) {
listeners.push(listener)
//取消该事件的订阅
return function unsubscribe() {
const index = listeners.indexOf(listener)
listeners.splice(index,1)
}
}
if (isBrowser) {
const revalidate = () => {
//如果页面不可见,不做任何处理
if(!isDocumentVisible) return
//页面可见时通知每个订阅者
for (let i =0; i<listeners.length; i++) {
const listener = listeners[i]
listener()
}
}
window.addEventListener('visibilitychange', revalidate, false)
}
7 useRequest
(1)封装请求
export default class Fetch<TData, TParam extends any[]> {
pluginImpls: any[];
count = 0; // 用于当前请求是否取消判断
state: {
loading: false,
data: undefined,
params: undefined,
error: undefined
};
// 使用public相当于在state中进行了声明
constructor(
public serviceRef,
public options,
public subsctibe,
public initState,
) {
this.state = {
...this.state,
// manual为true表示不会默认执行,此时loading为true
loading: !options.manual,
...initState
}
};
// 事件处理器
runPluginHandler(event, ...rest){
// 相当于filter(item => item)
const r = this.pluginImplis.map((i) => i[event]?.(...rest)).filter(Boolean)
return Object.assign({}, ...r)
}
// 异步调用
setState(obj: Record<string, any>) {
this.state = {
...this.state,
...s
}
// 使用useUpdate强制重新渲染
this.subscribe()
}
async runAsync (...params: TParam): Promise<TData> {
this.count += 1
const currentCount = this.count
const {
returnNow = false,
stopNow = false,
...state
} = this.runPluginHandler('onBefore', params)
// 取消返回空对象
if (stopNow) {
return new Promise(()=>{})
}
// 请求成功返回数据
if (returnNow) {
return Promise.resolve(state.data)
}
// 运行用户传入的onBefore方法
this.options.onBefore?.(params);
try {
// 是否返回了新的promise
let { servicePromise } = this.runPluginHandler('onRequest', this.serviceRef.current, params)
if(!servicePromise) {
//没有返回新的服务,直接调用传入的接口
servicePromise = this.serviceRef.current(...params)
}
const res = await servicePromise
//请求是否取消或被覆盖
if(currentCount !== this.count) {
return new Promise(()=>{})
}
this.setState({
data: res,
error: undefined,
loading: false
})
//调用传入请求成功的函数
this.options.onSuccess?.(res, params)
this.runPluginHandler('onSuccess', res, params)
// 调用传入请求完成后的函数
this.options.onFinally?.(params, res, undefined)
this.runPluginHandler('onFinally', params, res, undefined)
return res
} catch(error) {
// 如果请求已过期,错误无所谓
if (currentCount !== this.count) {
return new Promise(()=>{})
}
this.setState({
error,
loading: false
})
this.options.onError?.(error, params);
this.runPluginHandler('onError', error, params);
this.options.onFinally?.(params, undefined, error);
this.runPluginHandler('onFinally', params, undefined, error);
throw error
}
};
// run和runAsync的区别在于 run是同步函数,会自动捕捉异常 runAsync是一个返回promise的异步函数
run(...params: TParam) {
this.runAsync(...params).catch((error) => {
if (!this.options.onError){
console.error(error)
}
})
}
// 取消请求
cancel() {
this.count += 1
this.setState({ loading: false })
this.runPluginHandle('onCancel')
}
// 再请求一次
refresh() {
this.run(...(this.state.params|| []))
}
refreshAsync(){
this.runAsync(...(this.state.params|| []))
}
//立即变更数据
mutate(data: TData | (oldData?: TData) => TData | undefined) {
const source = typeof data === 'function' ? data(this.state.data) : data
this.runPluginHandler('onMutate', targetData)
this.setState({
data: source
})
}
}
(2)请求执行
type Service<TData, TParams extends any[]> = (...args: TParams) => Promise<TData>
function useRequestImplement<TData, TParams extends any[]>(
service: Service<TData, TParam>,
options: Record<string, any>,
plugins: any[],
) {
// 默认自动请求
const { manual = false, ...rest } = options
const fetchOptions = {
manual
...rest,
}
const update = useUpdate()
// 保障获取的是最新的请求值
const serviceRef = useLatest(service)
//请求示例
const fetchInstance = useCreation(() => {
const initState = plugins.map((p) => p?.onInit?.(fetchOptions)).filter(Boolean)
return new Fetch(
serviceRef,
fetchOptions,
update,
Object.assign({}, ...initState)
)
}, [])
fetchInstance.options = fetchOptions
//运行所有的插件hooks
fetchInstance.pluginImpls = plugins.map((p) => p(fetchInstance, fetchOptions));
useEffect(() => {
// 自动请求
if(!manual) {
const params = fetchInstance.state.params || options.defaultParams || []
fetchInstance.run(...params)
}
return () => {
fetchInstance.cancel()
}
}, [])
return {
loading: fetchInstance.state.loading,
data: fetchInstance.state.data,
error: fetchInstance.state.error,
params: fetchInstance.state.params || [],
cancel: fetchInstance.cencel.bind(fetchInstance),
refresh: fetchInstance.refresh.bind(fetchInstance),
run: fetchInstance.run.bind(fetchInstance),
runAsync: fetchInstance.runAsync.bind(fetchInstance),
mustate: fetchInstance.mustate.bind(fetchInstance),
}
}
(3)延迟loading状态
延迟loading变为true的时间,防止闪烁
function useLoadingDelayPlugin(fetchInstance, { loadingDelay, ready }) {
const timeRef = useRef()
if(!loadingDelay) return {}
const cancelTimeout = () => {
if (timeRef.current) {
clearTimeout(timeRef.current)
}
}
return {
onBefore: () => {
// 再次调用时,清除之前的定时器
cancelTimeout()
// 表明可以发出请求
if(ready !== false) {
timeRef.current = setTimeout(() => {
fetchInstance.setState({
loading: true
})
}, loadingDelay)
}
return {
loading: false
}
},
onFinally: () => {
cancelTimeout()
},
onCancel: () => {
cancelTimeout()
}
}
}
(4)轮询
type Timeout = ReturnType<typeof setTimeout>
const usePollingPlugin = (fetchInstance, {
pollingInterval, // 轮询间隔
pollingWhenHidden = true, // 页面隐藏时,是否继续轮询
pollingErrorRetryCount = -1, //轮询错误重试次数,-1为无限次
}) => {
const timeRef = useRef<Timeout>()
const countRef = useRef(0) // 错误重试次数
const unsubscribeRef = useRef<() => void>()
//停止轮询
const stopPolling = () => {
if(timeREf.current) {
clearTimeout(timeRef.current)
}
// 移除页面显示时 发出请求事件
unscribeRef.current?.()
}
//当取消轮询间隔时,停止轮询
useUpdateEffect(() => {
if (!pollingInterval) {
stopPolling()
}
}, [pollingInterval])
if (!pollingInterval) return {}
return {
onBefore: () => {
//在下次请求开始前停止上次请求
stopPolling()
},
onError: () => {
countRef.current += 1
},
onSuccess: () => {
//请求成功,重置请求错误次数
countRef.current = 0
},
onFinally: () => {
//当重试次数为无限次 或 当前重试次数小于预设重试次数
if (pollingErrorRetryCount === -1 ||
(pollingErrorRetryCount !== -1 && countRef.current <= pollingErrorRetryCount)) {
timeRef.current = setTimeout(() => {
// 如果页面隐藏时停止轮询
if(!pollingWhenHidden && 页面隐藏) {
unsubscribeRef.current = subscribe(() => {
// 页面显示时,调用请求函数
fetchInstance.refresh()
})
} else {
fetchInstance.refresh()
}
}, pollingInterval)
} else {
// 请求结束 且 重试次数大于预设重试次数,不再发起请求
countRef.current = 0
}
},
onCancel: () => {
stopPolling()
},
}
}
8 useSelections
单选多选逻辑封装
//传入一个checkbox列表
const useSelections = (items: any[], defaultSelected: any[]) => {
const [selected, setSelected] = useState(defaultSelected)
// 已选列表,去重
const selectSet = useMemo(() => new Set(selected), [selected])
// 是否一个都没有选择
const noneSelected = useMemo(() => {
return items.every((item) => !selectSet.has(item))
}, [])
//是否全选
const allSelected = useMemo(() => {
return items.every((item) => selectSet.has(item))
}, [items, selectSet])
//是否有选中
const partiallySelected = useMemo(() => !noneSelected && !allSelected, [noneSelected, allSelected])
//是否被选中
const isSelect = (item) => selectSet.has(item)
//选择单个元素
const select = (item) => {
selectedSet.add(item)
setSelected(Array.from(selectedSet))
}
//取消选择单个元素
const unselect = (item) =>{
selectedSet.delete(item)
setSelected(Array.from(selectedSet))
}
// 反选单个元素
const toggle = (item) => {
if(isSelected(item)) {
unselect(item)
} else {
select(item)
}
}
// 选择全部元素
const selectAll = () => {
items.forEach((item) => {
selectedSet.add(item)
})
setSelected(Array.from(selectedSet))
}
}
9 usePagination
const usePagination = (service, options) => {
const { defaultPageSize = 10, defayltCurrent = 1, ...rest } = options
const result = useRequest(service, {
defaultParams: [{ current: defaultCurrent, pageSize: defaultPageSize }],
refreshDepsAction: () => {
//依赖数组变化,跳转回第一页
changeCurrent(1)
},
...rest
})
const { current = 1, pageSize = defaultPageSize } = result.params[0] || {}
const total = result.data?.total || 0
const totalPage = useMemo(() => {
return Math.ceil(total/pageSize)
}, [total, pageSize])
// current 或 pagesize改变时调用
const onChange = (cur, pag) => {
let toCurrent = cur <= 0 ? 1:cur
const toPageSize = pag <=0 ? 1:pag
const tempTotalPage = Math.ceil(total / toPageSize)
// 如果跳转的页数大于总页数
if (toCurrent > tempTotalPage) {
toCurrent = Math.max(1, tempTotalPage)
}
const [oldPaginationParams = {}, ...params] = result.params || []
result.run({
...oldPaginationParams,
current: toCurrent,
pageSize: toPageSize,
},
...params
)
}
const changeCurrent = (cur) => {
onChange(cur, pageSize)
}
const changePageSize = (p) => {
onChange(current, p)
}
return {
...result,
pagination: {
current,
pageSize,
total,
totalPage,
onChange,
changeCurrent,
changePageSize
}
}
}
10 useAntdTable
const useAntdTable = (service, options) => {
const {
form,
defaultType = 'simple', //默认表单类型simple | advance
defaultParams, //默认参数 第一项为分页数据,第二项为表单数据
manual = false,
refreshDeps = [],
ready = true,
...rest
} = options
//手动控制请求
const result = usePagination(service, {
manual: true,
...rest
})
const { params = [], run } = result
//表单的所有数据
const allFormDataRef = useRef<Record<string, any>>({})
//验证提交的表单
const validateFields = (): Promise<Record<string, any>> => {
//没有传入form,不需要验证表单
if (!form) {
return Promise.resolve({})
}
//获取所有的表单项
const activeFieldsValue =
}
// 初始化时调用请求
useEffect(() => {
// 允许请求且为自动请求
if(!manual && ready) {
// 发起请求前需要验证提交的表单
}
}, [])
}