Для протокола: резюме проекта React

React.js

предисловие

Через три месяца я наконец завершил первый этап проекта с моими друзьями, и я много получил. Но поскольку это первый раз, когда мы полностью используем его в формальном проектеReactДля разработки у нас не хватило опыта построения предыдущего проекта, из-за чего мы столкнулись с некоторыми ограничениями при оптимизации проекта, и мы могли выбирать только какие-то второстепенные пути.

Далее я разберусь и "заполню пробелы" по всему процессу разработки на этот раз. Надеюсь, это тоже будет вам полезно. Если в тексте есть какие-то ошибки или неточности, пожалуйста, дайте мне несколько советов от большие ребята (в этой статье в основном рассказывается о миддл и бекстейджах). Процесс сборки проекта).

Кроме того, техническая итерация всех аспектов внешнего интерфейса сейчас выполняется немного быстрее, и некоторые методы написания между разными версиями отличаются, поэтому я буду отмечать версии зависимостей, которые я использую в настоящее время, чтобы различать их.

Подготовить

1. Инициализировать проект

Начиная с npm версии 5.2, была добавлена ​​команда npx, а узел поставляется с npm.По умолчанию здесь у всех установлен узел (как интерфейс, не должно быть ситуации, когда он не установлен).

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
    
  • redux,react-redux,redux-thunk

    redux: статус менеджера (статус данных).

    react-redux:react-reduxдаreduxТот, что упакован авторомreactСпециальная библиотека.

    redux-thunk: Пусть оригинал принимает только объектstore.dispatchСтановится объектом/методом, который может получать, и если метод получен, метод автоматически выполняется без срабатыванияreduxизstoreвозобновить.

    // 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
    
  • immer,use-immer

    immer: реализует неизменяемые структуры данных для js.

    use-immer:поставкаuseImmerметод.

    // immer@7.0.9、use-immer@0.4.1
    npm install immer use-immer
    
  • react-app-rewired,customize-cra,react-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
    
  • less,less-loader

    less:CSSязык предварительной обработки.

    less-loader:webpackБудуlessскомпилировано вcssизloader.

    //  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вступит в силу).

Многовходовая конфигурация

Потому что наш проект разделен на множество терминалов (официальный сайт, две промежуточные станции ПК, одна серверная часть ПК и одна мобильная часть). Для фона, как в случае с ПК, так и с мобильным терминалом (мобильный терминал является кастрированной версией версии для ПК), некоторые методы помощи, бизнес-логика и т. Д. Согласованы, поэтому мы помещаем два терминала в одно и то же. разработка в рамках проекта.

Для достижения этой цели нам необходимоwebpackКонфигурация расширена для поддержки нескольких записей.

1. Добавьте файлы, связанные с мобильными устройствами

Добавьте связанные файлы в корневой каталог проекта:

  • index-m.js: Файл записи мобильного терминала.
  • public/mobile.html: Мобильныйhtmlфайл шаблона.
  • AppMobile.js: Добавьте соответствующие компоненты конфигурации мобильного терминала вindex-m.jsимпортировать в.

и оригиналindex.js,public/index.html,App.jsОн используется как связанный файл на стороне ПК.

Исправлять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.jsа такжеAppMobile.jsДобавьте к нему некоторый контент, главное, чтобы вам было удобно различать ПК-терминал или мобильный терминал).

сделай это сноваnpm start, посетить отдельноhttp://localhost:3002/index.htmlа такжеhttp://localhost:3002/mobile.html(Здесь каждый обращается в соответствии с портом, работающим на их собственных проектах). Получилось очень красиво~.

2. Настройте прокси

Но вопрос в том, что наша первоначальная цель состоит в том, чтобы различать несколько терминалов, а не превращать его в многостраничное приложение, его суть все же одностраничное приложение, что нам делать?

Не волнуйтесь, не торопитесь~

мы сейчас прошлиindex.htmlа такжеmobile.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/, вроде без проблем, но...

3. Изменить веб-сокет

Когда я был тайно счастлив, реальность сильно ударила меня.

Когда я открыл консоль, я увидел сообщение об ошибке, как показано на рисунке ниже (почему у одного 404, а у другого 200, я не знаю, если кто знает, просветите, пожалуйста):

Этот результат является ошибкой горячего обновления, вызвавшей сбой, все изменения необходимо обновить вручную, но все предупреждения не работают, это, безусловно, неприемлемо (в конце концовreactВ процессе разработки соответствующие предупреждения все еще очень полезны).

