记录:React 项目总结

4,635 阅读12分钟

前言

历时三个月,终于和小伙伴们 “肝” 完了一阶段的项目,收获还是挺多的。但是由于这是我们第一次在正式项目中完全使用 React 进行开发,前期项目搭建的时候没有足够的经验,导致我们在进行项目优化的时候遇到了一些局限性,只能选择一些退而求其次的方式。

下面我就根据这次的整个开发过程进行一次梳理和 “补缺”,希望它能对你也同样有所帮助,文中如果有不对或者不足之处还请大佬们不吝指教(本文主要讲中后台项目的搭建过程)。

另外现在前端各方面的技术迭代实在有点快,不同版本之间的一些写法都不尽相同,所以我会把当前我使用的依赖的版本都进行一个标注,以便区分。

准备

1. 初始化项目

npm 从 5.2 版开始,增加了 npx 命令,而 node 自带 npm,这里就默认大家都装过 node 啦(作为前端应该不存在没安装的情况吧)。

npx create-react-app react-demo

npm install -g create-react-app
create-react-app react-demo

2. 安装必要的依赖(可选)

  • react-router-dom

    提供路由功能。

    // react-router-dom@5.2.0
    npm install react-router-dom
    
  • reduxreact-reduxredux-thunk

    redux:管理程序状态(数据状态)。

    react-reduxreact-reduxredux 的作者封装的一个 react 专用的库。

    redux-thunk:让原本只能接受对象的 store.dispatch 变成可以接收对象/方法,并且如果接收了一个方法后自动执行该方法,而不触发 reduxstore 更新。

    // redux@4.0.5、react-redux@7.2.1、redux-thunk@2.3.0
    npm install redux react-redux redux-thunk
    
  • redux-devtools-extension

    store 数据管理调试工具。

    // redux-devtools-extension@2.13.8
    npm install redux-devtools-extension
    
  • immeruse-immer

    immer:实现 js 的不可变数据结构。

    use-immer:提供 useImmer 方法。

    // immer@7.0.9、use-immer@0.4.1
    npm install immer use-immer
    
  • react-app-rewiredcustomize-crareact-app-rewire-multiple-entry

    react-app-rewired:是修改 CRA 配置的工具,提供在不暴露项目配置的情况,修改项目配置的功能。

    customize-cra:提供帮助方法,用于修改 webpack 配置。

    react-app-rewire-multiple-entry:添加多页入口。

    // react-app-rewired@2.1.6、customize-cra@1.0.0、react-app-rewire-multiple-entry@2.2.0
    npm install react-app-rewired customize-cra  --save-dev
    
  • dotenv-cli

    .env 文件中的环境变量加载到 process.env

    // dotenv-cli@4.0.
    npm install dotenv-cli --save-dev
    
  • lessless-loader

    lessCSS 预处理语言。

    less-loaderwebpackless 编译成 cssloader

    //  less@3.12.2、less-loader@7.0.1
    npm install less less-loader --save-dev
    

多环境配置

在开发过程中,项目会存在多个环境:开发、测试、uat、生产等,我们会根据各个环境对项目进行部署,这个时候就需要通过 dotenv 将对应环境的 .env 文件中的环境变量加载到 process.env 中,从而实现多环境配置(官网)。

  1. 在项目根目录下分别新建以下文件,并添加环境变量 BASE_URL

    • .env:用来设置一些公共的配置。
    • .env.development:开发环境配置,添加变量:REACT_APP_BASE_URL=development.api.com
    • .env.test:测试环境配置,添加变量:REACT_APP_BASE_URL=test.api.com
    • .env.production:生产环境配置,添加变量:REACT_APP_BASE_URL=production.api.com
  2. 修改 package.json 文件中的 scripts

"scripts": {
  "start": "react-app-rewired start",
  "build:dev": "dotenv -e .env.development react-app-rewired build",
  "build:test": "dotenv -e .env.test react-app-rewired build",
  "build:prod": "dotenv -e .env.production react-app-rewired build",
  "test": "react-app-rewired test",
  "eject": "react-scripts eject"
},
  1. 在根目录下新建 config-overrides.js 文件。

    当我们通过 react-app-rewired 对项目进行运行/打包的时候,会先读取 config-overrides.js 中的相关配置,对原有的 webpack 配置规则进行修改,然后再进行打包。这里我们就先创建一个空的 config-overrides.js 文件,后面再根据项目需要进行配置。

  2. 测试

    通过上面的配置,我们可以通过 process.env 访问配置中的环境变量,并且根据不同的编译命令,运行/打包各环境的代码。

    执行 npm start,启动项目。在 App.js 中打印 process.env.REACT_APP_BASE_URL,我们可以看到控制台输出了 development.api.com(其他环境可以自行测试,这里就不赘述了。另外,如果修改环境变量的文件或 config-overrides.js 中的内容,是需要重新执行 npm start 才会生效哦)。

