1 什么是Vue?
2 Vue基本属性
3 Vue基本指令
4 组件化
4.1 创建组件
4.1.1 注册全局组件
4.1.2 注册局部组件
4.2 组件通信
5 Vue底层原理
5.1 虚拟DOM
5.2 Vue运行流程
5.3 生命周期钩子
6 Vue-cli脚手架
6.1 Webpack工具
6.1.1 基本配置
6.1.2 转换器Loader
6.1.3 插件Plugin
6.1.4 搭建本地服务器
6.2 vue-cli
6.3 Vue-router
6.4 Vuex
1 什么是Vue?
Vue是一套用于构建用户界面的渐进式框架,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。
2 Vue基本属性
注:
计算属性与方法的区别:前者基于缓存的。只在相关响应式发生改变时计算属性才会重新求值,即只要响应式不变,多次访问计算属性会立即返回之前缓存的计算结果,而不必再次执行函数;而每次访问方法都会重新执行函数,从效率来看,计算属性优于方法。
3 Vue基本指令
表3.1
注:
1、黄色标记的是对应指令的语法糖
2、使用v-on指令时,若监听器无参数时可省略();若监听器有参数但省略()则默认第一个形参为事件对象;若监听器有多个参数且包含事件对象,需要用$修饰符,例如:
<button @click="test(123,$event)">测试</button> test(num,e){ console.log(num,e); }
v-on指令主要修饰符如表3.2所示,示例代码:
<button @click.stop.once="test(123,$event)">测试</button> <button @keypress.enter="test">测试</button>
3、使用v-for指令遍历数组时,数组的部分方法可以触发响应式渲染,例如push()、pop()、shift()、unshift()、splice()、sort()、reverse()
4、v-model指令主要修饰符如表3.3所示。
4 组件化
Vue组件化提供了一种抽象,可以开发出一个个独立可复用的组件来构造复杂的整体应用,这个角度下,任何应用都会被抽象成一棵组件树。组件分为全局组件——可以在所有Vue实例下使用;局部组件——仅在声明局部组件的Vue实例下使用。一个组件可以理解为一个Vue实例,享有Vue实例的所有属性。
4.1 创建组件
4.1.1 注册全局组件
Vue.component('m_cpn',{ template: '#cpn', data(){ return { title: '我是标题', text: '我是内容' } } });
4.1.2 注册局部组件
components:{ m_cpn: { template: '#cpn', data(){ return { title: '我是标题', text: '我是内容' } } } }
注册组件时即将组件名(上面示例中为m_cpn)与HTML模板(上面示例中为cpn)绑定。必须指出,若在组件内部注册局部组件,则此局部组件为该组件的子组件。组件必须挂载在某个Vue实例下,否则无法生效。
4.2 组件通信
组件对象自身的固有数据存储在data属性中,与Vue对象不同在于,组件的data属性必须是函数,且该函数返回一个存储数据的对象,以使每个组件使用独立的内存空间,防止组件间数据的相互影响与耦合。
(1) 父组件传递数据给子组件——props属性
props属性通常是一个对象,内部包含数据对象,数据对象则支持一系列属性:① type:接收数据类型验证。可以指定type为JS内置类型,如String、Number等,也可为自定义数据结构;② default:未接收传参时的默认数据。当type为Array或Object类型时,default必须为函数;③ required:为true时强制从父组件接收参数,否则报错。
父组件还可以通过两个属性访问子组件的数据:① $children:返回所有子组件对象组成的数组,但实际应用中子组件下标值不恒定,故一般不使用该属性;② $refs:通过$ref.x返回ref属性为x的子组件对象。
(2) 子组件传递数据给父组件——自定义事件
① 子组件中自定义事件,并在需要的时候将数据嵌入该事件发射到父组件
this.$emit('add-info',this.mBooks[index],index);
其中add-info即为子组件的自定义事件,后续参数为绑定事件的各个数据。
② 使用子组件的v-on指令,绑定自定义事件到父组件接收函数
<m_cpn :m-books="books" :m-msg="msg" @add-info="addClick"></m_cpn>
其中addClick即为父组件接收函数,缺省()时按顺序传入add-info发射的数据,接收函数内部访问子组件数据时,亦要求按发射顺序接收。类似地,子组件可通过$parent属性访问父组件,但会增强代际耦合,不建议使用。
5 Vue底层原理
5.1 虚拟DOM
如图5.1所示,浏览器渲染引擎工作流程大致分以下步骤:
(1) 使用HTML分析器分析HTML元素,构建DOM树;
(2) 使用CSS分析器分析CSS文件和元素行内样式,生成页面的样式表;
(3) 将DOM树和页面样式表关联起来,构建渲染树(Render树)。由于每个DOM节点都有attach方法——接受样式信息,返回渲染对象,因此该过程又称为Attachment;
(4) 浏览器根据Render树布局,为Render树上每个节点确定在显示屏上的精确坐标;
(5) 调用每个DOM节点的Paint方法,绘制页面。
用原生JS或JQ操作DOM时,浏览器会从构建DOM树开始从头到尾执行一遍渲染流程,因此操作DOM的代价仍旧是昂贵的,频繁操作还是会出现页面卡顿,影响用户体验。虚拟DOM是渲染真实DOM之前,在内存中的JS对象,其设计是为了改善浏览器性能,因为操作JS对象比DOM节点速度快,这样可以避免无谓的重复渲染,而只将最终效果反映到网页。在网页数据更新时会先对虚拟DOM进行打补丁(patch)和重渲染,再一次性渲染到真实DOM。
图5.2 DOM Diff算法示例
网页的变化本质上是DOM节点的变化,因此虚拟DOM的核心之一在于新旧DOM树的比较,称为DOM Diff算法。DOM Diff算法只对DOM树的变化部分进行渲染,而对其余部分不作改动,避免遍历,从而大大提高渲染效率。如图5.2所示,DOM Diff只需变化新插入的节点,这意味着虚拟DOM树并非数组类型的顺序数据结构,而是链表型的无序数据结构,因此要高效应用虚拟DOM,需要为DOM节点指定key属性。
5.2 Vue运行流程
可以看出在Vue架构中,最终渲染都由渲染函数完成。运行Vue框架有两种模式:
(1) runtime-only
:不包含模板编译阶段;
(2) runtime-compiler
:包含模板编译阶段。
// runtime-only new Vue({ el: '#app', render: h => h(App) }) // runtime-compiler new Vue({ el: '#app', components: { App }, template: '<App/>' })
runtime-only运行时不包含模板编译阶段,所有模板只能在.Vue文件中借助Vue插件vue-template-compiler完成开发时编译,在项目打包时模板只已render函数形式存在于工程中,运行时不会二次编译,因此runtime-only运行更快、项目更轻,但该模式下不能存在于非.Vue文件中,否则无法渲染。
runtime-compiler函数不限制模板存在的文件形式,但模板仅在运行时编译,性能低于runtime-only模式。
5.3生命周期钩子
Vue中生命周期指一个Vue实例从创建到销毁的全部过程,如图5.4所示。在Vue实例的生命周期中有很多特殊的时间节点,且往往需要在这些时间节点执行一定的逻辑。封装了这些逻辑的函数称为钩子函数。钩子函数不同于回调函数,前者在事件发生的第一时间激活,后者在事件发生后激活。
常见的生命周期钩子函数如表5.1所示。
6 Vue-cli脚手架
6.1 Webpack工具
6.1.1 基本配置
在大型前端工程中,模块化有助于降低代码耦合性,防止功能冲突。前端模块化的方案有AMD、CMD、CommonJS、ES6等,其中除ES6外的模块化开发方案都需要特定的运行环境。webpack是一个基于Node.js的现代JS应用的静态模块打包工具,统一了各模块化方案,并且可以自动处理模块间的相互依赖。
对于新工程,webpack打包的流程如下:
npm init // 安装本地webpack包(@指定版本) npm install webpack@3.5 --save-dev // 安装全局webpack包 // npm install webpack -g npm install
为便于不同项目的管理,通常在每个工程下安装局部webpack包,防止版本冲突。但终端执行webpack却会使用全局webpack包,因此要在产生的package.json中配置webpack启动项:
"scripts": { "build": "webpack --config ./build/prod.config.js", "dev": "webpack-dev-server --config ./build/dev.config.js --open " },
此时运行npm run build即可进行生产模式下项目的打包。
6.1.2 转换器Loader
原生webpack只能处理JS文件及其依赖,但前端开发中还有CSS、Vue文件等,此时需要webpack转化器loader对其他格式的文件进行扩展,大部分文件的转化器可在webpack官网查询。以安装CSS、Vue扩展转换器为例:
// 安装CSS loader cnpm install style-loader --save-dev cnpm install css-loader --save-dev // 安装Vue(运行时依赖,不加-dev) cnpm install vue@2.6.14 –save // 安装Vue loader cnpm install vue-loader vue-template-compiler --save-dev
6.1.3 插件Plugin
webpack插件是对webpack现有功能的各种扩展,使webpack使用更方便,常用插件如表6.1所示。
下面给出配置扩展插件的实例。
安装插件:
cnpm install html-webpack-plugin --save-dev cnpm install uglifyjs-webpack-plugin@1.1.1 --save-dev
配置.config.js:
const HtmlWebpackPlugin = require('html-webpack-plugin'); const VueLoaderPlugin = require('vue-loader/lib/plugin'); const webpack = require('webpack'); const uglifyJsWebpack = require('uglifyjs-webpack-plugin'); module.exports = { ... plugins:[ // vue-loader15以上的版本需要此插件解析.vue文件 new VueLoaderPlugin(), // 版权声明 new webpack.BannerPlugin('@Copyright Winter 2018 - 2021'), // 打包html到发布文件夹dist new HtmlWebpackPlugin({ template: 'index.html' // html模板 }) ], // 压缩JS代码 optimization: { minimizer: [new uglifyJsWebpack()], } }
6.1.4 搭建本地服务器
安装:
cnpm install webpack-dev-server --save-dev
配置.config.js:
// 服务器配置 devServer: { // 指定提供本地服务的文件夹 static: { directory: path.join(__dirname, '../dist'), }, // 实时监听 compress: true, // 指定服务器端口 port: 8080 }
6.2 vue-cli
在大型项目开发时,需要使用Vue-CLI辅助完成代码目录结构、项目结构的部署、热加载、单元测试、插件与与依赖安装等配置。
在Vue-CLI中通过以下指令初始化项目:
// Vue-CLI 2.x vue init webpack project-name // Vue-CLI >= 3.0 vue create project-name
以Vue-CLI 4.5为例介绍主要代码结构,如表6.2所示。
6.3 Vue-router
实现前端路由主要分为以下步骤:
(1) 路由管理器index.js中配置路由映射表
const routes = [ { path: '/', redirect: '/home', // 重定向 }, { path: '/home', name: 'Home', component: Home, children: [ { path: 'news', component: () => import('../views/HomeNews') // 路由懒加载 }, { path: 'message', component: () => import('../views/HomeMessage') } ] }, { path: '/user/:id', name: 'User', component: () => import('../views/User.vue') } ]
注意:
① 采用组件化开发,一条前端路由对应渲染一个组件(可嵌套其他组件)。
② 打包构建Web应用时,JS包通常体积较大,影响页面加载。因此Vue提供了一种路由懒加载机制——将不同路由对应的组件分为不同代码块,当路由被访问时才对该组件进行渲染和加载。通过箭头函数导入的组件属于懒加载模式。
(2) 路由器实例化并传入路由表
const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes }) export default router
(3) 将路由器挂载到根实例
createApp(App).use(router).mount('#app')
注意:
将路由器挂载到Web应用后会产生全局路由器实例router和局部路由实例route,通过this.$router和this.$route的语法访问。
全局路由器对象常用的属性和方法如表6.3所示,局部路由对象常用的属性和方法如表6.4所示。
(4) 路由渲染
<router-link to="/home" replace>Home</router-link> <router-link to="/about" replace>About</router-link> <!-- 动态路由 --> <router-link :to="{path:'/user/'+ userId}" replace>User</router-link>
注意:
<router-link>默认会被渲染成<a>标签;<router-view>可视为组件占位符,根据当前路由在该位置动态渲染出不同的组件,而保持其他内容不变。
6.4 Vuex
Vuex是一个专为Vue.js应用程序开发的状态管理模式。它采用集中式存储管理应用所有组件的状态,并以一定的规则保证状态以一种可预测的方式发生变化,提高可维护性。Vuex适用于存储多个组件共享、依赖的状态,或多个组件的行为都会变更的状态,此时全局管理可以避免繁琐的组件通信。
注意:
(1) 一般采用对象风格向Vuex进行提交状态改变,mutations中定义的方法可使用载荷payload来获取提交时额外传入的参数,例如:
// mutation-types.js export const INCREMENT = "increment" // index.js // 导入事件常量 import {INCREMENT} from './mutation-types' // Vuex配置 state: { count: 1 }, mutations: { [INCREMENT](state, payload) { state.count = payload.amount; } }, // 提交状态变化 this.$store.commit({ type: INCREMENT, amount: 10 })
推荐使用事件常量替代mutations事件类型,这样可以使linter之类的工具发挥作用,同时把这些常量放在单独的文件中便于维护整个应用包含的mutations事件。
(2) actions使用实例
actions: { [INFOUPDATE](context, payload) { return new Promise((resolve, reject) => { setTimeout(() => { context.commit({ type: INCREMENT, amount: payload.num }); resolve('完成了'); }, 1000) }) } }, // 建议使用Promise对异步操作的结果进行处理 this.$store.dispatch({ type: INFOUPDATE, num: 10 }).then(res => { console.log(res) })
(3) modules使用实例
const moduleA = { state: () => ({ ... }), mutations: { ... }, actions: { ... }, getters: { ... } } const moduleB = { state: () => ({ ... }), mutations: { ... }, actions: { ... } } const store = createStore({ modules: { a: moduleA, b: moduleB } }) this.$store.state.a // -> moduleA 的状态 this.$store.state.b // -> moduleB 的状态
(4) Vuex代码结构
└── store ├── index.js # 导出store的文件 ├── actions.js # 根级别的 action ├── mutations.js # 根级别的 mutation ├── mutation-types.js # mutations事件常量 ├── action-types.js # actions事件常量 └── modules # 模块 ├── cart.js # 购物车模块 └── products.js # 产品模块