Welcome to WuJiGu Developer Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
482 views
in Technique[技术] by (71.8m points)

自定义hook请求的问题

刚开始接触hook,体会到了它的方便之处,但是也遇到不少问题。基于hook和fetch封装了一个自定义hook.
代码如下:

import React, { useState, useEffect, useRef } from "react";
import NavigationService from "@/navigation/NavigationService";
import store from "react-native-simple-store";
import getUrl from "@/utils/UrlUtil";
import ToastUtil from "@/utils/ToastUtil";
let newUrl = "";

/**
 * @desc 数据请求公共方法
 * @param {*} method 请求方式
 */
const useFetch = (method = "GET") => {
  const [res, setRes] = useState({});
  const [url, setUrl] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const firstUpdate = useRef(true);
  const sendFetch = (url) => {
    setUrl(getUrl(url));
  };
  useEffect(() => {
    let disCancel = false;
    (async () => {
      if (firstUpdate.current) return (firstUpdate.current = false);
      console.log("Not the first update");
      try {
        let access_token;
        await store.get("loginData").then((data) => {
          if (data && data.access_token) access_token = data.access_token;
          return data;
        });
        const options = {
          method,
          headers: { "Content-Type": "application/json;charset=utf-8" },
        };
        if (access_token) {
          newUrl = `${url}&access_token=${access_token}`;
        } else {
          newUrl = url;
        }
        !disCancel && setLoading(true);
        const res = await fetch(newUrl, options);
        const json = await res.json();
        !disCancel && setLoading(false);
        if (json.error) {
          //* 请求成功,返回错误数据
          if (json.error === "invalid_token") {
            //* token过期
            store.delete("loginData");
            NavigationService.replace("Login");
            return;
          }
          ToastUtil.showLong(json.error);
        } else {
          //* 请求成功,返回正确数据
          !disCancel && setRes(json.data || json);
        }
      } catch (error) {
        !disCancel && setLoading(false);
        !disCancel && setError(error);
      }
    })();
    return () => {
      disCancel = true
    }
  }, [url]);
  return { sendFetch, res, loading, error };
};

export default useFetch;

问题一:参数传递总是:看了不少别人写的,基本上都是把url作为useEffect依赖的变量,然后把sendFetchexport出去,在需要发送请求的地方调用。但是其它的关键参数怎么传递更好呢。像getpost方法,我是在useFetch的时候传,还是sendFetch的时候。看别人写的hook都是把initialUrlinitailData这两个参数在调用useFetch的时候传递的,我感觉没必要,我只在useFetch的时候传了get或者post
问题:如何添加统一的token:我们接口的token是通过添加到url后面传递的,也就是我不能把token放到optionsheader里。现在的做法就是通过react-native-simple-store先从本地缓存里读取,然后在判断有没有token有就添加到url里,但是这个库是异步执行的,所以我必须在前面加下await确保先拿到token再发请求,感觉这样写代码又不太好看。总感觉自己封装的hook不简洁


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

确实 我开始用的时候也很纠结 总感觉哪哪都别扭
现在总结了一套做法感觉还行, 供你参考

首先实现一个useLoadable方法, 这是一个比较通用的方法,只接收一个异步方法,然后生成状态与结果。

const defaultState = { loading: undefined };
export function useLoadable<E extends Error, T extends AsyncRequest>(
  request: T,
  initialState: Response<T, E> = defaultState
) {
  const [state, setState] = useState<Response<T, E>>(initialState);
  const mountedRef = useRef(false);
  useEffect(() => {
    mountedRef.current = true;
    return () => {
      mountedRef.current = false;
    };
  }, []);
  const callRef = useRef(0);
  const callbackRef = useRef(request);
  useEffect(() => {
    callbackRef.current = request;
  }, [request]);

  const callback = useCallback((...args: Parameters<T>): ReturnType<T> => {
    const callId = ++callRef.current;
    return callbackRef.current(...args).then(
      (value) => {
        mountedRef.current &&
          callId === callRef.current &&
          setState({ value, loading: false });
        return value;
      },
      (error) => {
        mountedRef.current &&
          callId === callRef.current &&
          setState({ error, loading: false });
        return error;
      }
    ) as ReturnType<T>;
  }, []);
  return [state, callback] as [Response<T, E>, typeof callback];
}

类型定义:

type State<T, E> =
  | {
      loading: undefined;
      error?: undefined;
      value?: undefined;
    }
  | {
      loading: true;
      error?: E | undefined;
      value?: T;
    }
  | {
      loading: false;
      error: E;
      value?: undefined;
    }
  | {
      loading: false;
      error?: undefined;
      value: T;
    };

type AsyncRequest = (...args: any[]) => Promise<any>;
type PromiseType<P extends Promise<any>> = P extends Promise<infer T>
  ? T
  : never;
type Response<T extends AsyncRequest, E> = State<PromiseType<ReturnType<T>>, E>;

使用的例子:

  const [state, request] = useLoadable(async (p1: string, p2: string) => {
    return await new Promise<{ p1: string; p2: string }>((resolve) =>
      setTimeout(() => {
        resolve({ p1, p2 });
      }, 1000)
    );
  });
  const onClick = () => request('p1', 'p2');

然后对于确定的业务请求进行再次封装,比如:

const useAuth = () => {
  const [signinState, signin] = useLoadable(async (username:string, password:string) => {
    return await API.signin(username, password);
  })
  const [signupState, signup] = useLoadable(async (name:string, email:string) => {
    return await API.signup(name, email);
  });

  // state 可以进行自定义合并
  return [{...signinState, ...signupState}, signin, signup];
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to WuJiGu Developer Q&A Community for programmer and developer-Open, Learning and Share

2.1m questions

2.1m answers

62 comments

56.6k users

...