多入口配置

由于我们的项目分了很多个端(官网、两个 PC 中台、一个 PC 后台、一个移动端后台)。对于后台来说,在 PC 端和移动端都有的情况下(移动端就是 PC 版的阉割版本),一些帮助方法、业务逻辑等都是一致的,所以我们将这两个端放在同一个项目下进行开发。

为了实现这样的目的,我们需要对原有的 webpack 配置进行扩展,让它支持多入口。

1. 添加移动端相关的文件

在项目根目录下添加相关文件:

  • index-m.js:移动端入口文件。
  • public/mobile.html:移动端 html 模板文件。
  • AppMobile.js:添加移动端的相关配置构成组件,在 index-m.js 中进行引入。

而原有的 index.jspublic/index.htmlApp.js 则作为 PC 端的相关文件。

修改 config-overrides.js

const { override } = require('customize-cra')

const multipleEntry = require('react-app-rewire-multiple-entry')([
  {
    entry: 'src/index-m.js',
    template: 'public/mobile.html',
    outPath: '/mobile.html'
  }
])

const customWebpack = () => config => {
  multipleEntry.addMultiEntry(config)
  return config
}

module.exports = {
  webpack: override(
    customWebpack(),
  )
}

这里我们就已经将移动端的入口文件添加到原有的 webpack 配置中了,下面进行一下测试(可以分别在 App.jsAppMobile.js 中添加一些内容,只要方便自己区分是 PC 端还是移动端即可)。

重新执行 npm start,分别访问 http://localhost:3002/index.htmlhttp://localhost:3002/mobile.html(这里大家根据自己项目运行的端口访问)。结果一切都是那么美好~。

2. 设置代理

但是问题来了,我们最初的目的是希望区分多个端,而不是将其变成多页应用,其本质还是单页应用,要怎么办呢?

不要急,慢慢来~

我们现在是通过 index.htmlmobile.html 的后缀来进行区分的,如果可以改用端口或域名区分,那是不是就可以解决这个问题呢,我的思路是通过 Nginx 配置代理(Nginx 相关基础知识可以参照:Nginx基础知识入门,从零到一的实践),根据不同的端口访问不同的端。心动不如行动,尝试一下~

添加 Nginx 配置,并重启 sudo nginx -s reload

server {
  listen 8888;
  server_name localhost;

  location / {
    proxy_pass http://127.0.0.1:3002;
  }
}

server {
  listen 8889;
  server_name localhost;

  location ~ ^[^.]*$ {
    rewrite /(.*) /mobile.html break;
    proxy_pass http://127.0.0.1:3002;
  }
  location / {
    proxy_pass http://127.0.0.1:3002;
  }
}

访问 http://localhost:8888/http://localhost:8889/,看着好像没问题,but...

3. 修改 websocket

就在我暗自高兴的时候,现实狠狠地 “敲” 了我一下。

打开控制台,我看到了如下图所示的报错信息(为什么一个是 404,一个是 200,我也不知道,有知道的大佬还请赐教):

这个错误导致的结果就是热更新失效,所有的修改,都需要手动刷新,而且,所有的警告全部失效,这肯定是不能接受的(毕竟在 react 的开发过程中,相关警告的作用还是很大的)。

首先我们看一下 networksockjs-node 请求的相关信息:

连接失败的原因就在这里,因为我们的项目实际运行是在 3002 端口上的,而当我们使用 Nginx 设置了代理后,我们访问的是 8888 端口和 8889 端口。所以,我们只需要保持发送 sockjs-node 请求的端口还是 3002 就能解决这个问题了。

也就是说,我们需要确认在创建 websocket 连接的时候,是怎么设置的 url,进而将其设置为我们想要的值。

找到原因和解决方案之后,可以舒一口气了(至少有了方向)。下面我们看一下具体报错的位置(点击控制台输出的错误信息 webpackHotDevClient.js:60):

