让生活变得更加快乐的 Hooks!

useRequest

Source Code
export type RequestApi<A extends any[], T> = (...args: A) => Promise<T>
export function useRequest<A extends any[], D>(requestMethod: RequestApi<A, D>, initialValue?: D) {
  const [data, setData] = useState<D | undefined>(initialValue)
  const [loading, setLoading] = useState<boolean>(false)
  const [error, setError] = useState<any>()
  const isMounted = useRef<boolean | null>(true)

  useEffect(() => {
    isMounted.current = true;
    return () => { isMounted.current = false }
  }, []);

  const doRequest = useCallback((...args: A) => {
    setLoading(true)
    setError(undefined)
    requestMethod(...args)
    .then((data) => {
      if (isMounted.current) {
        setData(data)
      }
    }).catch((error) => {
      if (isMounted.current) {
        setError(error)
      }
    }).finally(() => {
      if (isMounted.current) {
        setLoading(false)
      }
    })
  }, [requestMethod])

  return {
    data: [data, setData],
    state: [loading, error],
    methods: [doRequest]
  } as const
}


Usage
async function getUserApi(userId: number) {
    return await axios.get(/*...*/)
}

function DemoPage() {
    const {
        data: [user],
        state: [isUserLoading, isUserError],
        methods: [getUser],
    } = useRequest(getUserApi)
    
    useEffect(() => {
        return getUser(1)
    }, [])
    
    return (
        <div>
            {isUserLoading ? (
                <div>Loading...</div>
            ) : (
                <div>{user.name}</div>
            )}
        </div>
    )
}

usePromise

Source Code
export type PromiseApi<A extends any[], T> = (...args: A) => Promise<T>
export type LoadingState = { [key: string]: boolean }
export function usePromise<A extends any[], D>(requestMethod: PromiseApi<A, D>) {
  const [loading, setLoading] = useState<LoadingState>({})
  const doRequest = useCallback((id: string = 'default') => {
    return (...args: A) => {
      return new Promise<D>((resolve, reject) => {
        setLoading((value) => ({ ...value, [id]: true, default: true }))
        requestMethod(...args)
        .then((data) => {
          resolve(data)
          setLoading((value) => ({ ...value, [id]: false, default: false }))
        }).catch((error) => {
          reject(error)
          setLoading((value) => ({ ...value, [id]: false, default: false }))
        })
      })
    }
  }, [requestMethod])

  const defaultLoading = useMemo(() => loading['default'], [loading])
  const defaultDoRequest = useMemo(() => doRequest(), [doRequest])

  return {
    state: [defaultLoading, loading],
    methods: [defaultDoRequest, doRequest],
   } as const
}

Usage
async function saveUserApi(userId: number, doc: any) {
  return await axios.post(/*...*/)
}

function DemoPage() {
  const {
      state: [saveUserLoading],
      methods: [saveUser],
  } = usePromise(saveUserApi)
  
  return (
      <div>
        <Button
          loading={saveUserLoading}
          onClick={() => { saveUser(1, { name: 'John Smith' }) }}
        ></Button>
      </div>
  )
}