├── docs
├── guide.md
└── index.md
├── src
├── hooks
│ ├── UseRequest
│ │ ├── .DS_Store
│ │ ├── Plugins
│ │ │ ├── loadingDeleyPlugin.js
│ │ │ ├── refreshOnWindowFocusPlugin.js
│ │ │ ├── autoRunPlugin.js
│ │ │ ├── throttlePlugin.js
│ │ │ ├── debouncePlugin.js
│ │ │ ├── pollingPlugin.js
│ │ │ ├── retryPlugin.js
│ │ │ └── catchPlugin.js
│ │ ├── index.js
│ │ ├── UseRequest.js
│ │ └── Fetch.js
│ ├── UseUpdate
│ │ └── index.js
│ ├── utils
│ │ ├── limit.js
│ │ ├── cacheSubscribe.js
│ │ ├── cachePromise.js
│ │ ├── cache.js
│ │ └── subscribeFocus.js
│ ├── UseMemoizedFn
│ │ └── index.js
│ ├── UseUpdateEffect
│ │ └── index.js
│ ├── UseCreation
│ │ └── index.js
│ └── index.md
├── index.js
├── Request
│ ├── env.js
│ ├── constant.js
│ ├── retry.js
│ ├── checkStatus.js
│ ├── index.js
│ ├── canceler.js
│ ├── transform.js
│ ├── request.js
│ └── index.md
├── locales
│ ├── tw_TW.json
│ ├── zh_CN.json
│ ├── ko_KR.json
│ ├── ar_AR.json
│ ├── th_TH.json
│ ├── tr_TR.json
│ ├── vi_VN.json
│ ├── ru_RU.json
│ ├── pl_PL.json
│ ├── sv_SE.json
│ ├── en_US.json
│ ├── id_ID.json
│ ├── pt_PT.json
│ ├── nl_NL.json
│ ├── es_ES.json
│ ├── fr_FR.json
│ └── de_DE.json
├── utils.js
├── evt.js
├── i18n.js
└── components
│ └── message
│ └── index.jsx
├── tsconfig.json
├── package.json
└── README.md
/docs/guide.md:
--------------------------------------------------------------------------------
1 | This is a guide example.
2 |
--------------------------------------------------------------------------------
/src/hooks/UseRequest/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nemo-crypto/bit-request/HEAD/src/hooks/UseRequest/.DS_Store
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | export { default as useRequest } from './hooks/UseRequest';
2 |
3 | export { default as request } from './Request';
4 |
5 | export { createRequest } from './Request';
6 |
--------------------------------------------------------------------------------
/src/Request/env.js:
--------------------------------------------------------------------------------
1 | export const device =
2 | typeof window === 'undefined'
3 | ? 'service'
4 | : window.currencyexchange || window.currency_exchange
5 | ? 'app'
6 | : 'browser';
7 |
--------------------------------------------------------------------------------
/src/hooks/UseUpdate/index.js:
--------------------------------------------------------------------------------
1 | import { useCallback, useState } from 'react';
2 |
3 | function useUpdate() {
4 | const [_, setState] = useState({});
5 | return useCallback(() => setState({}), []);
6 | }
7 |
8 | export default useUpdate;
9 |
--------------------------------------------------------------------------------
/src/Request/constant.js:
--------------------------------------------------------------------------------
1 | export const token = 'token_url';
2 |
3 | export const CONTENTTYPE = {
4 | JSON: 'application/json;charset=utf-8',
5 | FORM_URLENCODE: 'application/x-www-form-urlencoded;charset=UTF-8',
6 | FORM_DATA: 'multipart/form-data;charset=UTF-8',
7 | };
8 |
--------------------------------------------------------------------------------
/src/hooks/utils/limit.js:
--------------------------------------------------------------------------------
1 | export default function limit(fn, timespan) {
2 | let pending = false;
3 | return (...args) => {
4 | if (pending) return;
5 | pending = true;
6 | fn(...args);
7 | setTimeout(() => {
8 | pending = false;
9 | }, timespan);
10 | };
11 | }
12 |
--------------------------------------------------------------------------------
/src/locales/tw_TW.json:
--------------------------------------------------------------------------------
1 | {
2 | "err.400": "錯誤的請求:伺服器無法理解請求。",
3 | "err.401": "未經授權:需要身份驗證,但身份驗證失敗或未提供。",
4 | "err.403": "禁止訪問:伺服器理解請求,但拒絕授權。",
5 | "err.404": "找不到:找不到請求的資源。",
6 | "err.500": "內部伺服器錯誤:伺服器遇到意外情況,阻止它完成請求。",
7 | "err.501": "未實現:伺服器不支援所請求的功能。",
8 | "err.503": "服務不可用:伺服器目前無法處理請求,可能是因為伺服器過載或正在維護。"
9 | }
10 |
--------------------------------------------------------------------------------
/src/locales/zh_CN.json:
--------------------------------------------------------------------------------
1 | {
2 | "err.400": "错误请求:服务器无法理解该请求。",
3 | "err.401": "未经授权:需要身份验证,但身份验证失败或未提供。",
4 | "err.403": "禁止访问:服务器理解请求,但拒绝授权。",
5 | "err.404": "未找到:无法找到请求的资源。",
6 | "err.500": "内部服务器错误:服务器遇到了意外情况,阻止它完成请求。",
7 | "err.501": "未实现:服务器不支持所请求的功能。",
8 | "err.503": "服务不可用:服务器当前无法处理请求,可能是由于服务器过载或正在维护。"
9 | }
10 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "declaration": false,
5 | "skipLibCheck": true,
6 | "esModuleInterop": true,
7 | "jsx": "react",
8 | "baseUrl": "./",
9 | "paths": {
10 | "@@/*": [".dumi/tmp/*"],
11 | "@bit/request": ["src"],
12 | "@bit/request/*": ["src/*", "*"]
13 | }
14 | },
15 | "include": [".dumirc.ts", "src/**/*"]
16 | }
17 |
--------------------------------------------------------------------------------
/src/locales/ko_KR.json:
--------------------------------------------------------------------------------
1 | {
2 | "err.400": "잘못된 요청: 서버가 요청을 이해하지 못했습니다.",
3 | "err.401": "인증되지 않음: 인증이 필요하며 실패했거나 제공되지 않았습니다.",
4 | "err.403": "금지됨: 서버가 요청을 이해했지만 권한을 거부합니다.",
5 | "err.404": "찾을 수 없음: 요청한 리소스를 찾을 수 없습니다.",
6 | "err.500": "내부 서버 오류: 서버가 요청을 처리하는 데 방해가 되는 예상치 못한 상태를 만났습니다.",
7 | "err.501": "구현되지 않음: 서버는 요청된 기능을 지원하지 않습니다.",
8 | "err.503": "서비스 이용 불가: 서버는 현재 일시적으로 과부하되었거나 서버 유지 보수로 인해 요청을 처리할 수 없습니다."
9 | }
10 |
--------------------------------------------------------------------------------
/src/hooks/UseMemoizedFn/index.js:
--------------------------------------------------------------------------------
1 | import { useMemo, useRef } from 'react';
2 |
3 | function useMemoizedFn(fn) {
4 | const fnRef = useRef(fn);
5 |
6 | fnRef.current = useMemo(() => fn, [fn]);
7 |
8 | const memoizedFn = useRef();
9 |
10 | if (!memoizedFn.current) {
11 | memoizedFn.current = function (...args) {
12 | return fnRef.current.apply(this, args);
13 | };
14 | }
15 | return memoizedFn.current;
16 | }
17 |
18 | export default useMemoizedFn;
19 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | hero:
3 | title: library
4 | description: A react request library
5 | actions:
6 | - text: Hello
7 | link: /
8 | - text: World
9 | link: /
10 | features:
11 | - title: Hello
12 | emoji: 💎
13 | description: Put hello description here
14 | - title: World
15 | emoji: 🌈
16 | description: Put world description here
17 | - title: '!'
18 | emoji: 🚀
19 | description: Put ! description here
20 | ---
21 |
22 | @bit/request
23 |
--------------------------------------------------------------------------------
/src/hooks/UseUpdateEffect/index.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from 'react';
2 |
3 | function useUpdateEffect(effect, deps) {
4 | const isMounted = useRef(false);
5 |
6 | useEffect(() => {
7 | return () => {
8 | isMounted.current = false;
9 | };
10 | }, []);
11 |
12 | useEffect(() => {
13 | if (!isMounted.current) {
14 | isMounted.current = true;
15 | } else {
16 | return effect();
17 | }
18 | }, deps);
19 | }
20 |
21 | export default useUpdateEffect;
22 |
--------------------------------------------------------------------------------
/src/hooks/utils/cacheSubscribe.js:
--------------------------------------------------------------------------------
1 | const listeners = {};
2 |
3 | const trigger = (key, data) => {
4 | if (listeners[key]) {
5 | listeners[key].forEach((item) => item(data));
6 | }
7 | };
8 |
9 | const subscribe = (key, listener) => {
10 | if (!listeners[key]) {
11 | listeners[key] = [];
12 | }
13 | listeners[key].push(listener);
14 |
15 | return function unsubscribe() {
16 | const index = listeners[key].indexOf(listener);
17 | listeners[key].splice(index, 1);
18 | };
19 | };
20 |
21 | export { subscribe, trigger };
22 |
--------------------------------------------------------------------------------
/src/locales/ar_AR.json:
--------------------------------------------------------------------------------
1 | {
2 | "err.400": "طلب غير صحيح: الخادم لا يستطيع فهم الطلب.",
3 | "err.401": "غير مصرح: الهوية مطلوبة وفشلت أو لم يتم توفيرها.",
4 | "err.403": "ممنوع: الخادم يفهم الطلب ولكنه يرفض التفويض.",
5 | "err.404": "غير موجود: لا يمكن العثور على المورد المطلوب.",
6 | "err.500": "خطأ داخلي في الخادم: واجه الخادم حالة غير متوقعة منعته من تنفيذ الطلب.",
7 | "err.501": "غير مُنفَّذ: الخادم لا يدعم الميزة المطلوبة.",
8 | "err.503": "الخدمة غير متوفرة: الخادم غير قادر حاليًا على التعامل مع الطلب بسبب الحمل الزائد المؤقت أو صيانة الخادم."
9 | }
10 |
--------------------------------------------------------------------------------
/src/locales/th_TH.json:
--------------------------------------------------------------------------------
1 | {
2 | "err.400": "คำขอไม่ถูกต้อง: เซิร์ฟเวอร์ไม่สามารถเข้าใจคำขอได้",
3 | "err.401": "ไม่ได้รับอนุญาต: ต้องการการพิสูจน์ตัวตนและล้มเหลวหรือไม่ได้รับ",
4 | "err.403": "ห้าม: เซิร์ฟเวอร์เข้าใจคำขอ แต่ปฏิเสธให้สิทธิ์",
5 | "err.404": "ไม่พบ: ไม่สามารถค้นหาทรัพยากรที่ร้องขอ",
6 | "err.500": "ข้อผิดพลาดภายในเซิร์ฟเวอร์: เซิร์ฟเวอร์พบสถานการณ์ที่ไม่คาดคิดทำให้ไม่สามารถทำตามคำขอได้",
7 | "err.501": "ยังไม่ได้นำมาใช้: เซิร์ฟเวอร์ไม่รองรับความสามารถที่ขอ",
8 | "err.503": "บริการไม่พร้อมให้บริการ: เซิร์ฟเวอร์ไม่สามารถดำเนินการตามคำขอในขณะนี้เนื่องจากการโอนซองหรือการบำรุงรักษาชั่วคราวของเซิร์ฟเวอร์"
9 | }
10 |
--------------------------------------------------------------------------------
/src/locales/tr_TR.json:
--------------------------------------------------------------------------------
1 | {
2 | "err.400": "Geçersiz İstek: Sunucu isteği anlayamadı.",
3 | "err.401": "Yetkisiz: Kimlik doğrulama gerekiyor ve başarısız oldu veya sağlanmadı.",
4 | "err.403": "Yasak: Sunucu isteği anladı, ancak yetkilendirmeyi reddediyor.",
5 | "err.404": "Bulunamadı: İstenen kaynak bulunamadı.",
6 | "err.500": "İç Sunucu Hatası: Sunucu, isteği yerine getirmesini engelleyen beklenmeyen bir durumla karşılaştı.",
7 | "err.501": "Uygulanmadı: Sunucu, istenen özelliği desteklemiyor.",
8 | "err.503": "Hizmet Kullanılamıyor: Sunucu şu anda geçici olarak aşırı yükleme veya bakım nedeniyle isteği işleyemiyor."
9 | }
10 |
--------------------------------------------------------------------------------
/src/locales/vi_VN.json:
--------------------------------------------------------------------------------
1 | {
2 | "err.400": "Yêu cầu Không Hợp Lệ: Máy chủ không thể hiểu yêu cầu.",
3 | "err.401": "Không được Ủy Quyền: Yêu cầu xác thực và đã thất bại hoặc không được cung cấp.",
4 | "err.403": "Bị Từ Chối: Máy chủ hiểu yêu cầu, nhưng từ chối ủy quyền.",
5 | "err.404": "Không Tìm Thấy: Không thể tìm thấy nguồn tài nguyên yêu cầu.",
6 | "err.500": "Lỗi Nội Bộ của Máy Chủ: Máy chủ gặp phải một điều kiện không mong muốn đã ngăn chặn nó từ việc thực hiện yêu cầu.",
7 | "err.501": "Chưa Thực Hiện: Máy chủ không hỗ trợ tính năng được yêu cầu.",
8 | "err.503": "Dịch Vụ Không Khả Dụng: Máy chủ hiện không thể xử lý yêu cầu do quá tải tạm thời hoặc bảo trì máy chủ."
9 | }
10 |
--------------------------------------------------------------------------------
/src/locales/ru_RU.json:
--------------------------------------------------------------------------------
1 | {
2 | "err.400": "Неверный запрос: Сервер не смог понять запрос.",
3 | "err.401": "Не авторизован: Требуется аутентификация, и она не удалась или не предоставлена.",
4 | "err.403": "Запрещено: Сервер понял запрос, но отказывается предоставить авторизацию.",
5 | "err.404": "Не найдено: Запрошенный ресурс не найден.",
6 | "err.500": "Внутренняя ошибка сервера: Сервер столкнулся с непредвиденным состоянием, которое помешало ему выполнить запрос.",
7 | "err.501": "Не реализовано: Сервер не поддерживает запрошенную функцию.",
8 | "err.503": "Сервис недоступен: Сервер в данный момент не может обработать запрос из-за временной перегрузки или обслуживания сервера."
9 | }
10 |
--------------------------------------------------------------------------------
/src/locales/pl_PL.json:
--------------------------------------------------------------------------------
1 | {
2 | "err.400": "Zły żądanie: Serwer nie mógł zrozumieć żądania.",
3 | "err.401": "Nieautoryzowany: Wymagane jest uwierzytelnienie i nie powiodło się lub nie zostało podane.",
4 | "err.403": "Zakazane: Serwer zrozumiał żądanie, ale odmawia autoryzacji.",
5 | "err.404": "Nie znaleziono: Nie można znaleźć żądanego zasobu.",
6 | "err.500": "Wewnętrzny błąd serwera: Serwer napotkał nieoczekiwaną sytuację, która uniemożliwiła mu spełnienie żądania.",
7 | "err.501": "Nie zaimplementowano: Serwer nie obsługuje żądanej funkcji.",
8 | "err.503": "Usługa niedostępna: Serwer obecnie nie może obsługiwać żądania z powodu tymczasowego przeciążenia lub konserwacji serwera."
9 | }
10 |
--------------------------------------------------------------------------------
/src/hooks/utils/cachePromise.js:
--------------------------------------------------------------------------------
1 | const cachePromise = new Map();
2 |
3 | const getCachePromise = (cacheKey) => {
4 | return cachePromise.get(cacheKey);
5 | };
6 |
7 | const setCachePromise = (cacheKey, promise) => {
8 | // Should cache the same promise, cannot be promise.finally
9 | // Because the promise.finally will change the reference of the promise
10 | cachePromise.set(cacheKey, promise);
11 |
12 | // no use promise.finally for compatibility
13 | promise
14 | .then((res) => {
15 | cachePromise.delete(cacheKey);
16 | return res;
17 | })
18 | .catch(() => {
19 | cachePromise.delete(cacheKey);
20 | });
21 | };
22 |
23 | export { getCachePromise, setCachePromise };
24 |
--------------------------------------------------------------------------------
/src/locales/sv_SE.json:
--------------------------------------------------------------------------------
1 | {
2 | "err.400": "Felaktig förfrågan: Servern kunde inte förstå förfrågan.",
3 | "err.401": "Obehörig: Autentisering krävs och har misslyckats eller har inte tillhandahållits.",
4 | "err.403": "Förbjuden: Servern förstod förfrågan men vägrar att auktorisera den.",
5 | "err.404": "Ej hittad: Begärd resurs kunde inte hittas.",
6 | "err.500": "Intern Serverfel: Servern stötte på ett oväntat tillstånd som förhindrade den från att uppfylla begäran.",
7 | "err.501": "Ej Implementerat: Servern stöder inte den begärda funktionen.",
8 | "err.503": "Tjänst Ej Tillgänglig: Servern kan för närvarande inte hantera begäran på grund av temporär överbelastning eller underhåll av servern."
9 | }
10 |
--------------------------------------------------------------------------------
/src/hooks/UseCreation/index.js:
--------------------------------------------------------------------------------
1 | import { useRef } from 'react';
2 |
3 | function depsAreSame(oldDeps, deps) {
4 | if (oldDeps === deps) return true;
5 | for (let i = 0; i < oldDeps.length; i++) {
6 | if (!Object.is(oldDeps[i], deps[i])) return false;
7 | }
8 | return true;
9 | }
10 |
11 | function useCreation(factory, deps) {
12 | const { current } = useRef({
13 | deps,
14 | obj: undefined,
15 | initialized: false,
16 | });
17 |
18 | if (current.initialized === false || !depsAreSame(current.deps, deps)) {
19 | current.deps = deps;
20 | current.obj = factory();
21 | current.initialized = true;
22 | }
23 | return current.obj;
24 | }
25 |
26 | export default useCreation;
27 |
--------------------------------------------------------------------------------
/src/locales/en_US.json:
--------------------------------------------------------------------------------
1 | {
2 | "err.400": "Bad Request: The server could not understand the request.",
3 | "err.401": "Unauthorized: Authentication is required and has failed or has not been provided.",
4 | "err.403": "Forbidden: You do not have permission to perform this operation",
5 | "err.404": "Not Found: The requested resource could not be found.",
6 | "err.500": "Internal Server Error: The server encountered an unexpected condition that prevented it from fulfilling the request.",
7 | "err.501": "Not Implemented: The server does not support the requested feature.",
8 | "err.503": "Service Unavailable: The server is currently unable to handle the request due to temporary overloading or maintenance of the server."
9 | }
10 |
--------------------------------------------------------------------------------
/src/locales/id_ID.json:
--------------------------------------------------------------------------------
1 | {
2 | "err.400": "Permintaan Tidak Valid: Server tidak dapat memahami permintaan.",
3 | "err.401": "Tidak Diizinkan: Autentikasi diperlukan dan gagal atau tidak diberikan.",
4 | "err.403": "Dilarang: Server memahami permintaan, tetapi menolak memberikan izin.",
5 | "err.404": "Tidak Ditemukan: Sumber daya yang diminta tidak dapat ditemukan.",
6 | "err.500": "Kesalahan Internal Server: Server mengalami kondisi yang tidak terduga yang mencegahnya memenuhi permintaan.",
7 | "err.501": "Belum Diimplementasikan: Server tidak mendukung fitur yang diminta.",
8 | "err.503": "Layanan Tidak Tersedia: Server saat ini tidak dapat menangani permintaan karena kelebihan beban sementara atau pemeliharaan server."
9 | }
10 |
--------------------------------------------------------------------------------
/src/locales/pt_PT.json:
--------------------------------------------------------------------------------
1 | {
2 | "err.400": "Pedido Inválido: O servidor não conseguiu entender o pedido.",
3 | "err.401": "Não Autorizado: A autenticação é necessária e falhou ou não foi fornecida.",
4 | "err.403": "Proibido: O servidor entendeu o pedido, mas recusa autorização.",
5 | "err.404": "Não Encontrado: Não foi possível encontrar o recurso solicitado.",
6 | "err.500": "Erro Interno do Servidor: O servidor encontrou uma condição inesperada que o impediu de atender ao pedido.",
7 | "err.501": "Não Implementado: O servidor não suporta a funcionalidade solicitada.",
8 | "err.503": "Serviço Indisponível: O servidor está atualmente incapaz de lidar com o pedido devido a sobrecarga temporária ou manutenção do servidor."
9 | }
10 |
--------------------------------------------------------------------------------
/src/locales/nl_NL.json:
--------------------------------------------------------------------------------
1 | {
2 | "err.400": "Bad Request: De server kon het verzoek niet begrijpen.",
3 | "err.401": "Unauthorized: Authenticatie is vereist en is mislukt of niet verstrekt.",
4 | "err.403": "Forbidden: De server begreep het verzoek maar weigert autorisatie.",
5 | "err.404": "Not Found: De gevraagde bron kon niet worden gevonden.",
6 | "err.500": "Interne Serverfout: De server heeft een onverwachte toestand ervaren die hem heeft verhinderd om aan het verzoek te voldoen.",
7 | "err.501": "Niet geïmplementeerd: De server ondersteunt de gevraagde functie niet.",
8 | "err.503": "Service Niet Beschikbaar: De server kan het verzoek momenteel niet verwerken vanwege tijdelijke overbelasting of onderhoud van de server."
9 | }
10 |
--------------------------------------------------------------------------------
/src/locales/es_ES.json:
--------------------------------------------------------------------------------
1 | {
2 | "err.400": "Solicitud incorrecta: El servidor no pudo entender la solicitud.",
3 | "err.401": "No autorizado: Se requiere autenticación y ha fallado o no se ha proporcionado.",
4 | "err.403": "Prohibido: El servidor entendió la solicitud, pero se niega a autorizarla.",
5 | "err.404": "No encontrado: No se pudo encontrar el recurso solicitado.",
6 | "err.500": "Error Interno del Servidor: El servidor encontró una condición inesperada que le impidió cumplir con la solicitud.",
7 | "err.501": "No Implementado: El servidor no admite la característica solicitada.",
8 | "err.503": "Servicio No Disponible: El servidor actualmente no puede manejar la solicitud debido a una sobrecarga temporal o mantenimiento del servidor."
9 | }
10 |
--------------------------------------------------------------------------------
/src/locales/fr_FR.json:
--------------------------------------------------------------------------------
1 | {
2 | "err.400": "Requête incorrecte : Le serveur n'a pas pu comprendre la requête.",
3 | "err.401": "Non autorisé : L'authentification est requise et a échoué ou n'a pas été fournie.",
4 | "err.403": "Interdit : Le serveur a compris la requête, mais refuse de l'autoriser.",
5 | "err.404": "Non trouvé : La ressource demandée est introuvable.",
6 | "err.500": "Erreur Interne du Serveur : Le serveur a rencontré une condition inattendue qui l'a empêché de répondre à la requête.",
7 | "err.501": "Non Mis en Œuvre : Le serveur ne prend pas en charge la fonction demandée.",
8 | "err.503": "Service Indisponible : Le serveur est actuellement incapable de traiter la requête en raison d'une surcharge temporaire ou de la maintenance du serveur."
9 | }
10 |
--------------------------------------------------------------------------------
/src/locales/de_DE.json:
--------------------------------------------------------------------------------
1 | {
2 | "err.400": "Bad Request: Der Server konnte die Anfrage nicht verstehen.",
3 | "err.401": "Unauthorized: Authentifizierung ist erforderlich und ist fehlgeschlagen oder wurde nicht bereitgestellt.",
4 | "err.403": "Forbidden: Der Server hat die Anfrage verstanden, verweigert jedoch die Autorisierung.",
5 | "err.404": "Not Found: Die angeforderte Ressource konnte nicht gefunden werden.",
6 | "err.500": "Interner Serverfehler: Der Server hat einen unerwarteten Zustand erlebt, der ihn daran hinderte, die Anfrage zu erfüllen.",
7 | "err.501": "Nicht implementiert: Der Server unterstützt das angeforderte Feature nicht.",
8 | "err.503": "Dienst nicht verfügbar: Der Server kann die Anfrage derzeit aufgrund vorübergehender Überlastung oder Wartung nicht verarbeiten."
9 | }
10 |
--------------------------------------------------------------------------------
/src/hooks/utils/cache.js:
--------------------------------------------------------------------------------
1 | const cache = new Map();
2 |
3 | const setCache = (key, cacheTime, cachedData) => {
4 | const currentCache = cache.get(key);
5 | if (currentCache?.timer) {
6 | clearTimeout(currentCache.timer);
7 | }
8 |
9 | let timer = undefined;
10 |
11 | if (cacheTime > -1) {
12 | // if cache out, clear it
13 | timer = setTimeout(() => {
14 | cache.delete(key);
15 | }, cacheTime);
16 | }
17 |
18 | cache.set(key, {
19 | ...cachedData,
20 | timer,
21 | });
22 | };
23 |
24 | const getCache = (key) => {
25 | return cache.get(key);
26 | };
27 |
28 | const clearCache = (key) => {
29 | if (key) {
30 | const cacheKeys = Array.isArray(key) ? key : [key];
31 | cacheKeys.forEach((cacheKey) => cache.delete(cacheKey));
32 | } else {
33 | cache.clear();
34 | }
35 | };
36 |
37 | export { clearCache, getCache, setCache };
38 |
--------------------------------------------------------------------------------
/src/hooks/UseRequest/Plugins/loadingDeleyPlugin.js:
--------------------------------------------------------------------------------
1 | import { useRef } from 'react';
2 |
3 | function loadingDeleyPlugin(fetchInstance, options) {
4 | const { loadingDeley, ready } = options;
5 | // eslint-disable-next-line react-hooks/rules-of-hooks
6 | const timerRef = useRef();
7 |
8 | if (!loadingDeley) {
9 | return {};
10 | }
11 |
12 | const cancelTimeout = () => {
13 | if (timerRef.current) {
14 | clearTimeout(timerRef.current);
15 | }
16 | };
17 |
18 | return {
19 | onBefore: () => {
20 | cancelTimeout();
21 | if (ready !== false) {
22 | timerRef.current = setTimeout(() => {
23 | fetchInstance.setState({
24 | loading: true,
25 | });
26 | }, loadingDeley);
27 | }
28 |
29 | return {
30 | loading: false,
31 | };
32 | },
33 | onFinally: () => {
34 | cancelTimeout();
35 | },
36 | onCancel: () => {
37 | cancelTimeout();
38 | },
39 | };
40 | }
41 |
42 | export default loadingDeleyPlugin;
43 |
--------------------------------------------------------------------------------
/src/hooks/utils/subscribeFocus.js:
--------------------------------------------------------------------------------
1 | const listeners = [];
2 |
3 | const isBrowser = !!(
4 | typeof window !== 'undefined' &&
5 | window.document &&
6 | window.document.createElement
7 | );
8 |
9 | const isDocumentVisible = () => {
10 | if (isBrowser) {
11 | return document.visibilityState !== 'hidden';
12 | }
13 | return true;
14 | };
15 |
16 | const isOnline = () => {
17 | if (isBrowser && typeof navigator.onLine !== undefined) {
18 | return navigator.onLine;
19 | }
20 | return true;
21 | };
22 |
23 | function subscribe(listener) {
24 | listeners.push(listener);
25 | return function unsubscribe() {
26 | const index = listeners.indexOf(listener);
27 | if (index > -1) {
28 | listeners.splice(index, 1);
29 | }
30 | };
31 | }
32 |
33 | if (isBrowser) {
34 | const revalidate = () => {
35 | if (!isDocumentVisible() || !isOnline()) return;
36 | for (let i = 0; i < listeners.length; i++) {
37 | const listener = listeners[i];
38 | listener();
39 | }
40 | };
41 | window.addEventListener('visibilitychange', revalidate, false);
42 | window.addEventListener('focus', revalidate, false);
43 | }
44 |
45 | export default subscribe;
46 |
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 | import { isPlainObject } from 'lodash';
2 |
3 | export const isFunction = (fn) => {
4 | return Object.prototype.toString.call(fn) === '[object Function]';
5 | };
6 |
7 | export const deepMerge = (...configs) => {
8 | const newConfig = {};
9 |
10 | configs.forEach((config) => {
11 | Object.keys(config).forEach((key) => {
12 | if (isPlainObject(config[key])) {
13 | if (newConfig[key]) {
14 | newConfig[key] = deepMerge(newConfig[key], config[key]);
15 | } else {
16 | newConfig[key] = deepMerge(config[key]);
17 | }
18 | } else {
19 | newConfig[key] = config[key];
20 | }
21 | });
22 | });
23 |
24 | return newConfig;
25 | };
26 |
27 | /**
28 | * 获取URL中指定参数的值
29 | * @param {String} name 名称
30 | * @param {String} url 地址
31 | * @return {String} 值
32 | */
33 | export const getParameterByName = (name, url) => {
34 | name = name.replace(/[[]/, '\\[').replace(/[\]]/, '\\]');
35 | let regex = new RegExp('[\\?&]' + name + '=([^]*)'),
36 | results = regex.exec(url || window.location.search);
37 | return results === null
38 | ? ''
39 | : decodeURIComponent(results[1].replace(/\+/g, ' '));
40 | };
41 |
--------------------------------------------------------------------------------
/src/evt.js:
--------------------------------------------------------------------------------
1 | class EventEmitter {
2 | constructor() {
3 | this._events = {};
4 | }
5 |
6 | on(eventName, callback) {
7 | if (this._events[eventName]) {
8 | if (this.eventName !== 'newListener') {
9 | this.emit('newListener', eventName);
10 | }
11 | }
12 | const callbacks = this._events[eventName] || [];
13 | callbacks.push(callback);
14 | this._events[eventName] = callbacks;
15 | }
16 |
17 | emit(eventName, ...args) {
18 | const callbacks = this._events[eventName] || [];
19 | callbacks.forEach((cb) => cb(...args));
20 | }
21 |
22 | once(eventName, callback) {
23 | const one = (...args) => {
24 | callback(...args);
25 | this.off(eventName, one);
26 | };
27 | one.initialCallback = callback;
28 | this.on(eventName, one);
29 | }
30 |
31 | off(eventName, callback) {
32 | const callbacks = this._events[eventName] || [];
33 | const newCallbacks = callbacks.filter(
34 | (fn) =>
35 | fn !== callback &&
36 | fn.initialCallback !== callback /* 用于once的取消订阅 */,
37 | );
38 | this._events[eventName] = newCallbacks;
39 | }
40 | }
41 |
42 | export default new EventEmitter();
43 |
44 | export { EventEmitter };
45 |
--------------------------------------------------------------------------------
/src/hooks/UseRequest/Plugins/refreshOnWindowFocusPlugin.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from 'react';
2 | import limit from '../../utils/limit';
3 | import subscribeFocus from '../../utils/subscribeFocus';
4 |
5 | const refreshOnWindowFocusPlugin = (
6 | fetchInstance,
7 | { refreshOnWindowFocus, focusTimespan = 1000 * 10 },
8 | ) => {
9 | // eslint-disable-next-line react-hooks/rules-of-hooks
10 | const unsubscribeRef = useRef();
11 |
12 | const stopSubscribe = () => {
13 | unsubscribeRef.current?.();
14 | };
15 |
16 | // eslint-disable-next-line react-hooks/rules-of-hooks
17 | useEffect(() => {
18 | if (refreshOnWindowFocus) {
19 | const limitRefresh = limit(
20 | fetchInstance.refresh.bind(fetchInstance),
21 | focusTimespan,
22 | );
23 | unsubscribeRef.current = subscribeFocus(() => {
24 | limitRefresh();
25 | });
26 | }
27 | return () => {
28 | stopSubscribe();
29 | };
30 | }, [refreshOnWindowFocus, focusTimespan]);
31 |
32 | // eslint-disable-next-line react-hooks/rules-of-hooks
33 | useEffect(() => {
34 | return () => {
35 | stopSubscribe();
36 | };
37 | }, []);
38 |
39 | return {};
40 | };
41 |
42 | export default refreshOnWindowFocusPlugin;
43 |
--------------------------------------------------------------------------------
/src/Request/retry.js:
--------------------------------------------------------------------------------
1 | import { isNumber } from 'lodash';
2 |
3 | export class AxiosRetry {
4 | maximum_backoff = 64 * 1000;
5 | retryTimes = 3;
6 | /**
7 | * 重试
8 | */
9 | retry(axiosInstance, error) {
10 | const { config } = error;
11 | const retryRequest = config?.requestOptions?.retryRequest || 0;
12 | const count = isNumber(retryRequest)
13 | ? retryRequest
14 | : retryRequest?.count || this.retryTimes;
15 | config.__retryCount = config.__retryCount || 0;
16 | if (config.__retryCount >= count) {
17 | return;
18 | }
19 | //请求返回后config的header不正确造成重试请求失败,删除返回headers采用默认headers
20 | delete config.headers;
21 | const waitTime = this.createTimeout(config.__retryCount);
22 | return this.delay(waitTime).then(() => {
23 | config.__retryCount += 1;
24 | axiosInstance(config);
25 | });
26 | }
27 |
28 | createTimeout(times) {
29 | const random_number_milliseconds = Math.floor(Math.random() * 1000);
30 | return Math.min(
31 | Math.pow(2, times) * 1000 + random_number_milliseconds,
32 | this.maximum_backoff,
33 | );
34 | }
35 |
36 | /**
37 | * 延迟
38 | */
39 | delay(waitTime) {
40 | return new Promise((resolve) => setTimeout(resolve, waitTime));
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/hooks/UseRequest/Plugins/autoRunPlugin.js:
--------------------------------------------------------------------------------
1 | import { useRef } from 'react';
2 | import useUpdateEffect from '../../UseUpdateEffect';
3 |
4 | function autoRunPlugin(
5 | fetchInstance,
6 | { manual, ready = true, defaultParams = [], refreshDeps = [] },
7 | ) {
8 | // eslint-disable-next-line react-hooks/rules-of-hooks
9 | const hasAutoRun = useRef(false);
10 | hasAutoRun.current = false;
11 | // eslint-disable-next-line react-hooks/rules-of-hooks
12 | useUpdateEffect(() => {
13 | if (!manual && ready) {
14 | hasAutoRun.current = true;
15 | fetchInstance.run(...defaultParams);
16 | }
17 | }, [ready]);
18 | // eslint-disable-next-line react-hooks/rules-of-hooks
19 | useUpdateEffect(() => {
20 | if (hasAutoRun.current) {
21 | return;
22 | }
23 | if (!manual) {
24 | hasAutoRun.current = true;
25 | fetchInstance.refresh();
26 | }
27 | }, [...refreshDeps]);
28 |
29 | return {
30 | onBefore: () => {
31 | if (!ready) {
32 | return {
33 | stopNow: true,
34 | };
35 | }
36 | },
37 | };
38 | }
39 |
40 | autoRunPlugin.onInit = ({ ready = true, manual }) => {
41 | return {
42 | loading: !manual && ready,
43 | };
44 | };
45 |
46 | export default autoRunPlugin;
47 |
--------------------------------------------------------------------------------
/src/hooks/UseRequest/Plugins/throttlePlugin.js:
--------------------------------------------------------------------------------
1 | import { throttle } from 'lodash';
2 | import { useEffect, useRef } from 'react';
3 |
4 | function throttlePlugin(fetchInstance, options) {
5 | const { throttleWait } = options;
6 | // eslint-disable-next-line react-hooks/rules-of-hooks
7 | const throttleRef = useRef();
8 | // eslint-disable-next-line react-hooks/rules-of-hooks
9 | useEffect(() => {
10 | if (throttleWait) {
11 | const _originRunAsync = fetchInstance.runAsync.bind(fetchInstance);
12 |
13 | throttleRef.current = throttle((callback) => {
14 | callback();
15 | }, throttleWait);
16 |
17 | fetchInstance.runAsync = (...args) => {
18 | return new Promise((resolve, reject) => {
19 | throttleRef.current?.(() => {
20 | _originRunAsync(...args)
21 | .then(resolve)
22 | .catch(reject);
23 | });
24 | });
25 | };
26 |
27 | return () => {
28 | throttleRef.current?.cancel();
29 | fetchInstance.runAsync = _originRunAsync;
30 | };
31 | }
32 | }, [throttleWait]);
33 |
34 | if (!throttleWait) {
35 | return {};
36 | }
37 |
38 | return {
39 | onCancel: () => {
40 | throttleRef.current?.cancel();
41 | },
42 | };
43 | }
44 |
45 | export default throttlePlugin;
46 |
--------------------------------------------------------------------------------
/src/hooks/UseRequest/Plugins/debouncePlugin.js:
--------------------------------------------------------------------------------
1 | import { debounce } from 'lodash';
2 | import { useEffect, useRef } from 'react';
3 |
4 | function debouncePlugin(fetchInstance, options) {
5 | const { debounceWait } = options;
6 | // eslint-disable-next-line react-hooks/rules-of-hooks
7 | const debouncedRef = useRef();
8 | // eslint-disable-next-line react-hooks/rules-of-hooks
9 | useEffect(() => {
10 | if (debounceWait) {
11 | const _originRunAsync = fetchInstance.runAsync.bind(fetchInstance);
12 |
13 | debouncedRef.current = debounce((callback) => {
14 | callback();
15 | }, debounceWait);
16 |
17 | fetchInstance.runAsync = (...args) => {
18 | return new Promise((resolve, reject) => {
19 | debouncedRef.current?.(() => {
20 | _originRunAsync(...args)
21 | .then(resolve)
22 | .catch(reject);
23 | });
24 | });
25 | };
26 |
27 | return () => {
28 | debouncedRef.current?.cancel();
29 | fetchInstance.runAsync = _originRunAsync;
30 | };
31 | }
32 | }, [debounceWait]);
33 |
34 | if (!debounceWait) {
35 | return {};
36 | }
37 |
38 | return {
39 | onCancel: () => {
40 | debouncedRef.current?.cancel();
41 | },
42 | };
43 | }
44 |
45 | export default debouncePlugin;
46 |
--------------------------------------------------------------------------------
/src/hooks/UseRequest/index.js:
--------------------------------------------------------------------------------
1 | import { isArray, isFunction } from 'lodash';
2 | import { default as request } from '../../Request';
3 | import autoRunPlugin from './Plugins/autoRunPlugin';
4 | import cachePlugin from './Plugins/catchPlugin';
5 | import debouncePlugin from './Plugins/debouncePlugin';
6 | import loadingDeleyPlugin from './Plugins/loadingDeleyPlugin';
7 | import pollingInterval from './Plugins/pollingPlugin';
8 | import refreshOnWindowFocusPlugin from './Plugins/refreshOnWindowFocusPlugin';
9 | import throttlePlugin from './Plugins/throttlePlugin';
10 | import { default as requestHook } from './UseRequest';
11 |
12 | const fetch = (service) => () => {
13 | const [url, config, options] = service;
14 | return request.request({ url, method: 'GET', ...config }, options);
15 | };
16 |
17 | function useRequest(service, options, plugins) {
18 | let servicefn = () => new Promise(() => {});
19 | if (isFunction(service)) {
20 | servicefn = service;
21 | } else if (isArray(service)) {
22 | servicefn = fetch(service);
23 | }
24 | return requestHook(servicefn, options || {}, [
25 | ...(plugins || []),
26 | autoRunPlugin,
27 | cachePlugin,
28 | loadingDeleyPlugin,
29 | pollingInterval,
30 | throttlePlugin,
31 | debouncePlugin,
32 | refreshOnWindowFocusPlugin,
33 | ]);
34 | }
35 |
36 | export default useRequest;
37 |
--------------------------------------------------------------------------------
/src/hooks/UseRequest/Plugins/pollingPlugin.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from 'react';
2 |
3 | function pollingPlugin(fetchInstance, options) {
4 | const { pollingInterval } = options;
5 | // eslint-disable-next-line react-hooks/rules-of-hooks
6 | const timerRef = useRef();
7 | // eslint-disable-next-line react-hooks/rules-of-hooks
8 | const countRef = useRef(0);
9 |
10 | const stopPolling = () => {
11 | if (timerRef.current) {
12 | clearTimeout(timerRef.current);
13 | }
14 | };
15 | // eslint-disable-next-line react-hooks/rules-of-hooks
16 | useEffect(() => {
17 | if (!pollingInterval) {
18 | stopPolling();
19 | }
20 | }, [pollingInterval]);
21 |
22 | if (!pollingInterval) {
23 | return {};
24 | }
25 |
26 | return {
27 | onBefore: () => {
28 | stopPolling();
29 | },
30 | onError: () => {
31 | countRef.current += 1;
32 | },
33 | onSuccess: () => {
34 | countRef.current = 0;
35 | },
36 | onFinally: () => {
37 | if (countRef.current === 0) {
38 | // 轮询失败关闭轮询
39 | timerRef.current = setTimeout(() => {
40 | fetchInstance.refresh();
41 | }, pollingInterval);
42 | } else {
43 | countRef.current = 0;
44 | }
45 | },
46 | onCancel: () => {
47 | stopPolling();
48 | },
49 | };
50 | }
51 |
52 | export default pollingPlugin;
53 |
--------------------------------------------------------------------------------
/src/Request/checkStatus.js:
--------------------------------------------------------------------------------
1 | import message from '../components/message';
2 | import intl from '../i18n';
3 |
4 | export function checkStatus(status, msg, config, error) {
5 | const { errorMessageMode } = config?.requestOptions || {};
6 |
7 | let errorMessage = '';
8 |
9 | if (error?.message?.includes('timeout')) {
10 | errorMessage = 'Network timeout please refresh and try again!';
11 | } else {
12 | switch (status) {
13 | case 400:
14 | errorMessage = intl('err.400');
15 | break;
16 | case 401:
17 | errorMessage = intl('err.401');
18 | // 未登录 todo 跳登录
19 | break;
20 | case 403:
21 | errorMessage = intl('err.403');
22 | break;
23 | case 404:
24 | errorMessage = intl('err.404');
25 | break;
26 | case 500:
27 | errorMessage = intl('err.500');
28 | break;
29 | case 501:
30 | errorMessage = intl('err.501');
31 | break;
32 | case 503:
33 | errorMessage = intl('err.503');
34 | break;
35 | }
36 | }
37 |
38 | errorMessage = msg || errorMessage;
39 |
40 | if (errorMessage) {
41 | if (errorMessageMode === 'toast') {
42 | message(errorMessage);
43 | } else if (errorMessageMode === 'log') {
44 | // todo 上报
45 | console.log({
46 | status,
47 | msg: errorMessage,
48 | });
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/i18n.js:
--------------------------------------------------------------------------------
1 | import Cookies from 'js-cookie';
2 | import { get, memoize, template } from 'lodash';
3 | import { getParameterByName } from './utils';
4 |
5 | // const locales = {
6 | // 'en_US': './locales/en_US.json', // 英文
7 | // 'tw_TW': './locales/tw_TW.json', // 中文 - 繁体
8 | // 'es_ES': './locales/es_ES.json', // 西班牙
9 | // 'id_ID': './locales/id_ID.json', // 印尼
10 | // 'ar_AR': './locales/ar_AR.json', // 阿拉伯语
11 | // 'pl_PL': './locales/pl_PL.json', // 波兰语
12 | // 'tr_TR': './locales/tr_TR.json', // 土耳其语
13 | // 'fr_FR': './locales/fr_FR.json', // 法语
14 | // 'de_DE': './locales/de_DE.json', // 德语
15 | // 'th_TH': './locales/th_TH.json', // 泰语
16 | // 'nl_NL': './locales/nl_NL.json', // 荷兰语
17 | // 'pt_PT': './locales/pt_PT.json', // 葡萄牙语
18 | // 'ru_RU': './locales/ru_RU.json', // 俄语
19 | // 'vi_VN': './locales/vi_VN.json', // 越南语
20 | // 'sv_SE': './locales/sv_SE.json', // 瑞典语
21 | // 'ko_KR': './locales/ko_KR.json', // 韩语
22 | // };
23 |
24 | const currentLang =
25 | (typeof window !== 'undefined' &&
26 | getParameterByName('hl', window?.location?.search || '')) ||
27 | Cookies.get('clientCommonlanguage') ||
28 | 'en_US';
29 | const currentTemp = require(`./locales/${currentLang}.json`);
30 |
31 | function intl(key, values) {
32 | const compiled = template(get(currentTemp, key), {
33 | interpolate: /{([\s\S]+?)}/g, // {myVar}
34 | });
35 |
36 | return compiled(values);
37 | }
38 |
39 | export default memoize(intl);
40 |
--------------------------------------------------------------------------------
/src/hooks/UseRequest/Plugins/retryPlugin.js:
--------------------------------------------------------------------------------
1 | import { useRef } from 'react';
2 |
3 | const retryPlugin = (fetchInstance, { retryInterval, retryCount }) => {
4 | // eslint-disable-next-line react-hooks/rules-of-hooks
5 | const timerRef = useRef();
6 | // eslint-disable-next-line react-hooks/rules-of-hooks
7 | const countRef = useRef(0);
8 | // eslint-disable-next-line react-hooks/rules-of-hooks
9 | const triggerByRetry = useRef(false);
10 |
11 | if (!retryCount) {
12 | return {};
13 | }
14 |
15 | return {
16 | onBefore: () => {
17 | if (!triggerByRetry.current) {
18 | countRef.current = 0;
19 | }
20 | triggerByRetry.current = false;
21 |
22 | if (timerRef.current) {
23 | clearTimeout(timerRef.current);
24 | }
25 | },
26 | onSuccess: () => {
27 | countRef.current = 0;
28 | },
29 | onError: () => {
30 | countRef.current += 1;
31 | if (retryCount === -1 || countRef.current <= retryCount) {
32 | // Exponential backoff
33 | const timeout =
34 | retryInterval ?? Math.min(1000 * 2 ** countRef.current, 30000);
35 | timerRef.current = setTimeout(() => {
36 | triggerByRetry.current = true;
37 | fetchInstance.refresh();
38 | }, timeout);
39 | } else {
40 | countRef.current = 0;
41 | }
42 | },
43 | onCancel: () => {
44 | countRef.current = 0;
45 | if (timerRef.current) {
46 | clearTimeout(timerRef.current);
47 | }
48 | },
49 | };
50 | };
51 |
52 | export default retryPlugin;
53 |
--------------------------------------------------------------------------------
/src/Request/index.js:
--------------------------------------------------------------------------------
1 | import { deepMerge } from '../utils';
2 | import { CONTENTTYPE } from './constant';
3 | import { device } from './env';
4 | import { Request } from './request';
5 | import { transform } from './transform';
6 |
7 | // requestOptions
8 |
9 | function createRequest(opt = {}) {
10 | const requestInstance = new Request(
11 | deepMerge(
12 | {
13 | baseURL: '/',
14 | timeout: device === 'service' ? 5000 : 6000,
15 | headers: { 'Content-Type': CONTENTTYPE.FORM_URLENCODE },
16 | // 默认拦截器
17 | transform,
18 |
19 | // 配置项,下面的选项都可以在独立的接口请求中覆盖
20 | requestOptions: {
21 | // 是否携带公共参数 Boolean|Object
22 | publicParams: true,
23 |
24 | // 接口拼接地址
25 | urlPrefix: '',
26 |
27 | // 是否返回原生响应头 比如:需要获取响应头时使用该属性
28 | isReturnNativeResponse: false,
29 |
30 | // 消息提示类型 'none' 'toast' 'log'
31 | errorMessageMode: 'toast',
32 |
33 | // 忽略重复请求
34 | ignoreCancelToken: false,
35 |
36 | // 重试次数 Number|Boolean|Object {count: 3}
37 | retryRequest: 3,
38 |
39 | // 格式化请求响应数据
40 | transformData: null,
41 | },
42 | withCredentials: false,
43 | },
44 | opt || {},
45 | ),
46 | );
47 |
48 | return {
49 | request: requestInstance.request.bind(requestInstance),
50 | get: requestInstance.get.bind(requestInstance),
51 | post: requestInstance.post.bind(requestInstance),
52 | upload: requestInstance.upload.bind(requestInstance),
53 | put: requestInstance.put.bind(requestInstance),
54 | delete: requestInstance.delete.bind(requestInstance),
55 | setRequestHooks: requestInstance.setRequestHooks.bind(requestInstance),
56 | };
57 | }
58 |
59 | const request = createRequest();
60 |
61 | export default request;
62 |
63 | export { createRequest };
64 |
--------------------------------------------------------------------------------
/src/hooks/UseRequest/UseRequest.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from 'react';
2 | import useCreation from '../UseCreation';
3 | import useMemoizedFn from '../UseMemoizedFn';
4 | import useUpdate from '../UseUpdate';
5 | import Fetch from './Fetch';
6 |
7 | function useRequest(service, options, plugins) {
8 | const { manual = false, ...rest } = options || {};
9 |
10 | const serviceRef = useRef(service);
11 |
12 | const update = useUpdate();
13 |
14 | const fetchOptions = { manual, ...rest };
15 |
16 | const fetchInstance = useCreation(() => {
17 | /**
18 | * {loading, data, params, error}
19 | */
20 | const initState = plugins
21 | .map((p) => p?.onInit?.(fetchOptions))
22 | .filter(Boolean);
23 |
24 | return new Fetch(
25 | serviceRef,
26 | fetchOptions,
27 | update,
28 | Object.assign({}, ...initState),
29 | );
30 | }, []);
31 |
32 | fetchInstance.options = fetchOptions;
33 |
34 | fetchInstance.plugins = plugins.map((p) => p(fetchInstance, fetchOptions));
35 |
36 | useEffect(() => {
37 | if (!manual) {
38 | const params = fetchInstance.state.params || options.defaultParams || [];
39 | fetchInstance.run(...params);
40 | }
41 | return () => {
42 | fetchInstance.cancel();
43 | };
44 | }, []);
45 |
46 | return {
47 | loading: fetchInstance.state.loading,
48 | data: fetchInstance.state.data,
49 | error: fetchInstance.state.error,
50 | params: fetchInstance.state.params || [],
51 | cancel: useMemoizedFn(fetchInstance.cancel.bind(fetchInstance)),
52 | refresh: useMemoizedFn(fetchInstance.refresh.bind(fetchInstance)),
53 | refreshAsync: useMemoizedFn(fetchInstance.refreshAsync.bind(fetchInstance)),
54 | run: useMemoizedFn(fetchInstance.run.bind(fetchInstance)),
55 | runAsync: useMemoizedFn(fetchInstance.runAsync.bind(fetchInstance)),
56 | mutate: useMemoizedFn(fetchInstance.mutate.bind(fetchInstance)),
57 | };
58 | }
59 |
60 | export default useRequest;
61 |
--------------------------------------------------------------------------------
/src/components/message/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | class Message extends React.Component {
4 | constructor(props) {
5 | super(props);
6 | this.state = { visible: true };
7 | }
8 |
9 | componentDidMount() {
10 | const { duration = 3000, onClose } = this.props;
11 | this.timer = setTimeout(() => {
12 | this.setState({ visible: false }, onClose);
13 | }, duration);
14 | }
15 |
16 | componentWillUnmount() {
17 | clearTimeout(this.timer);
18 | }
19 | handleClose = () => {
20 | const { onClose } = this.props;
21 | this.setState({ visible: false }, onClose);
22 | };
23 | render() {
24 | const classNames = {
25 | position: 'fixed',
26 | top: ' 50%',
27 | left: '50%',
28 | transform: 'translate(-50%, -50%)',
29 | zIndex: 999,
30 | minWidth: ' 260px',
31 | maxWidth: '500px',
32 | wordWrap: 'break-word',
33 | textAlign: 'center',
34 | padding: ' 14px 20px',
35 | fontSize: '14px',
36 | color: '#fff',
37 | backgroundColor: 'rgba(67, 86, 104, 0.8)',
38 | borderRadius: ' 4px',
39 | };
40 | const { message } = this.props;
41 | const { visible } = this.state;
42 | return (
43 |
44 | {visible && (
45 |
46 | {message}
47 |
48 | )}
49 |
50 | );
51 | }
52 | }
53 |
54 | const message = (message, duration = 3000, onClose) => {
55 | const div = document.createElement('div');
56 | document.body.appendChild(div);
57 | const handleClose = () => {
58 | // eslint-disable-next-line react/no-deprecated
59 | ReactDOM.unmountComponentAtNode(div);
60 | document.body.removeChild(div);
61 | };
62 | // eslint-disable-next-line react/no-deprecated
63 | ReactDOM.render(
64 | ,
69 | div,
70 | );
71 | };
72 |
73 | export default message;
74 |
--------------------------------------------------------------------------------
/src/Request/canceler.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import qs from 'qs';
3 | import { isFunction } from '../utils';
4 |
5 | // 声明一个 Map 用于存储每个请求的标识 和 取消函数
6 | let pendingMap = new Map();
7 |
8 | const isAbortControllerSupport = () => {
9 | let abortControllerSupported = false;
10 |
11 | try {
12 | new AbortController();
13 | abortControllerSupported = true;
14 | } catch (e) {}
15 |
16 | return abortControllerSupported;
17 | };
18 |
19 | export const getPendingUrl = (config) =>
20 | [
21 | config.method,
22 | config.url,
23 | qs.stringify(config.data),
24 | qs.stringify(config.params),
25 | ].join('&');
26 |
27 | export class AxiosCanceler {
28 | /**
29 | * 添加请求
30 | * @param {Object} config
31 | */
32 | addPending(config) {
33 | this.removePending(config);
34 | const url = getPendingUrl(config);
35 | if (isAbortControllerSupport()) {
36 | const contraller = new AbortController();
37 | config.signal = config.signal || contraller.signal;
38 | if (!pendingMap.has(url)) {
39 | pendingMap.set(url, contraller);
40 | }
41 | } else {
42 | config.cancelToken =
43 | config.cancelToken ||
44 | new axios.CancelToken((cancel) => {
45 | if (!pendingMap.has(url)) {
46 | pendingMap.set(url, cancel);
47 | }
48 | });
49 | }
50 | }
51 |
52 | /**
53 | * @description: 清空所有pending
54 | */
55 | removeAllPending() {
56 | pendingMap.forEach((cancel) => {
57 | if (cancel) {
58 | isFunction(cancel?.abort) ? cancel.abort() : cancel();
59 | }
60 | });
61 | pendingMap.clear();
62 | }
63 |
64 | /**
65 | * 移除请求
66 | * @param {Object} config
67 | */
68 | removePending(config) {
69 | const url = getPendingUrl(config);
70 |
71 | if (pendingMap.has(url)) {
72 | // 如果在 pending 中存在当前请求标识,需要取消当前请求,并且移除
73 | const cancel = pendingMap.get(url);
74 | if (cancel) {
75 | isFunction(cancel?.abort) ? cancel.abort(url) : cancel(url);
76 | }
77 | pendingMap.delete(url);
78 | }
79 | }
80 |
81 | /**
82 | * @description: 重置
83 | */
84 | reset() {
85 | pendingMap = new Map();
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@bit/request",
3 | "version": "0.5.0",
4 | "description": "A react request library",
5 | "license": "MIT",
6 | "module": "dist/index.js",
7 | "types": "dist/index.d.ts",
8 | "files": [
9 | "dist"
10 | ],
11 | "scripts": {
12 | "build": "father build",
13 | "build:watch": "father dev",
14 | "dev": "dumi dev",
15 | "docs:build": "dumi build",
16 | "doctor": "father doctor",
17 | "lint": "npm run lint:es && npm run lint:css",
18 | "lint:css": "stylelint \"{src,test}/**/*.{css,less}\"",
19 | "lint:es": "eslint \"{src,test}/**/*.{js,jsx,ts,tsx}\"",
20 | "prepare": "husky install && dumi setup",
21 | "prepublishOnly": "father doctor && npm run build",
22 | "start": "npm run dev"
23 | },
24 | "commitlint": {
25 | "extends": [
26 | "@commitlint/config-conventional"
27 | ]
28 | },
29 | "lint-staged": {
30 | "*.{md,json}": [
31 | "prettier --write --no-error-on-unmatched-pattern"
32 | ],
33 | "*.{css,less}": [
34 | "stylelint --fix",
35 | "prettier --write"
36 | ],
37 | "*.{js,jsx}": [
38 | "eslint --fix",
39 | "prettier --write"
40 | ],
41 | "*.{ts,tsx}": [
42 | "eslint --fix",
43 | "prettier --parser=typescript --write"
44 | ]
45 | },
46 | "dependencies": {
47 | "axios": "^1.6.2",
48 | "js-cookie": "^3.0.5",
49 | "lodash": "^4.17.21",
50 | "qs": "^6.11.2",
51 | "query-string": "^8.1.0"
52 | },
53 | "devDependencies": {
54 | "@commitlint/cli": "^17.1.2",
55 | "@commitlint/config-conventional": "^17.1.0",
56 | "@types/react": "^18.0.0",
57 | "@types/react-dom": "^18.0.0",
58 | "@umijs/lint": "^4.0.0",
59 | "dumi": "^2.2.13",
60 | "eslint": "^8.23.0",
61 | "father": "^4.1.0",
62 | "husky": "^8.0.1",
63 | "lint-staged": "^13.0.3",
64 | "prettier": "^2.7.1",
65 | "prettier-plugin-organize-imports": "^3.0.0",
66 | "prettier-plugin-packagejson": "^2.2.18",
67 | "react": "^18.0.0",
68 | "react-dom": "^18.0.0",
69 | "stylelint": "^14.9.1"
70 | },
71 | "peerDependencies": {
72 | "react": ">=16.9.0",
73 | "react-dom": ">=16.9.0"
74 | },
75 | "publishConfig": {
76 | "access": "public"
77 | },
78 | "authors": [
79 | "https://github.com/nemo-crypto"
80 | ]
81 | }
82 |
--------------------------------------------------------------------------------
/src/hooks/UseRequest/Fetch.js:
--------------------------------------------------------------------------------
1 | class Fetch {
2 | plugins = [];
3 | count = 0;
4 | state = {
5 | loading: false,
6 | params: undefined,
7 | data: undefined,
8 | error: undefined,
9 | };
10 |
11 | constructor(serviceRef, options, update, initState) {
12 | this.state = {
13 | ...this.state,
14 | loading: !options.manual,
15 | ...initState,
16 | };
17 | this.serviceRef = serviceRef;
18 | this.update = update;
19 | }
20 |
21 | setState(state) {
22 | this.state = {
23 | ...this.state,
24 | ...state,
25 | };
26 | this.update();
27 | }
28 |
29 | runPluginHandler(event, ...rest) {
30 | const res = this.plugins.map((p) => p[event]?.(...rest)).filter(Boolean);
31 | return Object.assign({}, ...res);
32 | }
33 |
34 | async runAsync(...params) {
35 | this.count += 1;
36 | const currentCount = this.count;
37 |
38 | const {
39 | stopNow = false,
40 | returnNow = false,
41 | ...state
42 | } = this.runPluginHandler('onBefore', params);
43 |
44 | // stop request
45 | if (stopNow) {
46 | return new Promise(() => {});
47 | }
48 |
49 | this.setState({
50 | loading: true,
51 | params,
52 | ...state,
53 | });
54 |
55 | // return now
56 | if (returnNow) {
57 | return Promise.resolve(state.data);
58 | }
59 |
60 | this.options.onBefore?.(params);
61 |
62 | try {
63 | // replace service
64 | let { servicePromise } = this.runPluginHandler(
65 | 'onRequest',
66 | this.serviceRef.current,
67 | params,
68 | );
69 |
70 | if (!servicePromise) {
71 | servicePromise = this.serviceRef.current(...params);
72 | }
73 |
74 | const res = await servicePromise;
75 |
76 | if (currentCount !== this.count) {
77 | // prevent run.then when request is canceled
78 | return new Promise(() => {});
79 | }
80 |
81 | // const formattedResult = this.options.formatResultRef.current ? this.options.formatResultRef.current(res) : res;
82 |
83 | this.setState({
84 | data: res,
85 | error: undefined,
86 | loading: false,
87 | });
88 |
89 | this.options.onSuccess?.(res, params);
90 | this.runPluginHandler('onSuccess', res, params);
91 |
92 | this.options.onFinally?.(params, res, undefined);
93 |
94 | if (currentCount === this.count) {
95 | this.runPluginHandler('onFinally', params, res, undefined);
96 | }
97 |
98 | return res;
99 | } catch (error) {
100 | if (currentCount !== this.count) {
101 | // prevent run.then when request is canceled
102 | return new Promise(() => {});
103 | }
104 |
105 | this.setState({
106 | error,
107 | loading: false,
108 | });
109 |
110 | this.options.onError?.(error, params);
111 | this.runPluginHandler('onError', error, params);
112 |
113 | this.options.onFinally?.(params, undefined, error);
114 |
115 | if (currentCount === this.count) {
116 | this.runPluginHandler('onFinally', params, undefined, error);
117 | }
118 |
119 | throw error;
120 | }
121 | }
122 |
123 | run(...params) {
124 | this.runAsync(...params).catch((error) => {
125 | if (!this.options?.onError) {
126 | console.error(error);
127 | }
128 | });
129 | }
130 |
131 | cancel() {
132 | this.count += 1;
133 | this.setState({
134 | loading: false,
135 | });
136 |
137 | this.runPluginHandler('onCancel');
138 | }
139 |
140 | refresh() {
141 | this.run(...(this.state.params || []));
142 | }
143 |
144 | refreshAsync() {
145 | return this.runAsync(...(this.state.params || []));
146 | }
147 |
148 | mutate(data) {
149 | const targetData = isFunction(data) ? data(this.state.data) : data;
150 | this.runPluginHandler('onMutate', targetData);
151 | this.setState({
152 | data: targetData,
153 | });
154 | }
155 | }
156 |
157 | export default Fetch;
158 |
--------------------------------------------------------------------------------
/src/hooks/UseRequest/Plugins/catchPlugin.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from 'react';
2 | import useCreation from '../../UseCreation';
3 | import { getCache, setCache } from '../../utils/cache';
4 | import { getCachePromise, setCachePromise } from '../../utils/cachePromise';
5 | import { subscribe, trigger } from '../../utils/cacheSubscribe';
6 |
7 | const cachePlugin = (
8 | fetchInstance,
9 | {
10 | cacheKey,
11 | cacheTime = 5 * 60 * 1000,
12 | staleTime = 0,
13 | setCache: customSetCache,
14 | getCache: customGetCache,
15 | },
16 | ) => {
17 | // eslint-disable-next-line react-hooks/rules-of-hooks
18 | const unSubscribeRef = useRef();
19 | // eslint-disable-next-line react-hooks/rules-of-hooks
20 | const currentPromiseRef = useRef();
21 |
22 | const _setCache = (key, cachedData) => {
23 | if (customSetCache) {
24 | customSetCache(cachedData);
25 | } else {
26 | setCache(key, cacheTime, cachedData);
27 | }
28 | trigger(key, cachedData.data);
29 | };
30 |
31 | const _getCache = (key, params) => {
32 | if (customGetCache) {
33 | return customGetCache(params);
34 | }
35 | return getCache(key);
36 | };
37 | // eslint-disable-next-line react-hooks/rules-of-hooks
38 | useCreation(() => {
39 | if (!cacheKey) {
40 | return;
41 | }
42 |
43 | // get data from cache when init
44 | const cacheData = _getCache(cacheKey);
45 | if (cacheData && Object.hasOwnProperty.call(cacheData, 'data')) {
46 | fetchInstance.state.data = cacheData.data;
47 | fetchInstance.state.params = cacheData.params;
48 | if (
49 | staleTime === -1 ||
50 | new Date().getTime() - cacheData.time <= staleTime
51 | ) {
52 | fetchInstance.state.loading = false;
53 | }
54 | }
55 |
56 | // subscribe same cachekey update, trigger update
57 | unSubscribeRef.current = subscribe(cacheKey, (data) => {
58 | fetchInstance.setState({ data });
59 | });
60 | }, []);
61 | // eslint-disable-next-line react-hooks/rules-of-hooks
62 | useEffect(() => {
63 | return () => {
64 | unSubscribeRef.current?.();
65 | };
66 | }, []);
67 |
68 | if (!cacheKey) {
69 | return {};
70 | }
71 |
72 | return {
73 | onBefore: (params) => {
74 | const cacheData = _getCache(cacheKey, params);
75 |
76 | if (!cacheData || !Object.hasOwnProperty.call(cacheData, 'data')) {
77 | return {};
78 | }
79 |
80 | // If the data is fresh, stop request
81 | if (
82 | staleTime === -1 ||
83 | new Date().getTime() - cacheData.time <= staleTime
84 | ) {
85 | return {
86 | loading: false,
87 | data: cacheData?.data,
88 | error: undefined,
89 | returnNow: true,
90 | };
91 | } else {
92 | // If the data is stale, return data, and request continue
93 | return {
94 | data: cacheData?.data,
95 | error: undefined,
96 | };
97 | }
98 | },
99 | onRequest: (service, args) => {
100 | let servicePromise = getCachePromise(cacheKey);
101 |
102 | // If has servicePromise, and is not trigger by self, then use it
103 | if (servicePromise && servicePromise !== currentPromiseRef.current) {
104 | return { servicePromise };
105 | }
106 |
107 | servicePromise = service(...args);
108 | currentPromiseRef.current = servicePromise;
109 | setCachePromise(cacheKey, servicePromise);
110 | return { servicePromise };
111 | },
112 | onSuccess: (data, params) => {
113 | if (cacheKey) {
114 | // cancel subscribe, avoid trgger self
115 | unSubscribeRef.current?.();
116 | _setCache(cacheKey, {
117 | data,
118 | params,
119 | time: new Date().getTime(),
120 | });
121 | // resubscribe
122 | unSubscribeRef.current = subscribe(cacheKey, (d) => {
123 | fetchInstance.setState({ data: d });
124 | });
125 | }
126 | },
127 | onMutate: (data) => {
128 | if (cacheKey) {
129 | // cancel subscribe, avoid trigger self
130 | unSubscribeRef.current?.();
131 | _setCache(cacheKey, {
132 | data,
133 | params: fetchInstance.state.params,
134 | time: new Date().getTime(),
135 | });
136 | // resubscribe
137 | unSubscribeRef.current = subscribe(cacheKey, (d) => {
138 | fetchInstance.setState({ data: d });
139 | });
140 | }
141 | },
142 | };
143 | };
144 |
145 | export default cachePlugin;
146 |
--------------------------------------------------------------------------------
/src/Request/transform.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import Cookies from 'js-cookie';
3 | import { isBoolean, isNumber, isObject } from 'lodash';
4 | import queryString from 'query-string';
5 | import { isFunction } from '../utils';
6 | import { checkStatus } from './checkStatus';
7 | import { CONTENTTYPE, token as token_url } from './constant';
8 | import { device } from './env';
9 | import { AxiosRetry } from './retry';
10 |
11 | /**
12 | * @description: 数据处理,方便区分多种处理方式
13 | */
14 | export const transform = {
15 | /**
16 | * @description: 处理请求数据。直接返回
17 | */
18 | afterRequestHook: (res, options) => {
19 | const { isReturnNativeResponse } = options;
20 |
21 | // 是否返回原生响应头 比如:需要获取响应头时使用该属性
22 | if (isReturnNativeResponse) {
23 | return res;
24 | }
25 |
26 | let { data, config } = res || {};
27 |
28 | if (!data) {
29 | throw new Error('request error!');
30 | }
31 |
32 | const { transformData } = options;
33 |
34 | if (transformData && isFunction(transformData)) {
35 | try {
36 | data = transformData(data);
37 | } catch (err) {
38 | throw new Error(err || new Error('request error!'));
39 | }
40 | }
41 | // 除json等文件请求以外的响应数据如果没有code,提示响应数据格式不符合标准
42 | if (!('code' in data) && !/^[\s\S]*\.[\s\S]*$/.test(config.url)) {
43 | console.warn(
44 | '请求返回的数据格式不符合标准,请添加transformData修改或让服务端调整响应数据格式。否则下次升级版本将直接报错',
45 | );
46 | }
47 |
48 | return data || null;
49 | },
50 |
51 | /**
52 | * @description: 请求之前处理config
53 | */
54 | beforeRequestHook: (config, options) => {
55 | const { data, method, headers = {} } = config || {};
56 | const { urlPrefix } = options;
57 |
58 | const contentType = headers['Content-Type'] || headers['content-type'];
59 |
60 | if (
61 | method?.toUpperCase() !== 'GET' &&
62 | contentType === CONTENTTYPE.FORM_URLENCODE &&
63 | data
64 | ) {
65 | config.data = queryString.stringify(data);
66 | }
67 |
68 | if (urlPrefix) {
69 | config.url = `${urlPrefix}${config.url}`;
70 | }
71 |
72 | return config;
73 | },
74 |
75 | /**
76 | * @description: 请求拦截器处理
77 | */
78 | requestInterceptors: (config, options) => {
79 | /**
80 | * 添加公共参数 publicParams Boolean | Object
81 | **/
82 | const withPublicParams =
83 | config.requestOptions?.publicParams !== 'undefined'
84 | ? config.requestOptions?.publicParams
85 | : options.requestOptions?.publicParams;
86 |
87 | let publicParams = {};
88 |
89 | if (isObject(withPublicParams)) {
90 | publicParams = withPublicParams;
91 | } else if (typeof withPublicParams === 'boolean' && withPublicParams) {
92 | let token = '';
93 | if (device !== 'service') {
94 | // 非app用户登录校验
95 | if (device !== 'app') {
96 | token = Cookies.get(token_url) || '';
97 | }
98 |
99 | if (token) {
100 | publicParams = {
101 | token, // 用户token
102 | appName: navigator.appName, // 浏览器名称
103 | appCodeName: navigator.appCodeName, // 浏览器代码名称
104 | appVersion: navigator.appVersion, // 浏览器版本号
105 | userAgent: navigator.userAgent, // 浏览器版本信息
106 | cookieEnabled: navigator.cookieEnabled, // 浏览器是否启用cookie
107 | platform: navigator.platform, // 客户端操作系统
108 | userLanguage: navigator.language, // 浏览器语言
109 | vendor: navigator.vendor, // 浏览器厂商
110 | onLine: navigator.onLine, // 浏览器是否需要连接网络
111 | product: navigator.product, // 浏览器产品名称
112 | productSub: navigator.productSub, // 浏览器产品其他信息
113 | mimeTypesLen: navigator.mimeTypes?.length, // 浏览器的MIME类型数量
114 | pluginsLen: navigator.plugins?.length, // 浏览器的插件数量
115 | javaEnbled: navigator.javaEnabled(), // 浏览器是否启用JAVA
116 | windowScreenWidth: window.screen.width, // 屏幕分辨率 - 宽
117 | windowScreenHeight: window.screen.height, // 屏幕分辨率 - 高
118 | windowColorDepth: window.screen.colorDepth, // 屏幕色彩位数
119 | };
120 | }
121 | }
122 | }
123 |
124 | // 发送公共参数
125 | config.params = config.params
126 | ? Object.assign({}, publicParams, config.params)
127 | : publicParams;
128 |
129 | return config;
130 | },
131 |
132 | /**
133 | * @description: 请求错误拦截器处理
134 | */
135 |
136 | requestInterceptorsCatch: (error) => {
137 | return Promise.reject(error);
138 | },
139 |
140 | /**
141 | * @description: 响应拦截器处理
142 | */
143 | responseInterceptors: (res) => {
144 | // 请求失败 2xx
145 | if (res.status !== 200) {
146 | return Promise.reject(res);
147 | }
148 | return res;
149 | },
150 |
151 | /**
152 | * @description: 响应错误处理
153 | */
154 | responseInterceptorsCatch: (axiosInstance, error) => {
155 | const { response, config = {} } = error || {};
156 |
157 | // 请求是被取消的
158 | if (axios.isCancel(error)) {
159 | return Promise.reject(error);
160 | }
161 |
162 | if (device !== 'service') {
163 | // 统一状态码处理
164 | if (!config?.__retryCount) {
165 | checkStatus(
166 | response?.status,
167 | response?.data?.message || '',
168 | config || {},
169 | error,
170 | );
171 | }
172 |
173 | // 自动重试机制 只针对client端的GET请求
174 | const retryRequest = new AxiosRetry();
175 | const retryRequestTimes = config?.requestOptions?.retryRequest; // Number || Boolean || Object
176 | const needRetry = isBoolean(retryRequestTimes)
177 | ? retryRequestTimes
178 | : isNumber(retryRequestTimes)
179 | ? retryRequestTimes > 0
180 | : true;
181 | config?.method?.toUpperCase() === 'GET' &&
182 | needRetry &&
183 | retryRequest.retry(axiosInstance, error);
184 | }
185 |
186 | // 忽略重试error
187 | if (config?.__retryCount) {
188 | return;
189 | }
190 | return Promise.reject(error);
191 | },
192 | };
193 |
--------------------------------------------------------------------------------
/src/Request/request.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import { cloneDeep } from 'lodash';
3 | import { isFunction } from '../utils';
4 | import { AxiosCanceler } from './canceler';
5 | import { CONTENTTYPE } from './constant';
6 | import { device } from './env';
7 |
8 | /**
9 | * @description: axios模块
10 | */
11 | export class Request {
12 | constructor(options) {
13 | this.options = options;
14 | this.axiosInstance = axios.create(options);
15 | this.setupInterceptors();
16 | }
17 |
18 | getAxios() {
19 | return this.axiosInstance;
20 | }
21 |
22 | /**
23 | * @description: 重新配置axios
24 | */
25 | configAxios(config) {
26 | if (!this.axiosInstance) {
27 | return;
28 | }
29 | this.createAxios(config);
30 | }
31 |
32 | /**
33 | * @description: 添加自定义拦截器
34 | */
35 |
36 | setRequestHooks({ beforeRequestHook, afterRequestHook }) {
37 | if (this.axiosInstance) {
38 | this.axiosInstance.interceptors.request.use(
39 | (config) => {
40 | if (beforeRequestHook && isFunction(beforeRequestHook)) {
41 | config = beforeRequestHook(config, this.options);
42 | }
43 |
44 | return config;
45 | },
46 | (error) => {
47 | return Promise.reject(error);
48 | },
49 | );
50 | this.axiosInstance.interceptors.response.use(
51 | (res) => {
52 | if (afterRequestHook && isFunction(afterRequestHook)) {
53 | res = afterRequestHook(res);
54 | }
55 |
56 | return res;
57 | },
58 | (error) => {
59 | return Promise.reject(error);
60 | },
61 | );
62 | }
63 | }
64 |
65 | /**
66 | * @description: 设置通用header
67 | */
68 | setHeader(headers) {
69 | if (!this.axiosInstance) {
70 | return;
71 | }
72 | Object.assign(this.axiosInstance.defaults.headers, headers);
73 | }
74 |
75 | /**
76 | * @description: 创建axios实例
77 | */
78 | createAxios(config) {
79 | this.axiosInstance = axios.create(config);
80 | }
81 |
82 | /**
83 | * @description: 拦截器配置
84 | */
85 | setupInterceptors() {
86 | const { transform } = this.options;
87 | if (!transform) {
88 | return;
89 | }
90 | const {
91 | requestInterceptors,
92 | requestInterceptorsCatch,
93 | responseInterceptors,
94 | responseInterceptorsCatch,
95 | } = transform;
96 |
97 | const axiosCanceler = new AxiosCanceler();
98 |
99 | // 请求拦截器配置处理
100 | this.axiosInstance.interceptors.request.use(
101 | (config) => {
102 | // 是否忽略重复请求 优先取单个请求里的配置
103 | const ignoreCancel =
104 | config.requestOptions?.ignoreCancelToken !== 'undefined'
105 | ? config.requestOptions?.ignoreCancelToken
106 | : this.options.requestOptions?.ignoreCancelToken;
107 | !ignoreCancel && axiosCanceler.addPending(config);
108 |
109 | if (requestInterceptors && isFunction(requestInterceptors)) {
110 | config = requestInterceptors(config, this.options);
111 | }
112 |
113 | return config;
114 | },
115 | (error) => {
116 | if (requestInterceptorsCatch && isFunction(requestInterceptorsCatch)) {
117 | return requestInterceptorsCatch(error);
118 | }
119 | },
120 | );
121 |
122 | // 响应结果拦截器处理
123 | this.axiosInstance.interceptors.response.use(
124 | (res) => {
125 | res && axiosCanceler.removePending(res.config);
126 |
127 | if (responseInterceptors && isFunction(responseInterceptors)) {
128 | res = responseInterceptors(res);
129 | }
130 |
131 | return res;
132 | },
133 | (error) => {
134 | if (
135 | responseInterceptorsCatch &&
136 | isFunction(responseInterceptorsCatch)
137 | ) {
138 | return responseInterceptorsCatch(this.axiosInstance, error);
139 | }
140 | },
141 | );
142 | }
143 |
144 | /**
145 | * @description: 请求方法
146 | * options: requestOptions
147 | */
148 | request(config, options) {
149 | let conf = cloneDeep(config);
150 |
151 | const { requestOptions, transform } = this.options;
152 |
153 | // 单个请求和初始化requestOptions合并
154 | const mergedOpt = Object.assign({}, requestOptions, options);
155 | const { beforeRequestHook, afterRequestHook } = transform || {};
156 | if (beforeRequestHook && isFunction(beforeRequestHook)) {
157 | conf = beforeRequestHook(conf, mergedOpt);
158 | }
159 |
160 | //这里重新 赋值成最新的配置
161 | conf.requestOptions = mergedOpt;
162 |
163 | return new Promise((resolve, reject) => {
164 | this.axiosInstance
165 | .request(conf)
166 | .then((res) => {
167 | let ret = res;
168 | if (afterRequestHook && isFunction(afterRequestHook)) {
169 | try {
170 | ret = afterRequestHook(res, mergedOpt);
171 | } catch (err) {
172 | return reject(err || new Error('request error!'));
173 | }
174 | }
175 |
176 | resolve(ret);
177 | })
178 | .catch((e) => {
179 | // 取消请求
180 | if (axios.isCancel(e)) {
181 | return console.warn(e);
182 | }
183 | reject(e);
184 | });
185 | });
186 | }
187 |
188 | get(url, params, config, options) {
189 | return this.request({ url, params, ...config, method: 'GET' }, options);
190 | }
191 |
192 | post(url, data, config, options) {
193 | return this.request(
194 | {
195 | url,
196 | data,
197 | ...config,
198 | headers: {
199 | ...{ 'Content-Type': CONTENTTYPE.JSON },
200 | ...(config?.headers || {}),
201 | },
202 | method: 'POST',
203 | },
204 | options,
205 | );
206 | }
207 |
208 | put(url, config, options) {
209 | return this.request({ url, ...config, method: 'PUT' }, options);
210 | }
211 |
212 | delete(url, config, options) {
213 | return this.request({ url, ...config, method: 'DELETE' }, options);
214 | }
215 |
216 | upload(url, params, config) {
217 | if (device === 'service' || !FormData) {
218 | return Promise.reject('仅支持客户端上传');
219 | }
220 |
221 | const formData = new FormData();
222 | const fileName = params.name || 'file';
223 | if (params.fileName) {
224 | formData.append(fileName, params.file, params.fileName);
225 | } else {
226 | formData.append(fileName, params.file);
227 | }
228 |
229 | if (params.data) {
230 | Object.keys(params.data).forEach((key) => {
231 | const value = params.data[key];
232 | if (Array.isArray(value)) {
233 | value.forEach((item) => {
234 | formData.append(`${key}[]`, item);
235 | });
236 | return;
237 | }
238 |
239 | formData.append(key, params.data[key]);
240 | });
241 | }
242 |
243 | return this.request(
244 | {
245 | url,
246 | ...config,
247 | method: 'POST',
248 | data: formData,
249 | headers: {
250 | 'Content-type': CONTENTTYPE.FORM_DATA,
251 | },
252 | },
253 | {
254 | ignoreCancelToken: true,
255 | },
256 | );
257 | }
258 | }
259 |
--------------------------------------------------------------------------------
/src/Request/index.md:
--------------------------------------------------------------------------------
1 | # request
2 |
3 | ```jsx
4 | import { request } from '@bit/request';
5 | import { useEffect, useState } from 'react';
6 |
7 | export default () => {
8 | const [json, setJson] = useState(null);
9 | const [data, setData] = useState(null);
10 | const [data1, setPostData] = useState(null);
11 | const [data2, setPostFormData] = useState(null);
12 |
13 | const fetchJson = () => {
14 | request
15 | .get('/json/languse/bitruedemand/assetexport/en_US.json')
16 | .then((res) => {
17 | setJson(JSON.stringify(res));
18 | });
19 | };
20 |
21 | const fetchData = () => {
22 | request
23 | .get('/api/exchange-web/web/activity/getUserLoginStatus', { test: true })
24 | .then((res) => {
25 | setData(JSON.stringify(res));
26 | });
27 | };
28 |
29 | const postData = () => {
30 | request
31 | .post('/feapi/fe-co-api/common/public_info', {
32 | securityInfo: '',
33 | type: '1,2,3',
34 | uaTime: new Date(),
35 | })
36 | .then((res) => {
37 | setPostData(JSON.stringify(res));
38 | });
39 | };
40 |
41 | const postFormData = () => {
42 | request
43 | .post(
44 | '/feapi/fe-co-api/common/public_info',
45 | {
46 | securityInfo: '',
47 | type: '1,2,3',
48 | uaTime: new Date(),
49 | },
50 | {
51 | headers: {
52 | 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
53 | },
54 | },
55 | )
56 | .then((res) => {
57 | setPostFormData(JSON.stringify(res));
58 | });
59 | };
60 |
61 | return (
62 |
63 |
64 |
65 | json:
66 |
67 | {json}
68 |
69 |
70 |
71 |
72 | data:
73 |
74 | {data}
75 |
76 |
77 |
78 |
79 | data:
80 |
81 | {data1}
82 |
83 |
84 |
85 |
86 | data:
87 |
88 | {data2}
89 |
90 |
91 | );
92 | };
93 | ```
94 |
95 | ## request 请求方法
96 |
97 | `get(url, params, config, options)`
98 |
99 | `post(url, data, config, options)`
100 |
101 | `put(url, config, options)`
102 |
103 | `delete(url, config, options)`
104 |
105 | `upload(url, params, config)` 上传 params 中必须包含 file 属性
106 |
107 | ## options
108 |
109 | | 参数 | 说明 | 类型 | 默认值 |
110 | | ---------------------- | --------------------- | -------------------- | ----------------- |
111 | | publicParams | 请求携带的公共参数 | Boolean/Object | 见下 publicparams |
112 | | urlPrefix | 请求前缀 | String | '' |
113 | | isReturnNativeResponse | 是否返回原生 response | Boolean | false |
114 | | errorMessageMode | 错误消息提示类型 | 'toast'/'log'/'none' | 'none' |
115 | | ignoreCancelToken | 允许重复请求 | Boolean | false |
116 | | retryRequest | 失败是否重试 | Boolean/Number | true |
117 |
118 | ## 公共业务参数 publicparams
119 |
120 | 浏览器环境登录用户默认携带设备参数
121 |
122 | | 参数 | 含义 | 默认值 |
123 | | ------------------ | ---------------------- | -------------------------- |
124 | | token | token | token_url |
125 | | appName | 浏览器名称 | navigator.appName |
126 | | appCodeName | 浏览器代码名称 | navigator.appCodeName |
127 | | appVersion | 浏览器版本号 | navigator.appVersion |
128 | | userAgent | 浏览器版本信息 | navigator.userAgent |
129 | | cookieEnabled | 浏览器是否启用 cookie | navigator.cookieEnabled |
130 | | platform | 客户端操作系统 | navigator.platform |
131 | | userLanguage | 浏览器语言 | navigator.language |
132 | | vendor | 浏览器厂商 | navigator.vendor |
133 | | onLine | 浏览器是否连接网络 | navigator.onLine |
134 | | product | 浏览器产品名称 | navigator.product |
135 | | productSub | 浏览器产品其他信息 | navigator.productSub |
136 | | mimeTypesLen | 浏览器的 MIME 类型数量 | navigator.mimeTypes.length |
137 | | pluginsLen | 浏览器的插件数量 | navigator.plugins.length |
138 | | javaEnbled | 浏览器是否启用 JAVA | navigator.javaEnabled() |
139 | | windowScreenWidth | 屏幕分辨率 - 宽 | window.screen.width |
140 | | windowScreenHeight | 屏幕分辨率 - 高 | window.screen.width |
141 | | windowColorDepth | 屏幕色彩位数 | window.screen.colorDepth |
142 |
143 | ## 自定义拦截器
144 |
145 | `request.setRequestHooks()` 可以设置自定义 hooks;
146 |
147 | | 自定义 hook | trigger | 参数 | 返回值 |
148 | | ----------------- | --------------- | ----------------------------------- | --------------- |
149 | | beforeRequestHook | request 发起前 | (config, option) => {return config} | 返回最新 config |
150 | | afterRequestHook | response 返回后 | (res, option) => {return res} | 返回最终 res |
151 |
152 | ```jsx
153 | import { request } from '@bit/request';
154 | import { useEffect, useState } from 'react';
155 | export default () => {
156 | const [hooks, setHooks] = useState(false);
157 | const [json, setJson] = useState(null);
158 |
159 | const fetchJson = () => {
160 | request
161 | .get('/json/languse/bitruedemand/assetexport/en_US.json')
162 | .then((res) => {
163 | setJson(JSON.stringify(res));
164 | });
165 | };
166 |
167 | const setHook = () => {
168 | setHooks(true);
169 | const beforeRequestHook = (config, options) => {
170 | config.cusHook = true;
171 | return config;
172 | };
173 | const afterRequestHook = (res) => {
174 | res.cusHook = true;
175 | return res;
176 | };
177 |
178 | request.setRequestHooks({
179 | beforeRequestHook,
180 | afterRequestHook,
181 | });
182 | };
183 |
184 | return (
185 |
186 |
187 |
188 |
189 | json:
190 |
191 | {json}
192 |
193 |
194 | );
195 | };
196 | ```
197 |
198 | ## response 数据格式
199 |
200 | 统一数据格式 `{code:0, data, msg}`,对于响应数据格式不符合要求的接口,请联系服务端调整接口数据格式,或者通过 transformData 手动更改响应数据
201 |
202 | ## transformData
203 |
204 | ```jsx
205 | import { request } from '@bit/request';
206 | import { useEffect, useState } from 'react';
207 | export default () => {
208 | const [hooks, setHooks] = useState(false);
209 | const [json, setJson] = useState(null);
210 |
211 | const fetchJson = () => {
212 | request
213 | .get('/json/languse/bitruedemand/assetexport/en_US.json', null, null, {
214 | transformData: (res) => {
215 | return {
216 | code: 0,
217 | data: res,
218 | message: 'success',
219 | };
220 | },
221 | })
222 | .then((res) => {
223 | setJson(JSON.stringify(res));
224 | })
225 | .catch((err) => {});
226 | };
227 |
228 | return (
229 |
230 |
231 |
232 | json:
233 |
234 | {json}
235 |
236 |
237 | );
238 | };
239 | ```
240 |
241 | ## 上传
242 |
243 | ```jsx
244 | import { request } from '@bit/request';
245 | import { useRef } from 'react';
246 | export default () => {
247 | const fileRef = useRef();
248 |
249 | const handleUpload = (e) => {
250 | e.preventDefault();
251 | const file = fileRef.current.files[0];
252 | if (file) {
253 | request
254 | .upload('/api/exchange-web/web/userCenterGoogle/handleFileUpload', {
255 | file: file,
256 | })
257 | .then((res) => {
258 | console.log(res);
259 | });
260 | }
261 | };
262 |
263 | return (
264 |
265 |
269 |
270 | );
271 | };
272 | ```
273 |
274 | ## 异常流
275 |
276 | ```jsx
277 | import { request } from '@bit/request';
278 | import { useEffect, useState } from 'react';
279 |
280 | export default () => {
281 | const [errorMode, setErrorMode] = useState('none');
282 | const [error, setError] = useState(null);
283 |
284 | const fetchJson = () => {
285 | request
286 | .get('/json/languse/bitruedemand/assetexport/en_US1.json', null, null, {
287 | errorMessageMode: errorMode,
288 | })
289 | .then((res) => {
290 | // ...
291 | })
292 | .catch((err) => {
293 | setError(err);
294 | });
295 | };
296 |
297 | const fetchData = () => {
298 | request
299 | .get('/api/exchange-web/web/activity/notgetUserLoginStatus', null, null, {
300 | errorMessageMode: errorMode,
301 | })
302 | .then((res) => {
303 | // ...
304 | })
305 | .catch((err) => {
306 | setError(err);
307 | });
308 | };
309 |
310 | const postData = () => {
311 | request
312 | .post(
313 | '/feapi/fe-co-api/common/public_info1',
314 | {
315 | securityInfo: '',
316 | type: '1,2,3',
317 | uaTime: new Date(),
318 | },
319 | null,
320 | { errorMessageMode: errorMode },
321 | )
322 | .then((res) => {
323 | // ...
324 | })
325 | .catch((err) => {
326 | setError(err);
327 | });
328 | };
329 |
330 | const switchErrorMode = (mode) => () => setErrorMode(mode);
331 |
332 | return (
333 |
334 |
335 |
errorMode: {errorMode}
336 |
337 |
errorStatus: {JSON.stringify(error)}
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 | );
357 | };
358 | ```
359 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Development
2 |
3 | ```bash
4 | # install dependencies
5 | $ yarn install
6 |
7 | # develop library by docs demo
8 | $ yarn start
9 |
10 | # build library source code
11 | $ yarn run build
12 |
13 | # build library source code in watch mode
14 | $ yarn run build:watch
15 |
16 | # build docs
17 | $ yarn run docs:build
18 |
19 | # check your project for potential problems
20 | $ yarn run doctor
21 | ```
22 |
23 | # request
24 |
25 | ```jsx
26 | import { request } from '@bit/request';
27 | import { useEffect, useState } from 'react';
28 |
29 | export default () => {
30 | const [json, setJson] = useState(null);
31 | const [data, setData] = useState(null);
32 | const [data1, setPostData] = useState(null);
33 | const [data2, setPostFormData] = useState(null);
34 |
35 | const fetchJson = () => {
36 | request
37 | .get('/json/languse/test/assetexport/en_US.json')
38 | .then((res) => {
39 | setJson(JSON.stringify(res));
40 | });
41 | };
42 |
43 | const fetchData = () => {
44 | request
45 | .get('/api/exchange-web/web/activity/getUserLoginStatus', { test: true })
46 | .then((res) => {
47 | setData(JSON.stringify(res));
48 | });
49 | };
50 |
51 | const postData = () => {
52 | request
53 | .post('/feapi/fe-co-api/common/public_info', {
54 | securityInfo: '',
55 | type: '1,2,3',
56 | uaTime: new Date(),
57 | })
58 | .then((res) => {
59 | setPostData(JSON.stringify(res));
60 | });
61 | };
62 |
63 | const postFormData = () => {
64 | request
65 | .post(
66 | '/feapi/fe-co-api/common/public_info',
67 | {
68 | securityInfo: '',
69 | type: '1,2,3',
70 | uaTime: new Date(),
71 | },
72 | {
73 | headers: {
74 | 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
75 | },
76 | },
77 | )
78 | .then((res) => {
79 | setPostFormData(JSON.stringify(res));
80 | });
81 | };
82 |
83 | return (
84 |
85 |
86 |
87 | json:
88 |
89 | {json}
90 |
91 |
92 |
93 |
94 | data:
95 |
96 | {data}
97 |
98 |
99 |
100 |
101 | data:
102 |
103 | {data1}
104 |
105 |
106 |
107 |
108 | data:
109 |
110 | {data2}
111 |
112 |
113 | );
114 | };
115 | ```
116 |
117 | ## request 请求方法
118 |
119 | `get(url, params, config, options)`
120 |
121 | `post(url, data, config, options)`
122 |
123 | `put(url, config, options)`
124 |
125 | `delete(url, config, options)`
126 |
127 | `upload(url, params, config)` 上传 params 中必须包含 file 属性
128 |
129 | ## options
130 |
131 | | 参数 | 说明 | 类型 | 默认值 |
132 | | ---------------------- | --------------------- | -------------------- | ----------------- |
133 | | publicParams | 请求携带的公共参数 | Boolean/Object | 见下 publicparams |
134 | | urlPrefix | 请求前缀 | String | '' |
135 | | isReturnNativeResponse | 是否返回原生 response | Boolean | false |
136 | | errorMessageMode | 错误消息提示类型 | 'toast'/'log'/'none' | 'none' |
137 | | ignoreCancelToken | 允许重复请求 | Boolean | false |
138 | | retryRequest | 失败是否重试 | Boolean/Number | true |
139 |
140 | ## 公共业务参数 publicparams
141 |
142 | 浏览器环境登录用户默认携带设备参数
143 |
144 | | 参数 | 含义 | 默认值 |
145 | | ------------------ | ----------------------| -------------------------- |
146 | | token | token | token_url |
147 | | appName | 浏览器名称 | navigator.appName |
148 | | appCodeName | 浏览器代码名称 | navigator.appCodeName |
149 | | appVersion | 浏览器版本号 | navigator.appVersion |
150 | | userAgent | 浏览器版本信息 | navigator.userAgent |
151 | | cookieEnabled | 浏览器是否启用 cookie | navigator.cookieEnabled |
152 | | platform | 客户端操作系统 | navigator.platform |
153 | | userLanguage | 浏览器语言 | navigator.language |
154 | | vendor | 浏览器厂商 | navigator.vendor |
155 | | onLine | 浏览器是否连接网络 | navigator.onLine |
156 | | product | 浏览器产品名称 | navigator.product |
157 | | productSub | 浏览器产品其他信息 | navigator.productSub |
158 | | mimeTypesLen | 浏览器的 MIME 类型数量 | navigator.mimeTypes.length |
159 | | pluginsLen | 浏览器的插件数量 | navigator.plugins.length |
160 | | javaEnbled | 浏览器是否启用 JAVA | navigator.javaEnabled() |
161 | | windowScreenWidth | 屏幕分辨率 - 宽 | window.screen.width |
162 | | windowScreenHeight | 屏幕分辨率 - 高 | window.screen.width |
163 | | windowColorDepth | 屏幕色彩位数 | window.screen.colorDepth |
164 |
165 | ## 自定义拦截器
166 |
167 | `request.setRequestHooks()` 可以设置自定义 hooks;
168 |
169 | | 自定义 hook | trigger | 参数 | 返回值 |
170 | | ----------------- | ---------------| ----------------------------------- | ---------------|
171 | | beforeRequestHook | request 发起前 | (config, option) => {return config} | 返回最新 config |
172 | | afterRequestHook | response 返回后 | (res, option) => {return res} | 返回最终 res |
173 |
174 | ```jsx
175 | import { request } from '@bit/request';
176 | import { useEffect, useState } from 'react';
177 | export default () => {
178 | const [hooks, setHooks] = useState(false);
179 | const [json, setJson] = useState(null);
180 |
181 | const fetchJson = () => {
182 | request
183 | .get('/json/languse/tets/assetexport/en_US.json')
184 | .then((res) => {
185 | setJson(JSON.stringify(res));
186 | });
187 | };
188 |
189 | const setHook = () => {
190 | setHooks(true);
191 | const beforeRequestHook = (config, options) => {
192 | config.cusHook = true;
193 | return config;
194 | };
195 | const afterRequestHook = (res) => {
196 | res.cusHook = true;
197 | return res;
198 | };
199 |
200 | request.setRequestHooks({
201 | beforeRequestHook,
202 | afterRequestHook,
203 | });
204 | };
205 |
206 | return (
207 |
208 |
209 |
210 |
211 | json:
212 |
213 | {json}
214 |
215 |
216 | );
217 | };
218 | ```
219 |
220 | ## response 数据格式
221 |
222 | 统一数据格式 `{code:0, data, msg}`,对于响应数据格式不符合要求的接口,请联系服务端调整接口数据格式,或者通过 transformData 手动更改响应数据
223 |
224 | ## transformData
225 |
226 | ```jsx
227 | import { request } from '@bit/request';
228 | import { useEffect, useState } from 'react';
229 | export default () => {
230 | const [hooks, setHooks] = useState(false);
231 | const [json, setJson] = useState(null);
232 |
233 | const fetchJson = () => {
234 | request
235 | .get('/json/languse/test/assetexport/en_US.json', null, null, {
236 | transformData: (res) => {
237 | return {
238 | code: 0,
239 | data: res,
240 | message: 'success',
241 | };
242 | },
243 | })
244 | .then((res) => {
245 | setJson(JSON.stringify(res));
246 | })
247 | .catch((err) => {});
248 | };
249 |
250 | return (
251 |
252 |
253 |
254 | json:
255 |
256 | {json}
257 |
258 |
259 | );
260 | };
261 | ```
262 |
263 | ## 文件上传
264 |
265 | ```jsx
266 | import { request } from '@bit/request';
267 | import { useRef } from 'react';
268 | export default () => {
269 | const fileRef = useRef();
270 |
271 | const handleUpload = (e) => {
272 | e.preventDefault();
273 | const file = fileRef.current.files[0];
274 | if (file) {
275 | request
276 | .upload('/api/exchange-web/web/userCenterGoogle/handleFileUpload', {
277 | file: file,
278 | })
279 | .then((res) => {
280 | console.log(res);
281 | });
282 | }
283 | };
284 |
285 | return (
286 |
287 |
291 |
292 | );
293 | };
294 | ```
295 |
296 | ## 异常流处理
297 |
298 | ```jsx
299 | import { request } from '@bit/request';
300 | import { useEffect, useState } from 'react';
301 |
302 | export default () => {
303 | const [errorMode, setErrorMode] = useState('none');
304 | const [error, setError] = useState(null);
305 |
306 | const fetchJson = () => {
307 | request
308 | .get('/json/languse/test/assetexport/en_US1.json', null, null, {
309 | errorMessageMode: errorMode,
310 | })
311 | .then((res) => {
312 | // ...
313 | })
314 | .catch((err) => {
315 | setError(err);
316 | });
317 | };
318 |
319 | const fetchData = () => {
320 | request
321 | .get('/api/exchange-web/web/activity/notgetUserLoginStatus', null, null, {
322 | errorMessageMode: errorMode,
323 | })
324 | .then((res) => {
325 | // ...
326 | })
327 | .catch((err) => {
328 | setError(err);
329 | });
330 | };
331 |
332 | const postData = () => {
333 | request
334 | .post(
335 | '/feapi/fe-co-api/common/public_info1',
336 | {
337 | securityInfo: '',
338 | type: '1,2,3',
339 | uaTime: new Date(),
340 | },
341 | null,
342 | { errorMessageMode: errorMode },
343 | )
344 | .then((res) => {
345 | // ...
346 | })
347 | .catch((err) => {
348 | setError(err);
349 | });
350 | };
351 |
352 | const switchErrorMode = (mode) => () => setErrorMode(mode);
353 |
354 | return (
355 |
356 |
357 |
errorMode: {errorMode}
358 |
359 |
errorStatus: {JSON.stringify(error)}
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 | );
379 | };
380 | ```
381 |
382 |
--------------------------------------------------------------------------------
/src/hooks/index.md:
--------------------------------------------------------------------------------
1 | # useRequest
2 |
3 | api: `{data, error, loading, params, cancel, refresh, run } = useRequest(service, options)`;
4 |
5 | ## 基础用法
6 |
7 | 使用`useRequest(service, options)`来自动管理服务端状态;
8 |
9 | ```jsx
10 | /**
11 | * defaultShowCode: true
12 | */
13 | import { request, useRequest } from '@bit/request';
14 |
15 | const service = (...params) =>
16 | request.get('/api/exchange-web/web/activity/getUserLoginStatus', ...params);
17 |
18 | export default () => {
19 | const { data, loading, error } = useRequest(service);
20 | return (
21 |
22 | userName:{' '}
23 | {loading ? 'loading' : error ? error.message : JSON.stringify(data?.data)}
24 |
25 | );
26 | };
27 | ```
28 |
29 | 使用`useRequest([url, config, requestOptions], options)`;
30 |
31 | ```jsx
32 | /**
33 | * defaultShowCode: true
34 | */
35 | import { request, useRequest } from '@bit/request';
36 |
37 | export default () => {
38 | const { data, loading, error } = useRequest([
39 | '/api/exchange-web/web/coin/symbolForAsset',
40 | { method: 'GET', params: {} },
41 | ]);
42 |
43 | return (
44 |
45 | symbols:{' '}
46 | {loading
47 | ? 'loading'
48 | : error
49 | ? error.message
50 | : JSON.stringify(Object.keys(data))}
51 |
52 | );
53 | };
54 | ```
55 |
56 | ## 失败/成功回调
57 |
58 | 通过设置`options.onSuccess`和`options.onError`触发请求回调
59 |
60 | ```jsx
61 | /**
62 | * defaultShowCode: true
63 | */
64 | import { request, useRequest } from '@bit/request';
65 |
66 | const service = (...params) =>
67 | request.get('/api/exchange-web/web/activity/getUserLoginStatus', ...params);
68 |
69 | export default () => {
70 | const { data, loading, error, run } = useRequest(service, {
71 | onSuccess: (res, params) => {
72 | console.log(res, params);
73 | },
74 | onError: (error, params) => {
75 | console.log(error, params);
76 | },
77 | manual: true,
78 | });
79 |
80 | const fetchUserInfo = () => {
81 | if (loading) return;
82 | run();
83 | };
84 | return (
85 |
86 |
87 |
88 | userName:
89 | {loading
90 | ? 'loading'
91 | : error
92 | ? error.message
93 | : JSON.stringify(data?.data)}
94 |
95 |
96 | );
97 | };
98 | ```
99 |
100 | ## 手动触发
101 |
102 | 通过设置`options.manual = true`手动触发请求
103 |
104 | ```jsx
105 | /**
106 | * defaultShowCode: true
107 | */
108 | import { request, useRequest } from '@bit/request';
109 |
110 | const service = (...params) =>
111 | request.get('/api/exchange-web/web/activity/getUserLoginStatus', ...params);
112 |
113 | export default () => {
114 | const { data, loading, error, run } = useRequest(service, { manual: true });
115 |
116 | const fetchUserInfo = () => {
117 | if (loading) return;
118 | run();
119 | };
120 |
121 | return (
122 |
123 |
124 |
125 | userName:{' '}
126 | {loading
127 | ? 'loading'
128 | : error
129 | ? error.message
130 | : JSON.stringify(data?.data)}
131 |
132 |
133 | );
134 | };
135 | ```
136 |
137 | ## 延迟 loading
138 |
139 | 通过设置`options.loadingDeley = 3000`延迟 loading 加载效果, 适合无感知状态切换,减少用户等待的焦灼感
140 |
141 | ```jsx
142 | /**
143 | * defaultShowCode: true
144 | */
145 | import { useState, useRef, useCallback } from 'react';
146 | import { request, useRequest } from '@bit/request';
147 |
148 | const service = (...params) =>
149 | request.get('/api/exchange-web/web/activity/getUserLoginStatus', ...params);
150 |
151 | export default () => {
152 | const [loadingDeley, setLoadingDeley] = useState(false);
153 | const loadingDeleyRef = useRef(0);
154 | const { data, loading, error, run } = useRequest(service, {
155 | loadingDeley: loadingDeleyRef.current,
156 | manual: true,
157 | });
158 |
159 | const setDeley = useCallback(() => {
160 | setLoadingDeley(!loadingDeley);
161 | loadingDeleyRef.current = !loadingDeley ? 2000 : 0;
162 | }, [loadingDeley]);
163 |
164 | const runRequest = () => run();
165 |
166 | return (
167 |
168 |
171 |
172 |
173 |
174 | userName:{' '}
175 | {loading
176 | ? 'loading'
177 | : error
178 | ? error.message
179 | : JSON.stringify(data?.data)}
180 |
181 |
182 | );
183 | };
184 | ```
185 |
186 | ## 依赖更新自动加载
187 |
188 | 支持`option.ready`和`option.refreshDeps`两种依赖更新方式,设置了 ready 后,manual 请求需要在 ready 为 true 的状态下,run 才会发起请求。
189 | `option.refreshDeps`会在初始化和依赖项发生变化时触发请求。
190 |
191 | ```jsx
192 | /**
193 | * defaultShowCode: true
194 | */
195 | import { useState, useRef, useCallback } from 'react';
196 | import { request, useRequest } from '@bit/request';
197 |
198 | const service = (...params) =>
199 | request.get('/api/exchange-web/web/activity/getUserLoginStatus', ...params);
200 |
201 | export default () => {
202 | const [ready, setReady] = useState(false);
203 | const [count, setCount] = useState(0);
204 | const { data, loading, error } = useRequest(service, {
205 | ready: ready,
206 | });
207 |
208 | const {
209 | data: data1,
210 | loading: loading1,
211 | error: error1,
212 | } = useRequest(service, {
213 | ready: ready,
214 | refreshDeps: [count],
215 | });
216 |
217 | const handleReady = () => {
218 | setReady(true);
219 | };
220 |
221 | const handleCount = useCallback(() => {
222 | setCount(count + 1);
223 | }, [count]);
224 |
225 | return (
226 |
227 |
228 |
229 |
ready:{`${ready}`}
230 |
231 | userName:{' '}
232 | {loading
233 | ? 'loading'
234 | : error
235 | ? error.message
236 | : JSON.stringify(data?.data)}
237 |
238 |
239 |
240 |
241 |
242 |
count:{count}
243 |
244 | userName:{' '}
245 | {loading1
246 | ? 'loading'
247 | : error1
248 | ? error1.message
249 | : JSON.stringify(data1?.data)}
250 |
251 |
252 |
253 | );
254 | };
255 | ```
256 |
257 | ## 防抖
258 |
259 | 通过设置`option.debounceWait=300`开启请求防抖
260 |
261 | ```jsx
262 | /**
263 | * defaultShowCode: true
264 | */
265 | import { useState, useRef, useCallback } from 'react';
266 | import { request, useRequest } from '@bit/request';
267 |
268 | const service = (...params) =>
269 | request.get('/api/exchange-web/web/activity/getUserLoginStatus', ...params);
270 |
271 | export default () => {
272 | const [value, setValue] = useState(undefined);
273 | const [ready, setReady] = useState(false);
274 | const { data, loading, error } = useRequest(service, {
275 | ready,
276 | refreshDeps: [value],
277 | debounceWait: 300,
278 | });
279 |
280 | const handleInput = (e) => {
281 | const value = e.target.value;
282 | setReady(true);
283 | setValue(value);
284 | };
285 |
286 | return (
287 |
288 |
289 |
290 | userName:{' '}
291 | {loading
292 | ? 'loading'
293 | : error
294 | ? error.message
295 | : JSON.stringify(data?.data)}
296 |
297 |
298 | );
299 | };
300 | ```
301 |
302 | ## 节流
303 |
304 | 通过设置`option.throttleWait=300`开启请求节流
305 |
306 | ```jsx
307 | /**
308 | * defaultShowCode: true
309 | */
310 | import { useState, useRef, useCallback } from 'react';
311 | import { request, useRequest } from '@bit/request';
312 |
313 | const service = (...params) =>
314 | request.get('/api/exchange-web/web/activity/getUserLoginStatus', ...params);
315 |
316 | export default () => {
317 | const { data, loading, error, run } = useRequest(service, {
318 | manual: true,
319 | throttleWait: 1000 * 2,
320 | });
321 |
322 | const handleMouse = (e) => {
323 | run();
324 | };
325 |
326 | return (
327 |
328 |
332 |
333 | userName:{' '}
334 | {loading
335 | ? 'loading'
336 | : error
337 | ? error.message
338 | : JSON.stringify(data?.data)}
339 |
340 |
341 | );
342 | };
343 | ```
344 |
345 | ## 缓存
346 |
347 | 通过设置`option.cacheKey`开启请求缓存,通过设置`option.catchTime`设置缓存时间,超出这个时间会清空缓存,通过设置`option.staleTime`设置数据保鲜时间,在这个时间内不会重新发起请求,直接返回缓存数据;超过`staleTime`保鲜时间但是小于`catchTime`的请求会正常发起,但是会优先返回缓存数据,等请求数据返回后自动更新状态;同一个`cacheKey`的内容,在全局是共享的。
348 | 默认值:`option.catchTime = 5 * 60 * 1000`, `option.staleTime = 0`;
349 |
350 | ```jsx
351 | /**
352 | * defaultShowCode: true
353 | */
354 | import { useState, useRef, useCallback } from 'react';
355 | import { request, useRequest } from '@bit/request';
356 |
357 | const service = (...params) => {
358 | return new Promise((resolve) => {
359 | setTimeout(() => {
360 | resolve({
361 | data: Math.random() * 100,
362 | time: new Date().toString(),
363 | });
364 | }, 1000);
365 | });
366 | };
367 |
368 | export default () => {
369 | const currRef = useRef(new Date().getTime());
370 | const [duration, setDuration] = useState(0);
371 | const { data, loading, error, run } = useRequest(service, {
372 | cacheKey: 'random',
373 | catchTime: 1000 * 60,
374 | staleTime: 1000 * 30,
375 | manual: true,
376 | });
377 |
378 | const handleRefresh = () => {
379 | const Now = new Date().getTime();
380 | setDuration(Now - currRef.current);
381 | run();
382 | };
383 |
384 | const handleRun = () => {
385 | const Now = new Date().getTime();
386 | currRef.current = Now;
387 | run();
388 | };
389 |
390 | return (
391 |
392 |
393 |
394 |
request Interval: {duration}
395 |
396 | data: {loading && !data ? 'loading' : JSON.stringify(data?.data)}
397 |
398 |
399 | time: {loading && !data ? 'loading' : JSON.stringify(data?.time)}
400 |
401 |
402 | );
403 | };
404 | ```
405 |
406 | ## 轮询
407 |
408 | 通过设置`option.pollingInterval=3000`开启请求轮训
409 |
410 | ```jsx
411 | /**
412 | * defaultShowCode: true
413 | */
414 | import { useState, useRef, useCallback } from 'react';
415 | import { request, useRequest } from '@bit/request';
416 |
417 | const service = (...params) =>
418 | request.get('/api/exchange-web/web/activity/getUserLoginStatus', ...params);
419 |
420 | export default () => {
421 | const { data, loading, error, run, cancel } = useRequest(service, {
422 | manual: true,
423 | pollingInterval: 1000 * 5,
424 | });
425 |
426 | const handleStartPolling = () => {
427 | run();
428 | };
429 |
430 | const handleStopPolling = () => {
431 | cancel();
432 | };
433 |
434 | return (
435 |
436 |
437 |
438 |
439 | userName:{' '}
440 | {loading
441 | ? 'loading'
442 | : error
443 | ? error.message
444 | : JSON.stringify(data?.data)}
445 |
446 |
447 | );
448 | };
449 | ```
450 |
451 | ## 屏幕重新聚焦自动请求
452 |
453 | 通过设置`option.refreshOnWindowFocus=true`,在浏览器窗口 refocus 和 revisible 时,会重新发起请求。默认间隔是 1000 \* 10ms, 可以通过设置`option.focusTimespan`改变间隔时间
454 |
455 | ```jsx
456 | /**
457 | * defaultShowCode: true
458 | */
459 |
460 | import { useState, useRef, useCallback } from 'react';
461 | import { request, useRequest } from '@bit/request';
462 |
463 | const service = (...params) =>
464 | request.get('/api/exchange-web-gateway/hackerCompensate/info', ...params);
465 |
466 | export default () => {
467 | const { data, loading, error } = useRequest(service, {
468 | refreshOnWindowFocus: true,
469 | });
470 |
471 | return (
472 |
473 | info:{' '}
474 | {loading ? 'loading' : error ? error.message : JSON.stringify(data?.data)}
475 |
476 | );
477 | };
478 | ```
479 |
--------------------------------------------------------------------------------