看到这里是不是有种恍然大悟的感觉呢,成功离我们已经不远啦!从这段代码我们可以知道,如果设置了 WDS_SOCKET_HOSTWDS_SOCKET_PORT,则直接取对应的值;如果没有设置,则取当前的访问域名和端口。最终拼接为 websocket 连接的 url

所以,我们只需要在环境配置里面指定 WDS_SOCKET_HOSTWDS_SOCKET_PORT 为我们想要的域名和端口就可以解决这个问题了。在 .env.development 文件中(热更新正常只需要在开发环境中使用)添加以下内容:

WDS_SOCKET_HOST=127.0.0.1
WDS_SOCKET_PORT=3002

重新运行项目 npm start,分别访问 http://localhost:8888/http://localhost:8889/,没有报错,大功告成~

UI 组件库

项目中,PC端和移动端分别选择了 Ant Design 和 Ant Design Mobile 作为 UI 组件库。

安装:

// antd@4.6.6、antd-mobile@2.3.4
npm install antd antd-mobile --save

1. antd

index.js 中引入 antd 的样式文件 import 'antd/dist/antd.less'

antd 默认支持基于 ES modules 的 tree shaking,对于 js 部分,直接引入 import { Button } from 'antd' 就会有按需加载的效果。

2. antd-mobile

目前 antd-mobile 还需要手动实现按需加载。

修改 config-overrides.js 文件(官网):

const { override, fixBabelImports } = require('customize-cra')
// ...
module.exports = {
  webpack: override(
    customWebpack(),
    fixBabelImports('import', {
      libraryName: 'antd-mobile',
      style: true
    })
  )
}

重新运行项目,然后只需从 antd-mobile 引入模块即可,无需单独引入样式。

3. 新增 less 支持和定制主题

新增 less 支持

完成上面两步后,此时引用组件进行测试会发现,组件样式没有起作用。这是因为 CRA 创建的项目,默认是不支持编译 less 文件的,所以我们需要通过 config-overrides.js 扩展 webpack 的相关配置。(这里是需要安装 lessless-loader 的,前面已经安装过的,这里就跳过啦)(另外最近 Antd 官网也在一直更新,原先官网的例子是 addLessLoader,现在改成了引入 craco-less 来帮助加载 less 样式和修改变量,大家可以都尝试一下哦)。

完整的 config-overrides.js

const { override, addLessLoader, fixBabelImports } = require('customize-cra')

const multipleEntry = require('react-app-rewire-multiple-entry')([
  {
    entry: 'src/index-m.js',
    template: 'public/mobile.html',
    outPath: '/mobile.html'
  }
])

const customWebpack = () => config => {
  multipleEntry.addMultiEntry(config)
  return config
}

module.exports = {
  webpack: override(
    customWebpack(),
    addLessLoader({
      lessOptions: {
        javascriptEnabled: true,
        modifyVars: {
        }
      }
    }),
    fixBabelImports('import', {
      libraryName: 'antd-mobile',
      style: true
    })
  )
}

重新运行项目,对应的组件样式就可以生效了。

定制主题

在上面的配置中,我们可以通过 modifyVars 修改 antdantd-mobile 的主题样式,例如:主题色、文本颜色、圆角等。

样例:

// ...
addLessLoader({
  lessOptions: {
    javascriptEnabled: true,
    modifyVars: {
      '@primary-color': '#4085F5',
      '@text-color': 'rgba(0, 0, 0, 0.65)',
      '@brand-primary': '#4085F5',
      '@fill-body': '#F7F8FA',
      '@color-text-base': '#333333',
    },
  }
})
//...

CSS Module

使用 [name].module.css 文件命名约定支持 CSS Module 和常规 CSS 。 CSS Module 允许通过自动创建 [filename]\_[classname]\_\_[hash] 格式的唯一 classname 来确定 CSS 的作用域。

项目中我们使用了 Less 作为预处理器,而让其支持 CSS Module 需要修改 webpack 的配置。值得庆幸的是,上面我们使用了 react-app-rewired 的形式修改 CRA 的相关配置,同时也使用了 addLessLoader 方法来进行一些扩展。而 addLessLoader 默认已经帮我们修改了 webpack 中的相关 less-loader 的配置,所以我们可以直接使用 [name].module.less

