Вооружите свой интерфейсный проект «Практикой Vue»

Vue.js
Вооружите свой интерфейсный проект «Практикой Vue»

Содержание этой статьи

Проект в этой статье основан на Vue-Cli 3. Если вы хотите узнать, как его правильно собрать, посмотрите мою предыдущую статью:

Правильная позиция для обновления vue-cli3 в проекте "Vue Practice"

1. Обработка интерфейсного модуля

1.1 axiosВторичная упаковка

Основой для инкапсуляции здесь является фоновая передачаJWT, уже упаковано, пожалуйста, пропустите.

import axios from 'axios'
import router from '../router'
import {MessageBox, Message} from 'element-ui'

let loginUrl = '/login'
// 根据环境切换接口地址
axios.defaults.baseURL = process.env.VUE_APP_API
axios.defaults.headers = {'X-Requested-With': 'XMLHttpRequest'}
axios.defaults.timeout = 60000

// 请求拦截器
axios.interceptors.request.use(
  config => {
    if (router.history.current.path !== loginUrl) {
      let token = window.sessionStorage.getItem('token')
      if (token == null) {
        router.replace({path: loginUrl, query: {redirect: router.currentRoute.fullPath}})
        return false
      } else {
        config.headers['Authorization'] = 'JWT ' + token
      }
    }
    return config
  }, error => {
    Message.warning(error)
    return Promise.reject(error)
  })

Далее идет перехватчик ответа (т.е. обработка исключений)

axios.interceptors.response.use(
  response => {
    return response.data
  }, error => {
    if (error.response !== undefined) {
      switch (error.response.status) {
        case 400:
          MessageBox.alert(error.response.data)
          break
        case 401:
          if (window.sessionStorage.getItem('out') === null) {
            window.sessionStorage.setItem('out', 1)
            MessageBox.confirm('会话已失效! 请重新登录', '提示', {confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning'}).then(() => {
              router.replace({path: loginUrl, query: {redirect: router.currentRoute.fullPath}})
            }).catch(action => {
              window.sessionStorage.clear()
              window.localStorage.clear()
            })
          }
          break
        case 402:
          MessageBox.confirm('登陆超时 !', '提示', {confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning'}).then(() => {
            router.replace({path: loginUrl, query: {redirect: router.currentRoute.fullPath}})
          })
          break
        case 403:
          MessageBox.alert('没有权限!')
          break
        // ...忽略
        default:
          MessageBox.alert(`连接错误${error.response.status}`)
    }
    return Promise.resolve(error.response)
  }
  return Promise.resolve(error)
})

Обработка здесь заключается в том, что срок действия сеанса истек, а время входа в систему истекло. Конкретные данные необходимо изменить в соответствии с бизнесом.

Наконец, экспортируйте базовую инкапсуляцию типа запроса.

export default {
  get (url, param) {
    if (param !== undefined) {
      Object.assign(param, {_t: (new Date()).getTime()})
    } else {
      param = {_t: (new Date()).getTime()}
    }
    return axios({method: 'get', url, params: param})
  },
  // 不常更新的数据用这个
  getData (url, param) {
    return axios({method: 'get', url, params: param})
  },
  post (url, param, config) {
    return axios.post(url, param, config)
  },
  put: axios.put,
  _delete: axios.delete
}

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

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

1.2 Запросы на объединение по модулю

Запрос модуля:

import http from '@/utils/request'
export default {
  A (param) { return http.get('/api/', param) },
  B (param) { return http.post('/api/', param) }
  C (param) { return http.put('/api/', param) },
  D (param) { return http._delete('/api/', {data: param}) },
}

utils/api/index.js:

import http from '@/utils/request'
import account from './account'
// 忽略...
const api = Object.assign({}, http, account, \*...其它模块*\)
export default api

1.3 Обработка в global.js

существуетglobal.jsПредставлен в:

import Vue from 'vue'
import api from './api/index'
// 略...

const errorHandler = (error, vm) => {
  console.error(vm)
  console.error(error)
}

Vue.config.errorHandler = errorHandler
export default {
  install (Vue) {
    // 添加组件
    // 添加过滤器
    })
    // 全局报错处理
    Vue.prototype.$throw = (error) => errorHandler(error, this)
    Vue.prototype.$http = api
    // 其它配置
  }
}

При написании интерфейса его можно упростить до:

async getData () {
    const params = {/*...key : value...*/}
    let res = await this.$http.A(params)
    res.code === 4000 ? (this.aSata = res.data) : this.$message.warning(res.msg)
}

