1、创建一个useRequs文件并暴露方法
// useRequest是一个implement
import useRequestImplement from './useRequestImplement';
import type { Service, Options, Plugin } from './types';
function useRequest<TData, TParams extends any[]>(
service: Service<TData, TParams>,
options?: Options<TData, TParams>,
plugins?: Plugin<TData, TParams>[],
){
return useRequestImplement<TData, TParams>(service,
options, [...(plugins || [])] as Plugin<TData, TParams>[] );
}
export default useRequest;
2、定义TS的type属性
import type Fetch from './Fetch';
export type Service<TData, TParams extends any[]> = (...args: TParams) => Promise<TData>;
export type Subscribe = () => void;
export interface CachedData<TData = any, TParams = any> {
data: TData;
params: TParams;
time: number;
}
export interface Options<TData, TParams extends any[]> {
manual?: boolean;
onBefore?: (params: TParams) => void;
onSuccess?: (data: TData, params: TParams) => void;
onError?: (e: Error, params: TParams) => void;
// formatResult?: (res: any) => TData;
onFinally?: (params: TParams, data?: TData, e?: Error) => void;
defaultParams?: TParams;
// refreshDeps
refreshDeps?: DependencyList;
refreshDepsAction?: () => void;
// loading delay
loadingDelay?: number;
// polling
pollingInterval?: number;
pollingWhenHidden?: boolean;
pollingErrorRetryCount?: number;
// refresh on window focus
refreshOnWindowFocus?: boolean;
focusTimespan?: number;
// debounce
debounceWait?: number;
debounceLeading?: boolean;
debounceTrailing?: boolean;
debounceMaxWait?: number;
// throttle
throttleWait?: number;
throttleLeading?: boolean;
throttleTrailing?: boolean;
// cache
cacheKey?: string;
cacheTime?: number;
staleTime?: number;
setCache?: (data: CachedData<TData, TParams>) => void;
getCache?: (params: TParams) => CachedData<TData, TParams> | undefined;
// retry
retryCount?: number;
retryInterval?: number;
// ready
ready?: boolean;
// [key: string]: any;
}
export interface FetchState<TData, TParams extends any[]> {
loading: boolean;
params?: TParams;
data?: TData;
error?: Error;
}
export type Plugin<TData, TParams extends any[]> = {
(fetchInstance: Fetch<TData, TParams>, options: Options<TData, TParams>): PluginReturn<
TData,
TParams
>;
onInit?: (options: Options<TData, TParams>) => Partial<FetchState<TData, TParams>>;
};
export interface PluginReturn<TData, TParams extends any[]> {
onBefore?: (params: TParams) =>
| ({
stopNow?: boolean;
returnNow?: boolean;
} & Partial<FetchState<TData, TParams>>)
| void;
onRequest?: (
service: Service<TData, TParams>,
params: TParams,
) => {
servicePromise?: Promise<TData>;
};
onSuccess?: (data: TData, params: TParams) => void;
onError?: (e: Error, params: TParams) => void;
onFinally?: (params: TParams, data?: TData, e?: Error) => void;
onCancel?: () => void;
onMutate?: (data: TData) => void;
}
3、实现一个Fetch方法
import { Service, Options, Subscribe, FetchState, PluginReturn } from './types';
import { isFunction } from './endcodeHooks'
// fetch 构造函数
export default class Fetch<TData, TParams extends any[]> {
pluginImpl: PluginReturn<TData, TParams>[] | undefined;
count: number = 0;
state: FetchState<TData, TParams> = {
loading: false,
params: undefined,
data: undefined,
error: undefined,
};
constructor(
public serviceRef: MutableRefObject<Service<TData, TParams>>,
public options: Options<TData, TParams>,
public subscribe: Subscribe,
public initState: Partial<FetchState<TData, TParams>> = {},
) {
this.state = {
...this.state,
loading: !options.manual,
...initState,
};
}
setState(s: Partial<FetchState<TData, TParams>> = {}) {
this.state = {
...this.state,
...s,
};
this.subscribe();
}
runPluginHandler(event: keyof PluginReturn<TData, TParams>, ...rest: any[]) {
// @ts-ignore
const r = this.pluginImpl.map((i) => i[event]?.(...rest)).filter(Boolean);
return Object.assign({}, ...r);
}
async runAsync(...params: TParams): Promise<TData> {
this.count += 1;
const currentCount = this.count;
const {
stopNow = false,
returnNow = false,
...state
} = this.runPluginHandler('onBefore', params);
// stop request
if (stopNow) {
return new Promise(() => { });
}
this.setState({
loading: true,
params,
...state,
});
// return now
if (returnNow) {
return Promise.resolve(state.data);
}
this.options.onBefore?.(params);
try {
// replace service
let { servicePromise } = this.runPluginHandler('onRequest', this.serviceRef.current, params);
if (!servicePromise) {
servicePromise = this.serviceRef.current(...params);
}
const res = await servicePromise;
if (currentCount !== this.count) {
// prevent run.then when request is canceled
return new Promise(() => { });
}
// const formattedResult = this.options.formatResultRef.current ? this.options.formatResultRef.current(res) : res;
this.setState({
data: res,
error: undefined,
loading: false,
});
this.options.onSuccess?.(res, params);
this.runPluginHandler('onSuccess', res, params);
this.options.onFinally?.(params, res, undefined);
if (currentCount === this.count) {
this.runPluginHandler('onFinally', params, res, undefined);
}
return res;
} catch (error) {
if (currentCount !== this.count) {
// prevent run.then when request is canceled
return new Promise(() => { });
}
this.setState({
error,
loading: false,
});
this.options.onError?.(error, params);
this.runPluginHandler('onError', error, params);
this.options.onFinally?.(params, undefined, error);
if (currentCount === this.count) {
this.runPluginHandler('onFinally', params, undefined, error);
}
throw error;
}
}
run(...params: TParams) {
this.runAsync(...params).catch((error) => {
if (!this.options.onError) {
console.error(error);
}
});
}
cancel() {
this.count += 1;
this.setState({
loading: false,
});
this.runPluginHandler('onCancel');
}
refresh() {
// @ts-ignore
this.run(...(this.state.params || []));
}
refreshAsync() {
// @ts-ignore
return this.runAsync(...(this.state.params || []));
}
mutate(data?: TData | ((oldData?: TData) => TData | undefined)) {
const targetData = isFunction(data) ? data(this.state.data) : data;
this.runPluginHandler('onMutate', targetData);
this.setState({
data: targetData,
});
}
}
4、实现一个自定义的endcodeHooks
import { useEffect, useRef, useState, useCallback } from 'react';
type noop = (this: any, ...args: any[]) => any;
type PickFunction<T extends noop> = (
this: ThisParameterType<T>,
...args: Parameters<T>
) => ReturnType<T>
export const isFunction = (value: unknown): value is (...args: any) => any => typeof value === 'function';
const isDev = process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test';
export function useLatest<T>(value: T) {
const ref = useRef(value);
ref.current = value;
return ref;
}
export function useMount(fn: () => void) {
if (isDev) {
if (!isFunction(fn)) {
console.error(
`useMount: parameter \`fn\` expected to be a function, but got "${typeof fn}".`,
);
}
}
useEffect(() => {
fn?.();
}, []);
};
export function useMemoizedFn<T extends noop>(fn: T) {
// 根据fn创建 ref
const fnRef = useRef<T>(fn);
fnRef.current = fn;
// 创建一个useMemoizedFn
const memoizedRef = useRef<PickFunction<T>>();
if (!memoizedRef.current) {
memoizedRef.current = function (this, ...args) {
// apply
return fnRef.current.apply(this, args);
}
}
return memoizedRef.current;
}
export function useUpdate() {
const [, setState] = useState({});
return useCallback(() => setState({}), []);
};
export function useUnmount(fn: () => void) {
if (isDev) {
if (!isFunction(fn)) {
console.error(`useUnmount expected parameter is a function, got ${typeof fn}`);
}
}
const fnRef = useLatest(fn);
useEffect(
() => () => {
fnRef.current();
},
[],
);
};
5、实现一个useRequset的实例方法useRequestImplement
import Fetch from './Fetch'
import { useLatest, useMount, useMemoizedFn, useUpdate, useUnmount } from './endcodeHooks';
import type { Service, Options, Plugin } from './types';
function useRequestImplement<TData, TParams extends any[]>(
service: Service<TData, TParams>,
options?: Options<TData, TParams>,
plugins?: Plugin<TData, TParams>[],
){
// 针对fetch hooks options 管控请求
const { manual = false, ...rest } = options;
// 创建一个options作为透传
const fetchOptions = {
manual,
...rest
};
// 实现一个显示声明
const serviceRef = useLatest(service);
const update = useUpdate();
const initState = plugins?.map((plugin) => plugin?.onInit?.(fetchOptions)).fillter(Boolean);
const fetchInstance = new Fetch<TData, TParams>(
serviceRef,
update,
initState,
Object.assign({}, ...initState),
)
// 针对请求,如果非manual
useMount(()=>{
if(!manual){
fetchInstance.run(
...((fetchInstance.state.params || options?.defaultParams || []) as TParams)
)
}
});
useUnmount(()=>{
fetchInstance.cancel();
})
return {
loading: fetchInstance.state.loading,
data: fetchInstance.state.data,
error: fetchInstance.state.error,
params: fetchInstance.state.params,
run: useMemoizedFn(fetchInstance.run.bind(fetchInstance)),
runAsync: useMemoizedFn(fetchInstance.refreshAsync.bind(fetchInstance)),
refresh: useMemoizedFn(fetchInstance.refresh.bind(fetchInstance)),
cancel: useMemoizedFn(fetchInstance.cancel.bind(fetchInstance)),
}
};
export default useRequestImplement
就是完成一个自身useRequset请求实现 7、使用demo
import { useResetState } from 'encode-hooks';
interface State {
hello: string;
count: number;
}
export App =() => {
const [state, setState, resetState] = useResetState<State>({
hello: '',
count: 0,
});
return (
<div>
<pre>{JSON.stringify(state, null, 2)}</pre>
<p>
<button
type="button"
style={{ marginRight: '8px' }}
onClick={() => setState({ hello: 'world', count: 1 })}
>
set hello and count
</button>
<button type="button" onClick={resetState}>
resetState
</button>
</p>
</div>
);
};