Сначала давайте посмотрим наnetworkсерединаsockjs-nodeЗапрошенная информация:

Причина сбоя соединения здесь, потому что наш проект фактически работает в3002на порту, и когда мы используемNginxПосле настройки прокси мы получаем доступ8888порт и8889порт. Итак, нам просто нужно продолжать отправлятьsockjs-nodeЗапрошенный порт по-прежнему3002решит эту проблему.

То есть нам нужно подтвердить, что при созданииwebsocketКак настроить при подключенииurl, что, в свою очередь, устанавливает для него желаемое значение.

Найдя причину и решение, можно вздохнуть с облегчением (по крайней мере, с направлением). Давайте посмотрим на местоположение конкретной ошибки (нажмите на сообщение об ошибке, выводимое консольюwebpackHotDevClient.js:60):

Увидев это, неужели вдруг осознаешь, что успех не за горами! Из этого кода мы можем узнать, что если установленоWDS_SOCKET_HOSTа такжеWDS_SOCKET_PORT, то сразу взять соответствующее значение; если не задано, взять текущее имя домена доступа и порт. Окончательное сращиваниеwebsocketсвязаныurl.

Итак, нам нужно только указать в конфигурации средыWDS_SOCKET_HOSTа такжеWDS_SOCKET_PORTЭта проблема может быть решена для доменного имени и порта, которые мы хотим. существует.env.developmentДобавьте в файл следующее (горячее обновление обычно нужно использовать только в среде разработки):

WDS_SOCKET_HOST=127.0.0.1
WDS_SOCKET_PORT=3002

Перезапустите проектnpm start, посетить отдельноhttp://localhost:8888/а такжеhttp://localhost:8889/Нет ошибки, большие заслуги меня ~

библиотека компонентов пользовательского интерфейса

В проекте были выбраны Ant Design и Ant Design Mobile в качестве библиотеки компонентов пользовательского интерфейса для ПК и мобильных устройств соответственно.

Установить:

// 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-модулей, а для 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. Добавлено меньше поддержки и пользовательских тем

Добавлено меньше поддержки

После выполнения двух вышеуказанных шагов вы обнаружите, что стиль компонента не работает, когда вы обращаетесь к компоненту для тестирования. Это связано с тем, что проекты, созданные CRA, по умолчанию не поддерживают компиляцию меньшего количества файлов, поэтому нам нужно передатьconfig-overrides.jsрасширятьwebpackсоответствующая конфигурация. (вот что нужно установитьlessа такжеless-loaderДа, он был установлен ранее, поэтому я его здесь пропущу) (Кроме того, недавноAntdОфициальный сайт также постоянно обновляется.Пример исходного официального сайта:addLessLoader, теперь изменено на импортcraco-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Исправлятьantdа такжеantd-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]Уникальное имя класса в формате для области действия 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

Он предоставляет два метода:matchRoutesа такжеrenderRoutes, мы в основном смотрим на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Внутри, думаю, все сталкивались с проблемой утечек памяти, один из самых частых примеров — при переходе на страницу интерфейсный запрос не завершается, а в callback выполняются страничные операции. В это время возникает проблема с утечкой памяти. Что касается этой проблемы, моя идея состоит в том, чтобы отменить незавершенный запрос при переходе страницы (в зависимости от деловой ситуации). А действие «отменить» можно поместить в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сопутствующие операции.

В текущем проекте мы используем большеaxios,axiosПредоставляет нам два способа отмены. Просто посмотрите документацию:

Из документации мы можем знать, что первый способ может отменить несколько запросов, а второй — только определенный. Так что здесь я сразу выберу первый (лично я думаю, что большую часть времени в практических приложениях отменяются множественные запросы, поэтому я в основном смотрю на первый).

1. Простые аксиомы пакетов

Установить:

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

новыйserver/index.js:

Сначала подготовьте два интерфейса и установите 2-секундную задержку для удобства тестирования.

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_tokenа такжеcancelметод.

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())
// ...

Повторите описанный выше процесс проверки, и все готово~

Суммировать

Вещи ведут к другому, один за другим проектом, занят некогда черпать воду, ┭┮﹏┭┮ ... но хорошая новость в том, что, хоть и нон-стоп пишет дело, но всегда все же что-то в проекте собрано. Скрытность, статьи написаны какие-то грубые, знания относительно разрознены, после чего со временем постараюсь улучшить. Маленькие партнеры имеют любые предложения комментировать прямо в гостевой книге О ~

Продолжение следует...