我们可以简单看一下 addLessLoader 这部分的源码:

  1. 默认处理后的样式名,格式为 [local]--[hash:base64:5]
  2. 通过两个正则来区分 .less.module.less 两种类型。

下面我们就直接进入测试。

新建 style/base.module.less

.baseContainer {
  padding: 50px;
  .title {
    color: pink;
  }
  .fontSize {
    font-size: 20px;
  }
}

修改 App.js:

import React from 'react'
import style from './style/base.module.less'

function App() {
  return (
    <div className={style.baseContainer}>
      <div className={`${style.title} ${style.fontSize}`}>App</div>
    </div>
  )
}

export default App

查看运行结果:

更详细的使用方法参照官网:传送门

路由

1. 静态路由配置

用过 Vue 的同学都知道,在 Vue 里面,vue-router 为我们提供了许多 便利,像静态路由配置和导航守卫等。而在 React 里,这些都需要我们自己手动实现。

React 静态路由配置的依赖在 github 上已经有大佬为我们提供了:react-router-config

安装 react-router-config,这是一个 React 路由器的静态路由配置帮助程序。

// react-router-config@5.1.1
npm install react-router-config --save

它提供了两个方法:matchRoutesrenderRoutes,我们主要看一下 renderRoutes

先看源码(node_modules/reactrouter-config/modules/renderRoutes.js):

import React from "react";
import { Switch, Route } from "react-router";

function renderRoutes(routes, extraProps = {}, switchProps = {}) {
  return routes ? (
    <Switch {...switchProps}>
      {routes.map((route, i) => (
        <Route
          key={route.key || i}
          path={route.path}
          exact={route.exact}
          strict={route.strict}
          render={props =>
            route.render ? (
              route.render({ ...props, ...extraProps, route: route })
            ) : (
              <route.component {...props} {...extraProps} route={route} />
            )
          }
        />
      ))}
    </Switch>
  ) : null;
}

export default renderRoutes;

源码的内容很简单,就是将 routes 进行 map 处理。可能有同学会有疑问:为什么我们要使用这种方式加载路由呢?我的理解是,让我们的程序更简洁、更可控。

更简洁:可以模拟 Vue 中路由的写法,实现静态路由配置,路由结构清晰明了;同时当项目中有多种布局时,我们可以更清晰地书写路由和注册路由。

更可控:在 React 里面,相信大家都遇到过内存泄露的问题,举一个最常见的例子:页面跳转的时候,接口请求未结束,同时在回调里面执行了页面相关的操作。这个时候就好产生内存泄露的问题。关于这个问题,我的思路是在页面跳转时,取消未结束的请求(具体还要看业务情况)。而 “取消” 的这一动作我们可以放在 renderRoutes 中进行(在我看来就是导航守卫了)。

新建 route/renderRoutes.js,将依赖包中的源码复制进去,这样我们就可以根据需要进行扩展了。

新建 route/index.js

const routes = [
  { path: '/login', exact: true, component: Login},
  { path: '/404', component: NotFound},
  { 
    path: '/goods', component: GoodsLayout,
    routes: [
      { path: '/goods/list', component: GoodsList}
    ]
  },
  {
    component: BasicLayout,
    routes: [
      { path: '/', exact: true, component: Home},
      { path: '/home2', exact: true, component: Home2 },
      { path: '/home3', exact: true, component: Home3 }
    ]
  }
]

到这里,静态路由配置已经完成了。关于使用方式,具体可以参照 github 上 react-router-config 的介绍。

2. 扩展

有了上面 renderRoutes.js 之后,我们就对路由的大部分功能进行统一实现和管理了,例如路由鉴权、取消请求等。

简单看一下路由鉴权的实现,修改 renderRoutes.js

import React from 'react'
import { Switch, Route } from 'react-router'

function renderRoutes(routes, authed, extraProps = {}, switchProps = {}) {
  return routes ? (
    <Switch {...switchProps}>
      {routes.map((route, i) => (
        <Route
          key={route.key || i}
          path={route.path}
          exact={route.exact}
          strict={route.strict}
          render={props => {
            
            if (route.auth && !route.auth.includes(authed)) {
              // 进行拦截
            }

            return route.render ? (
              route.render({ ...props, ...extraProps, route: route })
            ) : (
              <route.component {...props} {...extraProps} route={route} />
            )
          }
          }
        />
      ))}
    </Switch>
  ) : null
}

export default renderRoutes