2. Автоматическая глобальная регистрация основных компонентов

от@SHERlocked93: Советы по использованию Vue

Официальная документация: Автоматизированная глобальная регистрация базовых компонентов

Когда мы пишем компоненты, нам обычно нужно вводить дополнительные компоненты:

<template>
    <BaseInput  v-model="searchText"  @keydown.enter="search"/>
    <BaseButton @click="search">
        <BaseIcon name="search"/>
    </BaseButton>
</template>
<script>
    import BaseButton from './baseButton'
    import BaseIcon from './baseIcon'
    import BaseInput from './baseInput'
    export default {
      components: { BaseButton, BaseIcon, BaseInput }
    }
</script>

Писать такие небольшие проекты нормально, но когда проект раздувается... цк цк. Вот поwebpack,использоватьrequire.context()метод создания собственного контекста модуля для автоматического динамическогоrequireкомпоненты.

Этот метод требует 3 параметра:

  • Каталог папок для поиска
  • Должен ли он также искать его подкаталоги
  • Регулярное выражение, соответствующее файлам.

Создайте новый в корневом каталоге папки, куда вы положили базовые компоненты.componentRegister.js:

import Vue from 'vue'
/**
 * 首字母大写
 * @param str 字符串
 * @example heheHaha
 * @return {string} HeheHaha
 */
function capitalizeFirstLetter (str) {
  return str.charAt(0).toUpperCase() + str.slice(1)
}
/**
 * 对符合'xx/xx.vue'组件格式的组件取组件名
 * @param str fileName
 * @example abc/bcd/def/basicTable.vue
 * @return {string} BasicTable
 */
function validateFileName (str) {
  return /^\S+\.vue$/.test(str) &&
    str.replace(/^\S+\/(\w+)\.vue$/, (rs, $1) => capitalizeFirstLetter($1))
}
const requireComponent = require.context('./', true, /\.vue$/)
// 找到组件文件夹下以.vue命名的文件,如果文件名为index,那么取组件中的name作为注册的组件名
requireComponent.keys().forEach(filePath => {
  const componentConfig = requireComponent(filePath)
  const fileName = validateFileName(filePath)
  const componentName = fileName.toLowerCase() === 'index'
    ? capitalizeFirstLetter(componentConfig.default.name)
    : fileName
  Vue.component(componentName, componentConfig.default || componentConfig)
})

Наконец мыmain.jsсередина

import 'components/componentRegister.js'

Мы можем использовать эти основные компоненты в любое время и в любом месте без ручного введения.

3. Отладка производительности страницы: Hiper

Когда мы пишем одностраничное приложение, довольно утомительно наблюдать за изменениями производительности после изменения страницы. Иногда я хочу знать, является ли это «положительной оптимизацией» или «отрицательной оптимизацией», и ее можно просмотреть только с помощью ручного обновления.network. иHiperЭто очень хорошо решает эту болевую точку (на самом делеHiperтихо работает в фоновом режимеChromiumНет смысла заниматься отладкой).

Официальная документация BIPER

После того, как мы разработаем проект или оптимизируем производительность проекта, как мы оценим, соответствует ли производительность проекта стандарту?

Наш общий путь вDev ToolсерединаperformanceиnetworkПросмотрите данные, запишите несколько ключевых показателей эффективности, а затем обновите несколько раз, чтобы увидеть эти показатели эффективности.

Иногда мы обнаруживаем, что из-за слишком малого количества образцов на это сильно влияет текущая загруженность «сети», «ЦП» и «памяти», а иногда оптимизированный проект работает медленнее, чем до оптимизации.

Если есть инструмент, который запрашивает страницы N раз за раз, а затем берет каждый индекс производительности и усредняет его, мы можем очень точно знать, является ли оптимизация «позитивной оптимизацией» или «негативной оптимизацией».

Кроме того, вы также можете проводить сравнения, чтобы получить точные данные о том, «насколько оптимизировано». Этот инструмент предназначен для решения этой болевой точки.

Установить глобально

sudo npm install hiper -g
# 或者使用 yarn:
# sudo yarn global add hiper

Представление

Key Value
Поиск DNS занимает время domainLookupEnd - domainLookupStart
Длительное TCP-соединение connectEnd - connectStart
Время, необходимое для того, чтобы первый байт достиг браузера responseStart - requestStart
Время загрузки страницы responseEnd - responseStart
Время для продолжения загрузки ресурсов после готовности DOM domComplete - domInteractive
время белого экрана domInteractive - navigationStart
DOM Время готовности domContentLoadedEventEnd - navigationStart
общее время загрузки страницы loadEventEnd - navigationStart

