vue.js - Nuxt 巨大的内存使用/泄漏以及如何防止
问题描述
我在 Nuxt v2.13 和 Vuetify v2 上,也在我的默认布局中使用 keep-alive。随着我的应用程序变得越来越大,我越来越注意到内存问题,因此我的应用程序需要在云服务器上至少有大约 4GB 的 RAM 才能构建并正常工作。我四处挖掘,发现零散的碎片,所以决定分享它们并讨论解决方案。
请根据他们的#numbers回答每一个
#1 - NuxtLink (vue-router) 内存泄漏 : 其他人发现 vue-router 可能存在泄漏;也因为与 nuxt-link 关联的 DOM 将被预取,因此内存中的使用率也可能很高。所以有人建议像这样使用 html 锚而不是 nuxt-link:
<template>
<a href="/mypage" @click.prevent="goTo('mypage')">my page link</a>
</template>
<script>
export default{
methods:{
goTo(link){
this.$router.push(link)
}
}
}
</script>
您如何看待这种方法?Vuetifyto
道具像 nuxt-link 一样工作吗?
<template>
<v-card to="/mypage" ></v-card>
</template>
#2 - 动态组件加载:
由于我的应用程序是双向的并且可以通过.env
文件自定义,我不得不像这样动态和有条件地延迟加载我的许多组件:
<template>
<component :is="mycomp" />
</template>
<script>
export default{
computed:{
mycomp(){
return import()=>(`@/components/${process.env.SITE_DIR}/mycomp.vue`)
}
}
}
</script>
这会导致高内存使用/泄漏吗?
# 3 - Nuxt 事件总线:
除了this.$emit()
在我的组件中正常之外,有时我不得不使用$nuxt.$emit()
. 我将它们全部删除beforeDestroy
:
<script>
export default{
created:{
this.$nuxt.$on('myevent', ()=>{
// do something
}
},
beforeDestroy(){
this.$nuxt.$off('myevent')
}
}
</script>
但有人告诉我,created
挂机的听众将是 SSR,不会在 CSRbeforeDestroy
挂机中被删除。所以我该怎么做?添加if(process.client){}
到created
??
#4 - 全局插件:
我发现了这个问题,也发现了这个文档。如本问题所述,我在全球范围内添加了我的插件/包。那么vue.use()
有问题吗?我应该inject
改用吗?如何?
// vue-product-zoomer package
import Vue from 'vue'
import ProductZoomer from 'vue-product-zoomer'
Vue.use(ProductZoomer)
# 5 - Vee Validate泄漏:我在这里 读到过,这真的会导致泄漏吗?我正在使用 Vee Validate v3:
我的 veevalidate.js 全局添加到 nuxt.config.js
import Vue from 'vue'
import { ValidationObserver, ValidationProvider, setInteractionMode } from 'vee-validate'
import { localize } from 'vee-validate';
import en from 'vee-validate/dist/locale/en.json';
import fa from 'vee-validate/dist/locale/fa.json';
localize({
en,
fa
});
setInteractionMode('eager')
let LOCALE = "fa";
Object.defineProperty(Vue.prototype, "locale", {
configurable: true,
get() {
return LOCALE;
},
set(val) {
LOCALE = val;
localize(val);
}
});
Vue.component('ValidationProvider', ValidationProvider);
Vue.component("ValidationObserver", ValidationObserver);
添加到每个页面/组件的我的 veevalidate mixin 使用了 veevalidate 。(我使用了 mixin,因为我需要使用我的 vuex 状态lang
)
import { required, email , alpha , alpha_spaces , numeric , confirmed , password } from 'vee-validate/dist/rules'
import { extend } from 'vee-validate'
export default {
mounted() {
extend("required", {
...required,
message: `{_field_} ${this.lang.error_required}`
});
extend("email", {
...email,
message: `{_field_} ${this.lang.error_email}`
});
extend("alpha", {
...alpha,
message: `{_field_} ${this.lang.error_alpha}`
});
extend("alpha_spaces", {
...alpha_spaces,
message: `{_field_} ${this.lang.error_alpha_spaces}`
});
extend("numeric", {
...numeric,
message: `{_field_} ${this.lang.error_numeric}`
});
extend("confirmed", {
...confirmed,
message: `{_field_} ${this.lang.error_confirmed}`
});
extend("decimal", {
validate: (value, { decimals = '*', separator = '.' } = {}) => {
if (value === null || value === undefined || value === '') {
return {
valid: false
};
}
if (Number(decimals) === 0) {
return {
valid: /^-?\d*$/.test(value),
};
}
const regexPart = decimals === '*' ? '+' : `{1,${decimals}}`;
const regex = new RegExp(`^[-+]?\\d*(\\${separator}\\d${regexPart})?([eE]{1}[-]?\\d+)?$`);
return {
valid: regex.test(value),
};
},
message: `{_field_} ${this.lang.error_decimal}`
})
}
}
# 6 - Keep-Alive : 正如我之前提到的,我在我的应用程序中使用了 keep-alive,它会自动缓存很多东西,并且可能不会破坏/删除插件和事件侦听器。
# 7 - setTimeout : 是否需要使用 clearTimeout 进行数据清除?
# 8 - Remove Plugins/Packages : 在这个文档中提到一些插件/包即使在组件被销毁后也不会被删除,我怎样才能找到那些?
这是我的包和 nuxt.config
// package.json
{
"name": "nuxt",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "nuxt",
"build": "nuxt build",
"start": "nuxt start",
"generate": "nuxt generate"
},
"dependencies": {
"@nuxt/http": "^0.6.0",
"@nuxtjs/auth": "^4.9.1",
"@nuxtjs/axios": "^5.11.0",
"@nuxtjs/device": "^1.2.7",
"@nuxtjs/google-gtag": "^1.0.4",
"@nuxtjs/gtm": "^2.4.0",
"chart.js": "^2.9.3",
"cookie-universal-nuxt": "^2.1.4",
"jquery": "^3.5.1",
"less-loader": "^6.1.2",
"nuxt": "^2.13.0",
"nuxt-user-agent": "^1.2.2",
"v-viewer": "^1.5.1",
"vee-validate": "^3.3.7",
"vue-chartjs": "^3.5.0",
"vue-cropperjs": "^4.1.0",
"vue-easy-dnd": "^1.10.2",
"vue-glide-js": "^1.3.14",
"vue-persian-datetime-picker": "^2.2.0",
"vue-product-zoomer": "^3.0.1",
"vue-slick-carousel": "^1.0.6",
"vue-sweetalert2": "^3.0.5",
"vue2-editor": "^2.10.2",
"vuedraggable": "^2.24.0",
"vuetify": "^2.3.9"
},
"devDependencies": {
"@fortawesome/fontawesome-free": "^5.15.1",
"@mdi/font": "^5.9.55",
"@nuxtjs/dotenv": "^1.4.1",
"css-loader": "^3.6.0",
"flipclock": "^0.10.8",
"font-awesome": "^4.7.0",
"node-sass": "^4.14.1",
"noty": "^3.2.0-beta",
"nuxt-gsap-module": "^1.2.1",
"sass-loader": "^8.0.2"
}
}
//nuxt.config.js
const env = require('dotenv').config()
const webpack = require('webpack')
export default {
mode: 'universal',
loading: {
color: 'green',
failedColor: 'red',
height: '3px'
},
router: {
// base: process.env.NUXT_BASE_URL || '/'
},
head: {
title: process.env.SITE_TITLE + ' | ' + process.env.SITE_SHORT_DESC || '',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'keywords', name: 'keywords', content: process.env.SITE_KEYWORDS || '' },
{ hid: 'description', name: 'description', content: process.env.SITE_DESCRIPTION || '' },
{ hid: 'robots', name: 'robots', content: process.env.SITE_ROBOTS || '' },
{ hid: 'googlebot', name: 'googlebot', content: process.env.SITE_GOOGLE_BOT || '' },
{ hid: 'bingbot', name: 'bingbot', content: process.env.SITE_BING_BOT || '' },
{ hid: 'og:locale', name: 'og:locale', content: process.env.SITE_OG_LOCALE || '' },
{ hid: 'og:type', name: 'og:type', content: process.env.SITE_OG_TYPE || '' },
{ hid: 'og:title', name: 'og:title', content: process.env.SITE_OG_TITLE || '' },
{ hid: 'og:description', name: 'og:description', content: process.env.SITE_OG_DESCRIPTION || '' },
{ hid: 'og:url', name: 'og:url', content: process.env.SITE_OG_URL || '' },
{ hid: 'og:site_name', name: 'og:site_name', content: process.env.SITE_OG_SITENAME || '' },
{ hid: 'theme-color', name: 'theme-color', content: process.env.SITE_THEME_COLOR || '' },
{ hid: 'msapplication-navbutton-color', name: 'msapplication-navbutton-color', content: process.env.SITE_MSAPP_NAVBTN_COLOR || '' },
{ hid: 'apple-mobile-web-app-status-bar-style', name: 'apple-mobile-web-app-status-bar-style', content: process.env.SITE_APPLE_WM_STATUSBAR_STYLE || '' },
{ hid: 'X-UA-Compatible', 'http-equiv': 'X-UA-Compatible', content: process.env.SITE_X_UA_Compatible || '' }
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: process.env.SITE_FAVICON },
// { rel: 'shortcut icon', type: 'image/x-icon', href: process.env.SITE_FAVICON },
{ rel: 'canonical', href: process.env.SITE_REL_CANONICAL },
// { rel: 'stylesheet', href: 'https://cdn.jsdelivr.net/npm/font-awesome@4.x/css/font-awesome.min.css' },
]
},
css: [
'~/assets/scss/style.scss',
'~/assets/scss/media.scss',
'~/assets/scss/customization.scss',
'~/assets/scss/sweetalert.scss',
'~/assets/scss/noty.scss',
'~/assets/scss/flipclock.scss',
'~/assets/scss/glide.scss',
'~/assets/scss/sorting.scss',
'~/assets/scss/cropper.scss',
'~/assets/scss/transitions.scss',
'~/assets/scss/product-zoom.scss',
'vue-slick-carousel/dist/vue-slick-carousel.css'
],
plugins: [
'plugins/mixins/reqerrors.js',
'plugins/mixins/user.js',
'plugins/mixins/language.js',
'plugins/mixins/shopinfo.js',
'plugins/mixins/formattedprice.js',
'plugins/mixins/utils.js',
'plugins/mixins/cms.js',
'plugins/mixins/client.js',
'plugins/mixins/cart.js',
'plugins/axios.js',
'plugins/veevalidate.js',
'plugins/noty.js',
'plugins/glide.js',
'@plugins/vuetify',
'@plugins/vuedraggable',
'@plugins/vuedraggable',
'@plugins/vue-slick-carousel.js',
{src: 'plugins/vuepersiandatepicker.js', mode: 'client'},
{src: 'plugins/cropper.js', mode: 'client'},
{src: 'plugins/vue-product-zoomer.js', mode: 'client'},
{src: 'plugins/vueeditor.js', mode: 'client'},
],
buildModules: [
'@nuxtjs/dotenv',
'nuxt-gsap-module'
],
modules: [
'@nuxtjs/axios',
'@nuxtjs/auth',
'@nuxtjs/device',
['vue-sweetalert2/nuxt',
{
confirmButtonColor: '#29BF12',
cancelButtonColor: '#FF3333'
}
],
'cookie-universal-nuxt',
'@nuxtjs/gtm',
'@nuxtjs/google-gtag',
'nuxt-user-agent',
],
gtm: {
id: process.env.GOOGLE_TAGS_ID,
debug: false
},
'google-gtag': {
id: process.env.GOOGLE_ANALYTICS_ID,
debug: false
},
gsap: {
extraPlugins: {
cssRule: false,
draggable: false,
easel: false,
motionPath: false,
pixi: false,
text: false,
scrollTo: false,
scrollTrigger: false
},
extraEases: {
expoScaleEase: false,
roughEase: false,
slowMo: true,
}
},
axios: {
baseURL: process.env.BASE_URL,
},
auth: {
strategies: {
local: {
endpoints: {
login: { url: 'auth/login', method: 'post', propertyName: 'token' },
logout: { url: 'auth/logout', method: 'post' },
user: { url: 'auth/info', method: 'get', propertyName: '' }
}
}
},
redirect: {
login: '/login',
home: '',
logout: '/login'
},
cookie: {
prefix: 'auth.',
options: {
path: '/',
maxAge: process.env.AUTH_COOKIE_MAX_AGE
}
}
},
publicRuntimeConfig: {
gtm: {
id: process.env.GOOGLE_TAGS_ID
},
'google-gtag': {
id: process.env.GOOGLE_ANALYTICS_ID,
}
},
build: {
transpile: ['vee-validate/dist/rules'],
plugins: [
new webpack.ProvidePlugin({
'$': 'jquery',
jQuery: "jquery",
"window.jQuery": "jquery",
'_': 'lodash'
}),
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
],
postcss: {
preset: {
features: {
customProperties: false,
},
},
},
loaders: {
scss: {
prependData: `$theme_colors: ("theme_body_color":"${process.env.THEME_BODY_COLOR}","theme_main_color":"${process.env.THEME_MAIN_COLOR}","theme_main_color2":"${process.env.THEME_MAIN_COLOR2}","theme_side_color":"${process.env.THEME_SIDE_COLOR}","theme_side_color2":"${process.env.THEME_SIDE_COLOR2}","theme_link_color":"${process.env.THEME_LINK_COLOR}");`
}
},
}
}
解决方案
我认为是时候分享我的理解了(尽管它很少):
#1 作为vue-router
使用预取,内存使用量可能会很大,具体取决于链接的数量。在我的情况下,没有太多,所以我让它们,还有一个选项可以在 nuxt 中禁用预取,所以如果您的应用程序非常繁忙或者您在单个页面中有数百个链接,最好禁用预取:
// locally
<nuxt-link to="/" no-prefetch>link</nuxt-link>
// globally in nuxt.config.js
router: {
prefetchLinks: false
}
#2 我没有发现动态组件有任何问题
#3 没有,但我在钩子中$nuxt.$on
使用时经历过它(事件侦听器没有被删除) 。所以最好尽可能将所有侦听器移动到客户端(安装前或安装前)window.addEventListener
created
#4 正如我在上面的评论中提到的,我尽可能多地删除了全局插件/css,以实现更轻的初始化并在本地使用它们,但是关于Vue.use()
内存泄漏,这是我的误解!在 nuxt doc 中说:
不要使用 Vue.use()、Vue.component() 和全局,不要在这个函数内插入任何 Vue 中的东西,专门用于 Nuxt 注入。它会导致服务器端的内存泄漏。
所以使用Vue.use()
内部注入函数可能会导致内存泄漏而不是Vue.use()
本身。
至于其他人仍然没有答案
推荐阅读
- c# - 如何在不显示物理路径的情况下显示文件夹中的文件?
- android - 在 Flutter 中播放 Vimeo 视频
- reactjs - 在使用 redux 处理输入表单时使用 useState Hook 是一种反模式吗?
- python - 从列表中查找总和和平均值?
- python - 从 Python 中的字符串中提取元素
- graphql - 如何从 Apollo Server 端点获得完整的 GraphQL 架构?
- go - Go 代码在本地环境中的运行结果与在 go play 中运行的结果不同
- javascript - TestCafe:如何选择具有某种类模式的第一个 DOM 项?
- javascript - 根据窗口大小调整 video.js 的大小
- variables - 如何在 Gitlab 中安排不同时间的作业