这里是通过传入 authed(当前用户的权限),在 render 方法中进行判断:如果该用户具备权限进入目标页面的权限,则正常跳转,否则进行拦截并作出对应的处理。

至于真实情况下的路由鉴权应该是怎么判断的,还需要业务逻辑进行设计。

Axios 取消请求

react 里面,相信很多同学都遇到过内存泄露的问题,主要是由于在页面卸载之后,执行了状态更新。比较典型的就是异步请求回调之后执行了 setData 相关的操作。

目前项目中我们使用的比较多的是 axiosaxios 为我们提供了两种取消方式。直接看文档:

从文档中,我们可以知道第一种方式可以取消多个请求,而第二个只能取消某一个。所以这里就先直接选择第一种(个人觉得实际应用中大部分时候都是取消多个请求,所以主要看第一种)。

1. 简单封装 axios

安装:

// axios@0.19.2、express@4.17.1
npm install axios express --save

新建 server/index.js

先准备两个接口,并设置 2s 延迟,便于测试。

const express = require('express')
const app = express()

app.all('*', function (req, res, next) {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild');
  res.header('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');

  if (req.method == 'OPTIONS') {
    res.send(200)
  } else next()
})

app.get('/api/test', function (req, res) {
  setTimeout(() => {
    res.send('测试结果')
  }, 2000)
})
app.post('/api/test2', function (req, res) {
  setTimeout(() => {
    res.send('测试结果')
  }, 2000)
})

let server = app.listen(9999, function () {

  let host = server.address().address
  let port = server.address().port

  console.log('访问地址为:', host, port)
})

新建 utils/request

import axios from 'axios'

const service = axios.create({
  baseURL: 'http://127.0.0.1:9999',
  timeout: 20000
})

service.interceptors.request.use(
  (config) => {
    return config
  },
  (error) => {
    return Promise.reject(error)
  }
)

service.interceptors.response.use(
  (response) => {
    return response
  },
  (error) => {
    return Promise.reject(error)
  }
)

export default service

新建 model/index.js

import fetch from '../utils/request.js'

class Model {

  api(options = {}) {
    if (!options.method) options.method = 'get'

    return new Promise((resolve, reject) => {
      let request
      let config = {
        method: options.method,
        url: options.url,
      }

      switch (options.method) {
      case 'get':
        request = fetch({
          ...config,
          params: options.params
        })
        break
      case 'post':
        request = fetch({
          ...config,
          data: options.data
        })
        break
      default:
      }
      request
        .then(response => {
          resolve(response)
        }).catch(error => {
          reject(error)
        })
    })
  }

  get (options = {}) {
    options.method = 'get'
    return this.api(options)
  }

  post (options = {}) {
    options.method = 'post'
    return this.api(options)
  }
}

export default Model

新建 model/common.js

import Model from './index'

class Common extends Model {
  getTest(options = {}) {
    options.url = '/api/test'
    return this.get(options)
  }
  getTest2(options = {}) {
    options.url = '/api/test2'
    return this.post(options)
  }
}

const commonModel = new Common()
export default commonModel

使用:

import React, { useState } from 'react'
import { Button } from 'antd'
import commonModel from '@/model/common'

function Index2() {

  const [test, setTest] = useState(0)

  const sendRequest = () => {
    commonModel.getTest().then((data) => {
      setTimeout(() => {
        setTest(1)
      }, 1000)
    })
    commonModel.getTest2().then((data) => {
      setTest(123)
    })
  }

  return (
    <div>
      <div>概览2</div>
      <div>{test}</div>
      <Button onClick={sendRequest}>send request</Button>
    </div>
  )
}

export default Index2

点击按钮发送请求,页面显示 test 值先为 123,后为 1,则 axios 配置成功。

模拟一下内存泄露:打开控制台,点击按钮发送请求,两秒之内跳转其他页面,我们可以看到以下错误:

这是因为当我们跳转页面时,上一个页面的请求没有结束,且触发了状态更新,然后导致了内存泄漏。下面我们就来解决这个问题。

2. axios.CancelToken

修改 utils/request.js

// ...
const getCancelToken = () => {
  let CancelToken = axios.CancelToken
  let source = CancelToken.source()
  return source
}
// ...
service.interceptors.request.use(
  (config) => {
    config.cancelToken = config.cancel_token
    return config
  },
  (error) => {
    return Promise.reject(error)
  }
)
// ...
export {
  getCancelToken
}