developer.Mozilla.org/this-cn/docs/…

Конфигурация варианта использования

 # 当我们省略协议头时,默认会在url前添加`https://`

 # 最简单的用法
 hiper baidu.com
 # 如何url中含有任何参数,请使用双引号括起来
 hiper "baidu.com?a=1&b=2"
 #  加载指定页面100次
 hiper -n 100 "baidu.com?a=1&b=2"
 #  禁用缓存加载指定页面100次
 hiper -n 100 "baidu.com?a=1&b=2" --no-cache
 #  禁JavaScript加载指定页面100次
 hiper -n 100 "baidu.com?a=1&b=2" --no-javascript
 #  使用GUI形式加载指定页面100次
 hiper -n 100 "baidu.com?a=1&b=2" -H false
 #  使用指定useragent加载网页100次
 hiper -n 100 "baidu.com?a=1&b=2" -u "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36"

Кроме того, можно настроитьCookieдоступ

module.exports = {
    ....
    cookies:  [{
        name: 'token',
        value: process.env.authtoken,
        domain: 'example.com',
        path: '/',
        httpOnly: true
    }],
    ....
}
# 载入上述配置文件(假设配置文件在/home/下)
hiper -c /home/config.json

# 或者你也可以使用js文件作为配置文件
hiper -c /home/config.js

4. Инкапсуляция компонентов более высокого порядка Vue

наш обычный<transition>и<keep-alive>Это компонент более высокого порядка (абстрактный).

export default {
  name: 'keep-alive',
  abstract: true,
  ...
}

Все компоненты более высокого порядка (абстрактные) определяютсяabstractвозможность объявить. Компоненты более высокого порядка (абстрактные) не отображаются реальнымиDOM. Обычный абстрактный компонент записывается так:

import { xxx } from 'xxx'
const A = () => {
    .....
}

export default {
    name: 'xxx',
    abstract: true,
    props: ['...', '...'],
    // 生命周期钩子函数
    created () {
      ....
    },
    ....
    destroyed () {
      ....
    },
    render() {
        const vnode = this.$slots.default
        ....
        return vnode
    },
})

4.1 Абстрактные компоненты подавления/дросселирования

Я не буду вдаваться в подробности о том, что такое анти-шейк и троттлинг. Код компонента размещен здесь:

быть адаптированы из:Vue реализует функцию антивибрационного компонента

const throttle = function(fn, wait=50, isDebounce, ctx) {
  let timer
  let lastCall = 0
  return function (...params) {
    if (isDebounce) {
      if (timer) clearTimeout(timer)
      timer = setTimeout(() => {
        fn.apply(ctx, params)
      }, wait)
    } else {
      const now = new Date().getTime()
      if (now - lastCall < wait) return
      lastCall = now
      fn.apply(ctx, params)
    }
  }
}

export default {
    name: 'Throttle',
    abstract: true,
    props: {
      time: Number,
      events: String,
      isDebounce: {
        type: Boolean,
        default: false
      },
    },
    created () {
      this.eventKeys = this.events.split(',')
      this.originMap = {}
      this.throttledMap = {}
    },
    render() {
        const vnode = this.$slots.default[0]
        this.eventKeys.forEach((key) => {
            const target = vnode.data.on[key]
            if (target === this.originMap[key] && this.throttledMap[key]) {
                vnode.data.on[key] = this.throttledMap[key]
            } else if (target) {
                this.originMap[key] = target
                this.throttledMap[key] = throttle(target, this.time, this.isDebounce, vnode)
                vnode.data.on[key] = this.throttledMap[key]
            }
        })
        return vnode
    },
})

через третий параметрisDebounceДля управления переключением антивибрационного троттлинга. Наконец вmain.jsЦитата:

import Throttle from '../Throttle'
....
Vue.component('Throttle', Throttle)

Как пользоваться

<div id="app">
    <Throttle :time="1000" events="click">
        <button @click="onClick($event, 1)">click+1 {{val}}</button>
    </Throttle>
    <Throttle :time="1000" events="click" :isDebounce="true">
        <button @click="onAdd">click+3 {{val}}</button>
    </Throttle>
    <Throttle :time="3300" events="mouseleave" :isDebounce="true">
        <button @mouseleave.prevent="onAdd">click+3 {{val}}</button>
    </Throttle>
</div>
const app = new Vue({
    el: '#app',
    data () {
        return {
            val: 0
        }
    },
    methods: {
        onClick ($ev, val) {
            this.val += val
        },
        onAdd () {
            this.val += 3
        }
    }
})

