Содержание этой статьи
- Обработка интерфейсного модуля
- Динамическая регистрация компонента Vue
- Отладка производительности страницы: Hiper
- Инкапсуляция компонентов более высокого порядка Vue
- Оптимизация производительности: инкапсуляция eventBus
- плагин webpack: действительно ароматный
Проект в этой статье основан на 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. Автоматическая глобальная регистрация основных компонентов
Официальная документация: Автоматизированная глобальная регистрация базовых компонентов
Когда мы пишем компоненты, нам обычно нужно вводить дополнительные компоненты:
<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
Нет смысла заниматься отладкой).
После того, как мы разработаем проект или оптимизируем производительность проекта, как мы оценим, соответствует ли производительность проекта стандарту?
Наш общий путь в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 в начале февраля:uglifyjs
webpack4.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
Размер в сжатом виде увеличился с 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
После включения gzip6.2.3 Как проверитьgzip
?
используяcurl
Протестируйте ответ на запрос для каждого ресурса и проверьтеContent-Encoding
:
показыватьContent-Encoding: gzip
, которыйНастроено успешно.
6.2.4 Отличие и значение двустороннего Gzip
Разница в следующем:
-
Webpack
Сжатие сжимает файлы один раз во время сборки, а затем сохраняет эти сжатые версии на диск. -
nginx
Некоторые пакеты могут иметь встроенное кэширование при сжатии файлов по запросу, поэтому снижение производительности происходит только один раз (или нечасто), но обычно разница в том, что это происходит при ответе на HTTP-запрос. -
Для сжатия в реальном времени часто бывает более эффективно, чтобы восходящие прокси-серверы (такие как Nginx) обрабатывали gzip и кеширование, поскольку они специально созданы для этого и не страдают от накладных расходов времени выполнения программ на стороне сервера (многие из них написаны на С) .
-
использовать
Webpack的
польза в том,Nginx
Каждый раз, когда серверу требуется длительное время для сжатия перед возвратом информации, не только значительно увеличиваются накладные расходы сервера, но и запрашивающий также проявляет нетерпение. мы вWebpack
При упаковке файлы с высоким уровнем сжатия генерируются напрямую и размещаются на сервере как статические ресурсы.Nginx
Это будет намного эффективнее в качестве двойной гарантии (при запросе других ресурсов каталога). -
Примечание. В зависимости от бизнес-конъюнктуры проекта требуется сжатие в режиме реального времени во время запроса или создание сжатых файлов во время построения.
Интерполяция ищет Шэньчжэнь
Изначально я хотел поблагодарить вас за форму динамической конфигурации, но она была слишком длинной и слишком сложной для написания.
Хорошо, давайте закончим еще одну статью и перейдем к теме:
В настоящее время я (и) готовлюсь к смене работы. Надеюсь, что вы и дамы из отдела кадров сможете предложить надежную работу в Шэньчжэне!996.ICU
Не бери в голову.
- WeChat:
huab119
- Почта:
454274033@qq.com
Сборник статей автора Nuggets
- «Практика Vue» — плагин Vue CLI за 5 минут
- Вооружите свой интерфейсный проект «Практикой Vue»
- «Intermediate and Advanced Front-End Interview» JavaScript Рукописный код Invincible Cheats
- «Узнайте из исходного кода» ответы на вопросы Vue, которые интервьюеры не знают
- JS-операция «Узнать из исходного кода» в исходном коде Vue
- «Учитесь на исходном коде», полностью разбирайтесь в параметрах Vue.
- Правильная позиция для обновления vue-cli3 в проекте "Vue Practice"
- Почему вы до сих пор не можете понять цепочку областей видимости JavaScript?