自定义hooks

目的:增加代码的可复用性,实现功能复用

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) {
			// 发起请求前需要验证提交的表单
			
		}
	}, [])
}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值