Абстрактные компоненты — это хороший способ заменить Mixins для реализации общедоступных функций абстрактных компонентов, не загрязняя DOM из-за использования компонентов (добавление нежелательных тегов div и т. д.), обертывания любого отдельного дочернего элемента и т. д.

Что касается того, использовать ли абстрактные компоненты, это вопрос мнения.

5. Оптимизация производительности: инкапсуляция eventBus

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

иeventBusРеализация тоже очень простая

import Vue from 'Vue'
export default new Vue

Вещи, которые мы часто упускаем из виду при использовании и о которых нельзя забывать:очистить шину событийeventBus

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

Через несколько минут постоянной работы страница зависает и занимает много памяти.

Так вообще в жизненном цикле VuebeforeDestroyилиdestroyed, вам нужно использовать экземпляр vue$offметод ясноeventBus

beforeDestroy(){
    bus.$off('click')
 }

когда у тебя несколькоeventBusтребует повторяющейся работы$offУничтожь эту вещь. Теперь упакуйтеeventBusявляется лучшим решением.

5.1 Имеет жизненный циклeventBus

Мы начинаем с исходного кода VueVue.initможно найти в:

 Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    // a uid vm实例唯一标识
    vm._uid = uid++
    // ....
    }

Каждый экземпляр Vue имеет свой собственный_uidв качестве уникального идентификатора, поэтому мы позволяемEventBusи_uidСвяжите и трансформируйте его:

Реализация исходит из:Пусть EventBus, используемый в Vue, тоже имеет жизненный цикл

class EventBus {
  constructor (vue) {
    if (!this.handles) {
      Object.defineProperty(this, 'handles', {
        value: {},
        enumerable: false
      })
    }
    this.Vue = vue
    // _uid和EventName的映射
    this.eventMapUid = {}
  }
  setEventMapUid (uid, eventName) {
    if (!this.eventMapUid[uid]) this.eventMapUid[uid] = []
    this.eventMapUid[uid].push(eventName) // 把每个_uid订阅的事件名字push到各自uid所属的数组里
  }
  $on (eventName, callback, vm) {
    // vm是在组件内部使用时组件当前的this用于取_uid
    if (!this.handles[eventName]) this.handles[eventName] = []
    this.handles[eventName].push(callback)
    if (vm instanceof this.Vue) this.setEventMapUid(vm._uid, eventName)
  }
  $emit () {
    let args = [...arguments]
    let eventName = args[0]
    let params = args.slice(1)
    if (this.handles[eventName]) {
      let len = this.handles[eventName].length
      for (let i = 0; i < len; i++) {
        this.handles[eventName][i](...params)
      }
    }
  }
  $offVmEvent (uid) {
    let currentEvents = this.eventMapUid[uid] || []
    currentEvents.forEach(event => {
      this.$off(event)
    })
  }
  $off (eventName) {
    delete this.handles[eventName]
  }
}
// 写成Vue插件形式,直接引入然后Vue.use($EventBus)进行使用
let $EventBus = {}

$EventBus.install = (Vue, option) => {
  Vue.prototype.$eventBus = new EventBus(Vue)
  Vue.mixin({
    beforeDestroy () {
      // 拦截beforeDestroy钩子自动销毁自身所有订阅的事件
      this.$eventBus.$offVmEvent(this._uid) 
    }
  })
}

export default $EventBus

использовать:

// main.js中
...
import EventBus from './eventBus.js'
Vue.use(EnemtBus)
...

Используемые компоненты:

 created () {
    let text = Array(1000000).fill('xxx').join(',')
    this.$eventBus.$on('home-on', (...args) => {
      console.log('home $on====>>>', ...args)
      this.text = text
    }, this) // 注意第三个参数需要传当前组件的this,如果不传则需要手动销毁
  },
  mounted () {
    setTimeout(() => {
      this.$eventBus.$emit('home-on', '这是home $emit参数', 'ee')
    }, 1000)
  },
  beforeDestroy () {
    // 这里就不需要手动的off销毁eventBus订阅的事件了
  }

6. Плагин webpack: действительно ароматный

6.1 ЗаменаuglifyjsизTerser Plugin

Я столкнулся с проблемой, когда проект обновил Vue-cli3 в начале февраля:uglifyjswebpack4.0 больше не поддерживается. Я огляделся, вGoogleнашел в поискеTerser Pluginэтот плагин.