修改 model/index.js

// ...
return new Promise((resolve, reject) => {
      let request
      let config = {
        method: options.method,
        url: options.url,
      }
      if (options.cancel_token) config.cancel_token = options.cancel_token

      switch (options.method) {
//...

修改页面:

import React, { useEffect, useRef, useState } from 'react'
import { Button, Space } from 'antd'
import commonModel from '@/model/common'
import { getCancelToken } from '@/utils/request'

function Index2() {
  const source = useRef(getCancelToken())

  const [test, setTest] = useState(0)

  useEffect(() => {
    return () => {
      source.current.cancel('取消请求')
      source.current = null
    }
  }, [])

  const sendRequest = () => {
    commonModel.getTest({
      cancel_token: source.current.token
    }).then((data) => {
      setTimeout(() => {
        setTest(1)
      }, 1000)
    })
    commonModel.getTest2({
      cancel_token: source.current.token
    }).then((data) => {
      setTest(123)
    })
  }

  return (
    <div>
      <div>概览2</div>
      <div>{test}</div>
      <Space>
        <Button onClick={sendRequest}>send request</Button>
      </Space>
    </div>
  )
}

export default Index2

重复上面的测试过程,我们会发现控制台打印了两次:“取消请求”,同时内存泄露的问题也没有再发生了。

简单梳理一下思路:

  • 给需要 “取消请求” 的方法传入 cancel_token
  • 通过 model/index.js 传给 utils/request.js
  • utils/request.js 接收 cancel_token,并添加到 axios 的请求拦截器的配置中。
  • 页面卸载时,调用 cancel 方法,取消请求。

如果每个页面都去做这样的处理,那就太麻烦了。是否可以有公共的地方去处理 “取消请求” 呢?

答案是肯定的~,首先取消请求的动作是在页面卸载(路由发生改变)的时候执行的,结合上面的 renderRoutes,是不是就可以解决这个问题了呢。

3. 统一处理

思路就是通过一个标识,用于区分在路由切换的时候需要取消请求的 api;对这些标记过的 api 添加 cancel_token;最后在路由切换时取消这些 api的请求。

新建 utils/global.js,用于存储 cancel_tokencancel 方法。

let global = {
  source: {
    token: null,
    cancel: null
  },
  timestamp: null
}

const changeGlobal = (key, value) => {
  global[key] = value
}

export {
  global,
  changeGlobal
}

修改 modal/common.js,通过变量 needCancel 标记哪些接口需要 “取消”。

import Model from './index'

class Common extends Model {
  getTest(options = {}) {
    options.url = '/api/test'
    options.needCancel = true
    return this.get(options)
  }
  getTest2(options = {}) {
    options.url = '/api/test2'
    options.needCancel = true
    return this.post(options)
  }
}

const commonModel = new Common()
export default commonModel

修改 modal/index.js,对标记的接口添加 cancel_token

import fetch from '../utils/request.js'
import { global } from '../utils/global'

class Model {
// ...
      if (options.needCancel && global.source) config.cancel_token = global.source.token

      switch (options.method) {
// ...

修改 route/renderRoutes.js

import React from 'react'
import { Switch, Route, Redirect } from 'react-router'
import { getCancelToken } from '@/utils/request'
import { global, changeGlobal } from '../utils/global'

function renderRoutes(routes, authed, extraProps = {}, switchProps = {}) {

  return routes ? (
    <Switch {...switchProps}>
      {routes.map((route, i) => (
        <Route
          key={route.key || i}
          path={route.path}
          exact={route.exact}
          strict={route.strict}
          render={props => {

            if (route.auth && !route.auth.includes(authed)) {
              // 进行拦截
            }
            // 生成新的 cancel_token
            if (global.source.token && global.source.cancel) global.source.cancel('取消请求')
            changeGlobal('source', getCancelToken())
            changeGlobal('timestamp', new Date().getTime())
// ...

重复上面的测试过程,大功告成~

总结

事情一件接一件,项目一个接一个,忙的都没时间划水,┭┮﹏┭┮...不过值得庆幸的是,虽然在不停的写业务,但在项目里总归还是有所收获的。忙里偷闲,文章写的有些粗糙,知识点也比较零碎,后面有时间我会尽量完善。小伙伴们有什么建议直接评论区留言噢~

未完待续...