吃透 Vue 项目开发实践|16个方面深入前端工程化开发技巧【下】
zhezhongyun 2025-10-14 08:33 5 浏览
前言
前面两篇文章总结了 Vue 开发的大部分技巧和内容,最后一篇文章来对它进行一个收尾
这篇文章我们来谈谈一些 Vue 理解和实践要求高一点的问题
首先是生命周期这一块内容,随着实践越多它的意义越大,理解也越深刻
mixin 功能强大,对代码复用组织都有很高的要求,算是 Vue 后期发力的高级技巧
服务端渲染可能是学习 Vue 最后一块阵地了,对于 SPA 框架的一个里程碑
最后,总结一下我在使用 Vue 中使用的技巧和经验
常规操作,先点赞后观看哦!你的点赞是我创作的动力之一!
前情提要
我将从 16 个方面来论述 Vue 开发过程中的一些技巧和原理。如果你还未观看上节文章,可以移步至
本篇概览
Vue 生命周期
什么是 Vue 生命周期?
Vue 生命周期大概就是:一个从 Vue 实例的创建到组件销毁的一个的过程。
具体情况下,我们分为几个核心的阶段,并且每个阶段都有一套钩子函数来执行我们需要的代码。
生命周期阶段与钩子
我们整理分类一下这些生命周期钩子,为了记忆方便分为 4 大核心阶段:
方便读者记忆,这里尽量使用图示:
可以看到每一个阶段中的钩子命名都很好记忆,阶段开始前使用 beforeXxx,阶段后结束后使用xxxed
除这 8 个核心钩子,另外还有 3 个新增功能型钩子,目前总共是 11 个钩子 顺带提一下这 3 个钩子的功能
- 组件缓存,activated 与 deactivated,这两个钩子也是一对的,分别表示被 keep-alive 缓存的组件激活和停用时调用。
- 组件错误捕获,errorCaptured,对组件中出现对异常错误进行处理,使用较少。
图解生命周期
我们看看官方的图解,在 Vue 教程实例这节 官方教程直接跳转
官方图解
官方图解并没有详细解释这张图。我猜一方面原因是这个图里面涉及的细节都是在 vue 源码里面体现,要真正解释起来也没那么简单。另一方面是希望我们多在实践中去理解里面的意义。
Vue 源码基本流程
对于上面那张图的理解,我们需要对 Vue 源码进行梳理,才能真正的理解。大概根据现有的源码,我梳理了一下大致的流程:
我们可以清楚地看到,从 Vue 实例创建、组件挂载、渲染的一些过程中,有着明显的周期节点。
简化文字图解
结合上面源码的流程和相关实践,简化每一个阶段做了哪些时期,每一个钩子里面是组件处于什么状态。
实践验证一下生命周期
提出问题
下面我们提出一些问题:
- 什么时期创建 el ?
- 什么时期挂载 data ?
- 什么时期可以访问 dom ?
- 什么情况下组件会更新?更新是同步更新还是异步更新?
- 什么情况下组件会被销毁?
- 销毁组件后,还可以访问哪些内容?
编写代码
- 首先写一个小 demo,打印关键组件信息
<template>
<div>
<div class="message">
{{message}}
</div>
</div>
</template>
<script>export default {
data() {
return {
message: '1'
}
},
methods: {
printComponentInfo(lifeName) {
console.log(lifeName)
console.log('el', this.$el)
console.log('data', this.$data)
console.log('watch', this.$watch)
}
}
}</script>
复制代码
- 增加核心的 8 个生命周期钩子,分别调用打印方法
// ...
beforeCreate() {
this.printComponentInfo('beforeCreate')
},
created() {
this.printComponentInfo('created')
},
beforeMount() {
this.printComponentInfo('beforeMount')
},
mounted() {
this.printComponentInfo('mounted')
},
beforeUpdate() {
this.printComponentInfo('beforeUpdate')
},
updated() {
this.printComponentInfo('updated')
},
beforeDestroy() {
this.printComponentInfo('beforeDestroy')
},
destroyed() {
this.printComponentInfo('destroyed')
},
// ...
复制代码
创建阶段
beforeCreate 中methods中方法直接报错无法访问,直接访问 el 和 data 后
发现只能访问到 watch, el 和 data 均不能访问到
created 时期 el 无法访问到,但是可以访问到 data 了
挂载阶段
beforeMount 中可以访问 data 但是仍然访问不到 el
mounted 中可以访问到 el 了
首次加载页面,更新阶段和销毁阶段到钩子都未触发
更新阶段
我们增加一行代码
this.message = this.message + 1
复制代码
如果增加在 created 阶段,发现 update钩子仍然未触发,但是 el 和 data 的值都变成了 2
如果增加在 mounted 阶段,发现 update钩子此时触发了
销毁阶段
怎样触发销毁的钩子呢? 大概有这几种方法
- 手动调用 $destory
- v-if 与 v-for 指令,(v-show 不行)
- 路由切换和关闭或刷新浏览器 我们在 mounted 钩子里面增加一行代码手动销毁当前组件,或者跳转路由
this.$destory('lifecycle')
复制代码
发现beforeDestory 和 destoryed 都触发了,而且el、data都一样还是可以访问到
生命周期钩子常见使用的场景
beforeCreate 谨慎操作 this
beforeCreate 无法访问到 this 中的 data、method
// 错误实例
beforeCreate() {
// 允许
console.log('ok')
// 不允许
this.print() // 报错找不到
this.message = 1 // 报错找不到
}
复制代码
请求应放在 created 钩子中
created 可以访问 this,但无法访问 dom,dom 未挂载
created() {
// 允许并推荐
this.$http.get(xxx).then(res => {
this.data = res.data
})
// 不允许
this.$el
this.$ref.demo
const a = document.getElementById('demo')
}
复制代码
操作 DOM 代码应放在 mounted 钩子中
mounted 已经挂载 dom,可以访问 this
mounted() {
// 允许
this.$el
this.$ref.demo
let a = document.getElementById('')
}
复制代码
生命周期相关demo 代码见github-lifecycle-demo
理解并合理使用 mixin
什么是 mixin(混入)
当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项 大致原理就是将外来的组件、方法以某种方式进行合并。合并的规则有点像继承和扩展。
当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”
我们看一下一个组件里面有哪些东西是可以合并的
// mixins/demo
export default {
data() {
return {}
},
mounted() {},
methods: {},
computed: {},
components: {},
directives: {}
}
复制代码
data、methods、computed、directives、components 生命周期钩子
没错这些都可以混入
import demoMixin form '@/mixins/demo'
export default {
mixins: [demoMixin]
}
复制代码
这样看来,很多页面重复的代码我们都可以直接抽取出来
或者是封装成一个公共的 mixin
比如我们做 H5 页面,里面很多短信验证的逻辑固有逻辑,但是需要访问到 this。使用工具函数肯定不行。
这时候就可以考虑使用 mixin,封装成一个具有响应式的模块。供需要的地方进行引入。
mixin 规则
首先是优先级的问题,当重名选项时选择哪一个为最后的结果
默认规则我这里分为 3 类
- data 混入: 以当前组件值为最后的值
- 生命周期钩子: 保留所有钩子,先执行 mixins 的,后执行当前组件的
- methods、computed、directives、components 这种健值对形式,同名key,统统以当前组件为准
当然如果想改变规则,也可以通过配置来改变规则
Vue.config.optionMergeStrategies.myOption = function (toVal, fromVal) {
// 返回合并后的值
}
复制代码
mixin 的好处
我们知道 Vue 最能复用代码的就是组件。一般情况,我们通过 props 来控制组件的,将原有组件封装成 HOC 高阶组件。而控制 props 的生成不一样的功能的代码还是写在基础组件里。
<template lang="pug">
div.dib
van-button.btn(
@click="$emit('click')"
:class="getClass" v-bind="$attrs"
:style="{'width': size === 'large' ? '345px': '', 'backgroundColor': getBgColor, borderColor: getBgColor, color: getBgColor}")
slot
</template>
<script>import { Button } from 'vant'
import Vue from 'vue'
import { getColor } from '@/utils'
Vue.use(Button)
export default {
name: 'app-button',
props: {
type: {
type: String,
default: 'primary'
},
theme: {
type: String,
default: 'blue'
},
size: {
type: String,
default: ''
}
}
}复制代码
以这个组件为例,我们还是通过公共组件内部的逻辑,来改变组件的行为。
但是,使用 mixin 提供了另一个思路。我们写好公共的 mixin,每一个需要使用 mixin 的地方。我们进行扩展合并,不同与公共 mixin 的选项我们在当前组件中进行自定义,也就是扩展。我们新的逻辑是写在当前组件里面的,而非公共 mixins 里。
画个图理解一下:
最后总结一下 mixin 的优点
- 复用代码,公共逻辑抽离
- 可以访问 this, 可以操作响应式代码
- mixins 相对与组件来说,更像扩展
说了这么多那 mixins 有什么坏处呢?
第一,千万不能滥用全局 mixins 因为会影响所有多子组件
第二,由于 mixins 的合并策略固有影响,可能在一些极端情况达不到你想要的效果。
比如:我已经存在一个 mixins,我的页面里面也有一些方法,我要引入mixins 就要做很多改动,来保证我的代码按我的需要运行。
页面 data 有一个 message 值,mixins 里面同样有一个。
按照默认规则,mixins 里面的 message 会被页面里面 message 覆盖。但是这两个 message 可能代表的意义不一样,都需要存在。那么我就需要改掉其中的一个,如果业务太深的话,可能这个 message 没那么好改。
有时候需要考虑这些问题,导致使用 mixins 都会增加一些开发负担。当然也是这些问题可以使用规范来规避。
理解并使用 SSR
SSR 是 Serve Side Render 的缩写,翻译过来就是我们常说的服务端渲染
简单归纳 SSR 存在的原因
- SPA 框架 SEO 的解决方案
- 提升首屏加载速度
但是它还存在以下问题
- 有些生命周期钩子无法使用(之前提到的 activated 和 deactivated 等等)
- 额外很多的配置
- 服务器资源的需求
总得来说,SSR 是必要的但不是充分的,SPA 的 SEO 现在没有更好的方案,有这方面强烈需求的网站来说,SSR 确实很有必要
SSR 原理
通过上面图我们可以得大致几点内容
- 通用业务代码只写一套。
- 通过暴露客户端和服务端两个入口,对 Vue 实例进行访问。
- 通过构建工具打成两个包,服务端包通过 node 服务器渲染给浏览器访问。客户端和原来一样通过访问资源获取。
实现SSR 有 3 种方式
1. 根据官网教程搭建,一步一步搭建SSR, 这里有具体教程,略微有点麻烦,但是能够体验搭建的过程,更加深入细节。使用于练习,和现有项目的改造
2. 使用demo改造,开源 demo,方便省时,适用代码参考学习 vue-ssr-demo
3. 使用nuxt,预设了 SSR 和预渲染,适用于需要 SSR 的新项目。
分别尝试用这 3 种方式搭建 SSR
五步简单理解SSR(根据官网教程,具体完操作查看整教程和demo)
这里主要加深理解,vue-cli3+ 实现基本 SSR
第一步:安装依赖
- vue-server-renderer (核心依赖,版本必须与 vue 版本一致)
- webpack-merge(用于webpack配置合并)
- webpack-node-externals (用于webpack配置更改)
- express (用于服务端渲染)
第二步:建立入口,并改造改造
分为 2 个入口,将 main.js 定为通用入口, 并额外增加entry-client.js 和 entry-serve.js 两个
1.改造主要入口,创建工厂函数
// main.js
import Vue from 'vue'
import App from './App.vue'
import { createRouter } from "./router"
// app、router
export function createApp () {
const router = createRouter()
const app = new Vue({
router,
render: h => h(App)
})
return { app, router }
}
复制代码
2.客户端入口
// client.js
import { createApp } from './main'
// 客户端特定引导逻辑
const { app } = createApp()
app.$mount('#app')
复制代码
3.服务端入口
// serve.js
import { createApp } from "./main";
export default context => {
// 因为有可能会是异步路由钩子函数或组件,所以我们将返回一个 Promise
return new Promise((resolve, reject) => {
const { app, router } = createApp();
// 设置服务器端 router 的位置
router.push(context.url);
// 等到 router 将可能的异步组件和钩子函数解析完
router.onReady(() => {
const matchedComponents = router.getMatchedComponents();
// 匹配不到的路由,执行 reject 函数
if (!matchedComponents.length) {
return reject({
code: 404
});
}
// Promise 应该 resolve 应用程序实例,以便它可以渲染
resolve(app);
}, reject);
});
};
复制代码
第三步:改造 vue.config 配置
const VueSSRServerPlugin = require("vue-server-renderer/server-plugin");
const VueSSRClientPlugin = require("vue-server-renderer/client-plugin");
const nodeExternals = require("webpack-node-externals");
const merge = require("webpack-merge");
const TARGET_NODE = process.env.WEBPACK_TARGET === "node";
const target = TARGET_NODE ? "server" : "client";
module.exports = {
configureWebpack: () => ({
entry: `./src/entry-${target}.js`,
devtool: 'source-map',
target: TARGET_NODE ? "node" : "web",
node: TARGET_NODE ? undefined : false,
output: {
libraryTarget: TARGET_NODE ? "commonjs2" : undefined
},
externals: TARGET_NODE
? nodeExternals({
whitelist: [/\.css$/]
})
: undefined,
optimization: {
splitChunks: undefined
},
plugins: [TARGET_NODE ? new VueSSRServerPlugin() : new VueSSRClientPlugin()]
}),
//...
};
复制代码
第四步:对路由 router 改造
export function createRouter(){
return new Router({
mode: 'history',
routes: [
//...
]
})
}
复制代码
第五步:使用 express 运行服务端代码
这一步主要是让 node 服务端响应 HTML 给浏览器访问
const Vue = require('vue')
const server = require('express')()
const renderer = require('vue-server-renderer').createRenderer()
server.get('*', (req, res) => {
const app = new Vue({
data: {
url: req.url
},
template: `<div>访问的 URL 是: {{ url }}</div>`
})
renderer.renderToString(app, (err, html) => {
if (err) {
res.status(500).end('Internal Server Error')
return
}
res.end(`
<!DOCTYPE html>
<html lang="en">
<head><title>Hello</title></head>
<body>${html}</body>
</html>
`)
})
})
server.listen(8080)
复制代码
nuxt 体验
简单几步体验下 nuxt
安装后
简单看了一下源码,nuxt 把我们之前提到的重要的改造,全部封装到 .nuxt 文件夹里面了
跑一下 dev 发现有两个端,一个 clinet 端,一个 server 端
最后查看一下效果,整个过程挺丝滑的。目录结构也比较符合我的风格,新项目需要 SSR 会考虑使用 nuxt
使用 webpack 插件预渲染
解决 SEO 问题是不是只有 SSR 呢?其实预渲染也能做到,首先
区别使用 SSR 和预渲染
- 服务端渲染解决的问题,不仅只是把 HTML 页面给浏览器,更重要的是处理动态逻辑和 JS 代码后,将渲染后完整的 HTML 给浏览器,渲染的过程在服务端。
- 预渲染,是利用构建工具在 webpack 中生成静态的 HTML,直接给浏览器,渲染的过程在本地。
- 预渲染插件里面提到两种不能使用:大量路由、动态内容
使用 prerender-spa-plugin 插件进行简单预渲染
- 安装 prerender-spa-plugin
yarn prerender-spa-plugin
复制代码
- 修改 webpack 配置,比较简单就能完成配置
const path = require('path')
const PrerenderSPAPlugin = require('prerender-spa-plugin')
const Renderer = PrerenderSPAPlugin.PuppeteerRenderer
module.exports = {
plugins: [
//...
new PrerenderSPAPlugin({
staticDir: path.join(__dirname, 'dist'),
outputDir: path.join(__dirname, 'prerendered'),
indexPath: path.join(__dirname, 'dist', 'index.html'),
routes: [ '/', '/about', '/some/deep/nested/route' ],
postProcess (renderedRoute) {
renderedRoute.route = renderedRoute.originalPath
renderedRoute.html = renderedRoute.html.split(/>[\s]+</gmi).join('><')
if (renderedRoute.route.endsWith('.html')) {
renderedRoute.outputPath = path.join(__dirname, 'dist', renderedRoute.route)
}
return renderedRoute
},
minify: {
collapseBooleanAttributes: true,
collapseWhitespace: true,
decodeEntities: true,
keepClosingSlash: true,
sortAttributes: true
},
renderer: new Renderer({
inject: {
foo: 'bar'
},
maxConcurrentRoutes: 4
]
}
复制代码
Vue 开发技巧总结
从 5 个大的角度来提升开发效率和体验,代码美观和代码质量,用户体验
5 大角度提升
代码复用
- 组件化开发,代码效率 * n
- 使用 mixins 抽离公共逻辑,代码效率 * n
- 工具化函数、使用 filter 编码效率+
- sass 复用 css,编码体验、效率+
代码质量
- 代码静态检查 eslint + prettier,代码风格+、基础语法错误-
- 数据类型控制 typescript,代码质量+
- 前端测试 test,代码质量+
代码优化
- 合理使用 vue,渲染性能+
- 合理使用 vuex 减少请求,使用图片懒加载,加载性能+
- 合理使用函数组件,组件性能+
- 合理骨架屏、路由过渡,用户体验+
开发效率
- 使用更新的脚手架 vue-cli4,webpack 配置效率+
- 使用配置好的脚手架模版 vue-h5-template,vue 配置效率+
- 使用更简洁的模版 pug,HTML 编写效率+
- 使用更强大的 css 编写 sass,CSS 编写效率+
- 使用模拟数据 mock,脱离后端开发效率+
- 开源组件封装 HOC,组件开发,页面编写效率+
瓶颈解决
- 路由history使用,服务端配置相关,URL美观+
- 解决SEO与首屏加载、服务端渲染 SSR 基本解决
后记
作者面临 “失业” 和 “禁足” 在家里的双重打击下,仍然坚持完成了这个系列的最后一篇文章。
如果能对你有帮助,便是它最大的价值。都看到这里还不点赞,太过不去啦!
由于技术水平有限,文章中如有错误地方,请在评论区指出,感谢!
相关推荐
- C/C++语言的引用与指针比较说明_c++语言中的引用类型与指针的不同之处
-
在C/C++编程中,引用与指针是实现数据间接访问的核心工具,二者均能优化参数传递效率、支持复杂数据操作,但在本质、语法与使用场景上存在显著差异。混淆二者易导致内存泄漏、悬空访问等严重问题。一、基础概念...
- 再也不点来点去了!用这几条命令,文件管理快10倍!(第003课)
-
大家好,今天给大家介绍如何用命令提示符(CMD)来管理你的文件!听起来可能有点高大上,但其实非常简单,而且一旦学会了,效率超高,特别适合那些喜欢快速搞定事情的朋友。那我们就从基础的操作开始,教你怎...
- C/C++逆向分析实战:变量存储与安全防护全攻略
-
在软件开发的世界里,C/C++语言因其卓越的性能和强大的功能而备受开发者青睐。然而,随着技术的不断进步,逆向工程也逐渐成为一种常见的攻击手段。今天,我们将深入探讨C/C++中不同类型的变量在逆向分析中...
- 简单的python-核心篇-命名空间与作用域
-
命名空间是变量名到对象的映射,作用域决定了变量在哪些地方可以被访问。Python使用LEGB规则来确定变量的作用域。#全局命名空间global_var="我是全局变量"de...
- Jmeter参数化:User Defined Variables-用户定义的变量
-
位置:如下图,Add--ConfigElement--UserDefinedVariables作用:定义你所需要的参数,比如IP使用:${IP}使用的场景:比如域名什么的,可以提出来参数化,以...
- PySnooper:实时看行号 / 变量值,摆脱 print 的函数调试工具
-
开篇:调试也能这么爽?你是不是和我一样,天天跟print()打交道,一行行地往代码里塞调试信息?有时候加完又删,删完又忘,简直要疯掉!要是能像Bash的set-x那样,一键打开“观察模式...
- 组态王入门之建立变量、变量连接、弹窗设计
-
一、建立变量(1)打开组态王软件,左侧找到变量的菜单(2)点击菜单(3)找到变量组,再新建一个二级变量组(4)在新建的泵站分配井阀门中建立变量因是新的PLC需要先建立一个驱动找到西门子àS7-200(...
- 为什么老外还是喜欢在官方网站上买东西?
-
今天看了一下一些品牌的官方网站,从浏览到购买支付流程都是很顺畅的,而一些国外的品牌在国内的网站好多都是引导至微信小程序或是淘宝京东上面去购买。国外的品牌官网好像都很简单,比如一些卖服装的类的,基本就是...
- 支撑京东小程序的开发框架 「Taro」,免费学习
-
转载自:性能与架构公众号Taro简介现在小程序平台太多了,例如:微信小程序QQ小程序支付宝小程序百度小程序字节跳动小程序针对他们都各自开发一套的话开发成本就太高了。如果写一套代码,就能开发出适配这么...
- 比较工具大集合_比较各种工具在编辑使用pl/sql程序过程中的优劣
-
现在各大网络平台流传着大量的文件夹和文件比较工具,其中不乏滥竽充数的,软件使用不够流畅,对比功能不够强大。很多人要么找不到合适的工具,要么在寻找过程中浪费了大量的时间,下面小编就和大家分享一些个人私藏...
- 关于前端开发的20篇文档与指南_前端开发文档怎么编写
-
相信在2015年很多这个行业的人都会有这样的两种感受:真的不知所措,这个行业到底有多少东西需要去学习;渴望更多,并迫不及待的为接下来的学习寻求一些思想方向。第一个来自于我们的个人感受,而第二个则是纯粹...
- 成为一名合格的前端架构师,前端知识技能与项目实战教学
-
一、教程描述本套前端架构师教程,大小35.94G,共有672个文件。二、教程目录01.node介绍和环境配置(共6课时)02.ES6语法(共5课时)03.node基础(共29课时)04.Express...
- 吃透 Vue 项目开发实践|16个方面深入前端工程化开发技巧【下】
-
前言前面两篇文章总结了Vue开发的大部分技巧和内容,最后一篇文章来对它进行一个收尾这篇文章我们来谈谈一些Vue理解和实践要求高一点的问题首先是生命周期这一块内容,随着实践越多它的意义越大,理解...
- 在w3cschool上学完html、css后要怎么提升
-
原标题:在w3cschool学完html,css,javascript,jquery以后,还是不会做前端怎么办?w3cschool是一个非盈利性的在线技术学习网站,提供按W3C标准编写的基础教程。完整...
- 从0到1无比流畅的React入门教程_react教程推荐
-
React是什么简介用于构建Web和原生交互界面的库React用组件创建用户界面通俗来讲:==是一个将数据渲染为HTML视图的开源JS库==其他信息Facebook开发,并且开源为什么使用R...
- 一周热门
- 最近发表
- 标签列表
-
- HTML 教程 (33)
- HTML 简介 (35)
- HTML 实例/测验 (32)
- HTML 测验 (32)
- JavaScript 和 HTML DOM 参考手册 (32)
- HTML 拓展阅读 (30)
- HTML文本框样式 (31)
- HTML滚动条样式 (34)
- HTML5 浏览器支持 (33)
- HTML5 新元素 (33)
- HTML5 WebSocket (30)
- HTML5 代码规范 (32)
- HTML5 标签 (717)
- HTML5 标签 (已废弃) (75)
- HTML5电子书 (32)
- HTML5开发工具 (34)
- HTML5小游戏源码 (34)
- HTML5模板下载 (30)
- HTTP 状态消息 (33)
- HTTP 方法:GET 对比 POST (33)
- 键盘快捷键 (35)
- 标签 (226)
- HTML button formtarget 属性 (30)
- opacity 属性 (32)
- transition 属性 (33)