Я в основном использую эти функции:

  • cache, чтобы включить кэширование файлов.
  • parallel, используя многопроцессный параллелизм для увеличения скорости сборки.
  • sourceMap, который сопоставляет местоположения сообщений об ошибках с модулями (где хранится информация о местоположении).
  • drop_console, исключая все при упаковкеconsoleутверждение
  • drop_debugger, исключая все при упаковкеdebuggerутверждение

Как ленивый B, который управляет интерфейсом команды, много раз написание страниц оставляет позадиconsole.log, что влияет на производительность. настраиватьdrop_consoleОн очень ароматный. Следующая конфигурация действительна для профессионального теста.

const TerserPlugin = require('terser-webpack-plugin')
....
new TerserPlugin({
cache: true,
parallel: true,
sourceMap: true, // Must be set to true if using source-maps in production
terserOptions: {
  compress: {
    drop_console: true,
    drop_debugger: true
  }
}
})

Дополнительные сведения о конфигурации см.Terser Plugin

6.2 Откройте gzip с обеих сторон

Каковы преимущества включения сжатия gzip?

Размер файла может быть уменьшен, а скорость передачи выше. gzip — это эффективный способ сэкономить трафик и ускорить работу вашего сайта.

  • Content-Encoding: gzip можно настроить, когда сервер отправляет данные, а пользователь указывает метод сжатия данных.
  • После того, как клиент получает данные, он проверяет информацию о соответствующем поле и может декодировать ее в соответствии с соответствующим форматом.
  • Когда клиент запрашивает, можно использовать Accept-Encoding: gzip, и пользователь указывает, какие методы сжатия принимаются.

6.2.1 Webpackвключиgzip

Здесь используются следующие плагины:CompressionWebpackPlugin

const CompressionWebpackPlugin = require('compression-webpack-plugin')
module.exports = { 
    “plugins”:[new CompressionWebpackPlugin] 
}

Конкретная конфигурация:

const CompressionWebpackPlugin = require('compression-webpack-plugin');

webpackConfig.plugins.push(
    new CompressionWebpackPlugin({
      asset: '[path].gz[query]',
      algorithm: 'gzip',
      test: new RegExp('\\.(js|css)$'),
      // 只处理大于xx字节 的文件,默认:0
      threshold: 10240,
      // 示例:一个1024b大小的文件,压缩后大小为768b,minRatio : 0.75
      minRatio: 0.8 // 默认: 0.8
      // 是否删除源文件,默认: false
      deleteOriginalAssets: false
    })
)

开启gzip前:

Перед включением gzip

После включения gzip Размер в сжатом виде увеличился с 277 КБ до всего ~ 91,2 КБ!

6.2.2 Расширение знаний:Nginxизgzipнастраивать

Открытым/etc/nginx/conf.dНапишите следующую конфигурацию.

server {
    gzip on;
    gzip_static on;    
    gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
    gzip_proxied  any;
    gzip_vary on;
    gzip_comp_level 6;
    gzip_buffers 16 8k;
    gzip_http_version 1.1;    
    ...
}

Nginxпопробуй найти и отправить файл/path/to/bundle.js.gz. Если файл не существует или клиент не поддерживаетgzip, Nginx отправит несжатую версию файла.

После сохранения конфигурации перезагружаемсяNginx:

$ sudo service nginx restart

Перед включением gzip

После включения gzip

6.2.3 Как проверитьgzip?

используяcurlПротестируйте ответ на запрос для каждого ресурса и проверьтеContent-Encoding:

показыватьContent-Encoding: gzip, которыйНастроено успешно.

6.2.4 Отличие и значение двустороннего Gzip

Разница в следующем:

  1. WebpackСжатие сжимает файлы один раз во время сборки, а затем сохраняет эти сжатые версии на диск.

  2. nginxНекоторые пакеты могут иметь встроенное кэширование при сжатии файлов по запросу, поэтому снижение производительности происходит только один раз (или нечасто), но обычно разница в том, что это происходит при ответе на HTTP-запрос.

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

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

  5. Примечание. В зависимости от бизнес-конъюнктуры проекта требуется сжатие в режиме реального времени во время запроса или создание сжатых файлов во время построения.

Интерполяция ищет Шэньчжэнь

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

Хорошо, давайте закончим еще одну статью и перейдем к теме:

В настоящее время я (и) готовлюсь к смене работы. Надеюсь, что вы и дамы из отдела кадров сможете предложить надежную работу в Шэньчжэне!996.ICUНе бери в голову.

  • WeChat:huab119
  • Почта:454274033@qq.com

Сборник статей автора Nuggets

публика