为了账号安全,请及时绑定邮箱和手机立即绑定

从零搭建React项目脚手架(系列一:基于Axios封装一个带缓存功能的请求方法)

标签:
React.JS

前言

自入职以来,感觉生活着实是变得很忙碌,虽然是实习生,但工作依然是被安排的很充足。每天等着9点领完夜宵再下班已经是常态了,回到出租屋洗完澡已是10点,此后虽然还想着再学些什么却又更想还是看一会儿视频就睡觉好了。五一假期亦没有出行,一是没有想一起出游的伙伴,二是确实想好好休息下,与其出去受罪我还是想在学校歇着,玩一玩好久没动的游戏,看看书什么的。
说回此篇文章,在我实习经历中,我自己一直都在做的是类似于打杂的工作,主要时间都是在基于师兄建好的框架上进行页面的开发,虽然说在项目中这样的工作是占了绝大部分,但毫无疑问,那些前期基础的工作对于项目来说才是举足轻重的,是否掌握这些工程化的知识,也是区分前端工程师和前端切图仔的重要标准。出于掌握并分享知识的目的,我决定分为几个系列步骤,逐步搭建一个自己的React项目脚手架。

封装axios

将axios根据业务的需求封装为对外暴露的request方法,整体代码还是比较简单明了的,这里就不再过多解释,代码中也有注释,其中还用到了一个decodehtml方法,是用来将后端返回的富文本错误信息进行展示。

/** decodeHtml */
export const decodeHtml = (html: string): string => {
  let oDiv = document.createElement('div');
  oDiv.innerHTML = html;
  const textContent = oDiv.textContent;
  oDiv = null;
  return textContent;
};

以下为request方法的代码(src/utils/request.ts)

import { Message, Dialog } from '@alifd/next';
import axios, { AxiosRequestConfig } from 'axios';
import { decodeHtml } from '@/utils';

export interface IRequest extends AxiosRequestConfig {
  url: string;
  /** 请求参数,当method为post的时候请使用data属性 */
  params?: any;
  /** 当method为post的时候使用data属性 */
  data?: any;
  /**
   * 是否缓存数据,相同url数据只请求一次,后面的请求都使用第一次请求的数据
   */
  cache?: boolean;
  /** 更据 cacheKey 进行缓存, 当cache 为true时生效 */
  cacheKey?: string;
  /**
   * 错误时是否Message提示错误信息, 默认开启
   */
  enableErrorMsg?: boolean;
  //  缓存有效期
  maxAge?: number;
  [key: string]: any;
}

// 缓存存储对象,当项目大了的时候考虑释放内存,目前不需要
const request_cache = {};

export interface IResult {
  success?: boolean;
  code?: string;
  message?: string;
  data?: any;
  [key: string]: any;
}

// 设置为ajax请求
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';

/**
 * 数据请求
 * @example
 * request({
 *   url: '/a/b/c',
 *   params: {}
 * })
 * // res.success === true的时候执行 then
 * .then(res => {...})
 * // res.success === false 的时候执行 catch
 * .catch(res => {...})
 *
 * 请配合 `ahooks` 的 `useRequest` 使用 https://ahooks.js.org/hooks/async#dependent-request
 */
export default ({
  cache = false,
  enableErrorMsg = true,
  cacheKey = '',
  maxAge = Infinity, //过期时间
  ...params
}: IRequest) => {
  // 本地缓存,只请求一次
  if (cache && request_cache[`${params.url}-${cacheKey}`]) {
    if (request_cache[`${params.url}-${cacheKey}`].then) {
      return request_cache[`${params.url}-${cacheKey}`];
    } else if (
      new Date().getTime() -
        request_cache[`${params.url}-${cacheKey}`].cacheTime <
      maxAge
    ) {
      return new Promise(resolve => {
        resolve(request_cache[`${params.url}-${cacheKey}`].res);
      });
    }
  } else if (request_cache[`${params.url}-${cacheKey}`]) {
    delete request_cache[`${params.url}-${cacheKey}`];
  }

  if (params.method && params.method.toLowerCase() === 'post') {
    // post时传入参数是data字段
    params.data = params.data || params.params || undefined;
    // params.data = qs.stringify(params.data); // 和后端约定 post请求用json的方式传递数据,所以关掉
    params.params = undefined;
    params.headers = params.headers || {
      'Content-Type': 'application/json;charset=UTF-8', // 和后端约定 post请求用json的方式传递数据
    };
    // 如果需要做csrf可以采用以下方案,需要后端先在接口返回信息中设置cookie 
    // if (!isLocal()) {
    //   const csrfToken = getCookie('XSRF-TOKEN');
    //   if (csrfToken) {
    //     params.headers['X-XSRF-TOKEN'] = csrfToken;
    //   } else {
    //     Message.error('cookie获取失败'); // XSRF-TOKEN cookie 缺失
    //     throw new Error('cookie获取失败');
    //   }
    // }
  } else if (
    !params.method ||
    (params.method && params.method.toLowerCase() === 'get')
  ) {
    // get时传入的参数是params字段
    params.params = params.params || params.data || undefined;
  }

  const servicePromise = new Promise((resolve, reject) => {
    axios(params)
      .then(({ data: res, status }: IResult) => {
        const { message, success, data, code } = res;
        if (success && status === 200) {
          // 成功
          // 需要缓存的数据缓存下来
          cache &&
            (request_cache[`${params.url}-${cacheKey}`] = {
              res,
              cacheTime: new Date().getTime(),
            });
          resolve(res);
        } else if (code === '9003') {
          // 状态过期重定向,后端code为9003,这里的code每个团队有自己的规范 
          Dialog.confirm({
            content: `状态已过期,是否重新刷新页面? `,
            onOk: () => {
              window.location.reload();
            },
          });
        } else if (data?.applyUrl) {
          // 临时处理无权限跳转
          window.location.href = data.applyUrl;
          reject(res);
        } else {
          // 失败
          // 失败时是否展示错误信息
          enableErrorMsg &&
            Message.error(
              `[${params.apiName || params.url}]: ${decodeHtml(message)}`
            );
          reject(res);
        }
      })
      .catch(e => {
        // 接口非200 || 接口非304
        enableErrorMsg && Message.error(`[http]: ${e}`);
        reject(e);
      });
  });
//缓存这里需要考虑一种情况是,在第二次请求发出时,第一次请求还未完成
//那么可以先把第一次请求操作缓存,第二次请求时判断若还在pending中,则返回promise对象,否则返回结果
  if (cache) {
    request_cache[`${params.url}-${cacheKey}`] = servicePromise;
  }

  return servicePromise;
};

使用方式

应用中所有的请求方法都放在 src/services/ 目录下,每个模块/页面使用的请求方法放在一个对应的js/ts文件中。

//src/services/examplePage.ts
import request from '@/utils/request';

export const exampleApi = ({ data, ...elseConfig }) => {
  return request({
    url: `/api/reputation/evaluation/deepInsight/emotion`,
    method: 'post',
    data,
    ...elseConfig,
  });
};

在页面中使用,推荐搭配ahooks 的 useRequest使用

import {exampleApi} from '@/services/examplePage.ts';
import { useRequest } from 'ahooks';

  const { data, error, loading } = useRequest(()=>exampleApi({
      data:{
          yourInfo:''
      },
      cache:true,
      maxAge:360000  
  });

作者:羽晋
链接:https://juejin.cn/post/6957945548607324191
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
数据库工程师
手记
粉丝
42
获赞与收藏
202

关注作者,订阅最新文章

阅读免费教程

  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消