React native Query、Retryer源码分析一

216 阅读2分钟

XXXPage

1、QueryClientProvider

const queryClient = new QueryClient({
    ...
}); 



<QueryClientProvider client={queryClient}>

   <XXXPage props={props} />
   
 </QueryClientProvider>

QueryClient,管理默认配置和全局状态。

QueryClientProvider 把QueryClient  Provider 给 子view children。

export const QueryClientProvider = ({
  client,
  children,
  context,
  contextSharing = false,
}: QueryClientProviderProps): JSX.Element => {
  
  ...

  const Context = getQueryClientContext(context, contextSharing)

  return (
    <QueryClientSharingContext.Provider value={!context && contextSharing}>
      <Context.Provider value={client}>{children}</Context.Provider>
    </QueryClientSharingContext.Provider>
  )
}

从context里面那到provider的QueryClient,  useQueryClient

export const useQueryClient = ({ context }: ContextOptions = {}) => {
  const queryClient = React.useContext(
    getQueryClientContext(context, React.useContext(QueryClientSharingContext)),
  )

  ...

  return queryClient
}

2、useQuery

const { data, isLoading, isError, refetch } = useQuery<XXXResponse>(
        ['key'],
        () => requestXXXInfo()
  
  ...
  ...
  
 return (
       ...
            {isLoading && (
                <Loading/>
            )}

            {isError && (
               <NetError/>
            )}
        
            {data && ( <View/>                         
            )}
  
export function useQuery<
  TQueryFnData = unknown,
  TError = unknown,
  TData = TQueryFnData,
  TQueryKey extends QueryKey = QueryKey,
>(
  queryKey: TQueryKey,
  queryFn: QueryFunction<TQueryFnData, TQueryKey>,
  options?: Omit<
    UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>,
    'queryKey' | 'queryFn'
  >,
): UseQueryResult<TData, TError>
    
    
export function useQuery<
  TQueryFnData,
  TError,
  TData = TQueryFnData,
  TQueryKey extends QueryKey = QueryKey,
>(
  arg1: TQueryKey | UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>,
  arg2?:
    | QueryFunction<TQueryFnData, TQueryKey>
    | UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>,
  arg3?: UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>,
): UseQueryResult<TData, TError> {
  const parsedOptions = parseQueryArgs(arg1, arg2, arg3)
  return useBaseQuery(parsedOptions, QueryObserver)
}

useBaseQuery

当每次调用 useQuery 时,内部都会实例一个观察者对象 observer。

observer 会订阅 query 的状态,当这些状态变化时,触发 React 强制更新。


  const [observer] = React.useState(
    () =>
      new Observer<TQueryFnData, TError, TData, TQueryData, TQueryKey>(
        queryClient,
        defaultedOptions,
      ),
  )

3、QueryCache

export class QueryCache extends Subscribable<QueryCacheListener> {
  config: QueryCacheConfig

  private queries: Query<any, any, any, any>[]
  private queriesMap: QueryHashMap

  constructor(config?: QueryCacheConfig) {
    super()
    this.config = config || {}
    this.queries = []
    this.queriesMap = {}
  }
  
  . . .
  . . .

}

4、Retryer 重试器

网络请求,重试、取消、延迟...

export interface Retryer<TData = unknown> {
  promise: Promise<TData>
  cancel: (cancelOptions?: CancelOptions) => void
  continue: () => Promise<unknown>
  cancelRetry: () => void
  continueRetry: () => void
}

RetryerConfig

interface RetryerConfig<TData = unknown, TError = unknown> {
  fn: () => TData | Promise<TData>
  abort?: () => void
  onError?: (error: TError) => void
  onSuccess?: (data: TData) => void
  onFail?: (failureCount: number, error: TError) => void
  onPause?: () => void
  onContinue?: () => void
  retry?: RetryValue<TError>
  retryDelay?: RetryDelayValue<TError>
  networkMode: NetworkMode | undefined
}
export type NetworkMode = 'online' | 'always' | 'offlineFirst'

createRetryer 创建重试器

...  
...


export function createRetryer<TData = unknown, TError = unknown>(
  config: RetryerConfig<TData, TError>,
): Retryer<TData> {
  let isRetryCancelled = false
  let failureCount = 0
  let isResolved = false
  let continueFn: ((value?: unknown) => boolean) | undefined
  let promiseResolve: (data: TData) => void
  let promiseReject: (error: TError) => void

  // Resolve Reject 通知外部
  const promise = new Promise<TData>((outerResolve, outerReject) => {
    promiseResolve = outerResolve
    promiseReject = outerReject
  })

  const cancel = (cancelOptions?: CancelOptions): void => {
    if (!isResolved) {
      reject(new CancelledError(cancelOptions))
      config.abort?.()
    }
  }
  const cancelRetry = () => {
    isRetryCancelled = true
  }

  const continueRetry = () => {
    isRetryCancelled = false
  }

  const shouldPause = () =>
    !focusManager.isFocused() ||
    (config.networkMode !== 'always' && !onlineManager.isOnline())

  const resolve = (value: any) => {
    if (!isResolved) {
      isResolved = true
      config.onSuccess?.(value)
      continueFn?.()
      promiseResolve(value)
    }
  }

  const reject = (value: any) => {
    if (!isResolved) {
      isResolved = true
      config.onError?.(value)
      continueFn?.()
      promiseReject(value)
    }
  }

  const pause = () => {
    return new Promise((continueResolve) => {
      // continueFn 抛出去 用于continue
      continueFn = (value) => {
        const canContinue = isResolved || !shouldPause()
        if (canContinue) {
          // canContinue == true
          // Resolve 之后继续执行then
          continueResolve(value)
        }
        return canContinue
      }
      config.onPause?.()
    }).then(() => {
      continueFn = undefined
      if (!isResolved) {
        config.onContinue?.()
      }
    })
  }

  // Create loop function
  const run = () => {
    ...
    
    // Value 转promise对象
    Promise.resolve(promiseOrValue)
    
    // 执行成功 resolve
      .then(resolve)
    
    // 执行失败 开始重试
      .catch((error) => {
        // Stop if the fetch is already resolved
        if (isResolved) {
          return
        }

        // 重试次数
        const retry = config.retry ?? 3
        
        // 重试延迟
        const retryDelay = config.retryDelay ?? defaultRetryDelay
        const delay =
          typeof retryDelay === 'function'
            ? retryDelay(failureCount, error)
            : retryDelay
        const shouldRetry =
          retry === true ||
          (typeof retry === 'number' && failureCount < retry) ||
          (typeof retry === 'function' && retry(failureCount, error))

        if (isRetryCancelled || !shouldRetry) {
          // We are done if the query does not need to be retried
          reject(error)
          return
        }

        failureCount++

        // Notify on fail
        config.onFail?.(failureCount, error)

        // Delay
        sleep(delay)
          // Pause if the document is not visible or when the device is offline
          .then(() => {
            if (shouldPause()) {
              return pause()
            }
            return
          })
          .then(() => {
            if (isRetryCancelled) {
              reject(error)
            } else {
              // loop
              run()
            }
          })
      })
  }

  // Start loop
  if (canFetch(config.networkMode)) {
    run()
  } else {
    pause().then(run)
  }

  return {
    promise,
    cancel,
    continue: () => {
      const didContinue = continueFn?.()
      // didContinue 继续重试 返回Promise,否者直接resolve
      return didContinue ? promise : Promise.resolve()
    },
    cancelRetry,
    continueRetry,
  }
}

5、整体结构