Vue2学习笔记 学习来源:黑马程序员
1 预备知识 1.1 前端工程化 前端工程化指的是:在企业级的前端项目开发中,把前端开发所需的工具、技术、流程、经验等进行规范化、标准化。
企业中的 Vue 项目和 React 项目,都是基于工程化的方式进行开发的。
好处:前端开发自成体系,有一套标准的开发方案和流程。
前端工程化解决方案
早期的前端工程化解决方案:
目前主流的前端工程化解决方案:
1.2 webpack 1.2.1 概念 概念:webpack 是前端项目工程化的具体解决方案。
主要功能:它提供了友好的前端模块化开发支持,以及代码压缩混淆、处理浏览器端 JavaScript 的兼容性、性能优化等强大的功能。
好处:让程序员把工作的重心放到具体功能的实现上,提高了前端开发效率和项目的可维护性。
注意:目前 Vue,React 等前端项目,基本上都是基于 webpack 进行工程化开发的 。
1.2.2 基本使用 ① 需求和初始化步骤 需求:页面隔行变色
步骤
新建项目空白目录,并运行 npm init –y
命令,初始化包管理配置文件 package.json
新建 src
源代码目录
新建 src -> index.html
首页和 src -> index.js
脚本文件
初始化首页基本的结构
运行 npm install jquery –S
命令,安装 jQuery
通过 ES6 模块化的方式导入 jQuery,实现列表隔行变色效果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta http-equiv ="X-UA-Compatible" content ="IE=edge" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > <script src ="./index.js" > </script > </head > <body > <ul > <li > 这是第 1 个 li</li > <li > 这是第 2 个 li</li > <li > 这是第 3 个 li</li > <li > 这是第 4 个 li</li > <li > 这是第 5 个 li</li > <li > 这是第 6 个 li</li > <li > 这是第 7 个 li</li > <li > 这是第 8 个 li</li > <li > 这是第 9 个 li</li > </ul > </body > </html >
1 2 3 4 5 6 7 8 9 import $ from "jquery" $(function ( ) { $('li:odd' ).css('background-color' , 'red' ) $('li:even' ).css('background-color' , 'pink' ) })
打开浏览器发现没有效果。
② 安装和配置webpack 安装命令:
1 npm install webpack@5.42.1 webpack-cli@4.7.2 -D
-D
表示开发环境,被记录在devDependencies
节点下
配置
在项目根目录中,创建名为 webpack.config.js
的 webpack 配置文件,并初始化如下的基本配置:
1 2 3 4 5 module .exports = { mode : 'development' }
在 package.json
的 scripts 节点下,新增 dev 脚本如下:
1 2 3 4 "scripts" : { "test" : "echo \"Error: no test specified\" && exit 1" , "dev" : "webpack" }
在终端中运行 npm run dev
命令,启动 webpack 进行项目的打包构建,运行后命令后在项目文件夹下生成dist
,即为打包后的项目
1 <script src ="../dist/main.js" > </script >
页面效果
③ 其他细节
mode 的可选值
mode 节点的可选值有两个,分别是:
development
开发环境
不会对打包生成的文件进行代码压缩和性能优化
打包速度快,适合在开发阶段使用
production
生产环境
会对打包生成的文件进行代码压缩和性能优化
打包速度很慢,仅适合在项目发布阶段使用
webpack.config.js 文件的作用
webpack.config.js 是 webpack 的配置文件。webpack 在真正开始打包构建之前,会先读取这个配置文件,从而基于给定的配置,对项目进行打包。
注意:由于 webpack 是基于 node.js 开发出来的打包工具,因此在它的配置文件中,支持使用 node.js 相关的语法 和模块进行 webpack 的个性化配置。
webpack 中的默认约定
在 webpack 4.x 和 5.x 的版本中,有如下的默认约定:
默认的打包入口文件为 src -> index.js
默认的输出文件路径为 dist -> main.js
注意:可以在 webpack.config.js 中修改打包的默认约定
在 webpack.config.js 配置文件中,通过 entry
节点指定打包的入口。通过 output
节点指定打包的出口。
2 vue基础 2.1 vue简介 官方给出的概念:Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的前端框架。
2.2.1 特性 vue 框架的特性,主要体现在如下两方面:
数据驱动视图
双向数据绑定
数据驱动视图
在使用了 vue 的页面中,vue 会监听数据的变化,从而自动重新渲染页面 的结构。示意图如下:
好处:当页面数据发生变化时,页面会自动重新渲染!
注意:数据驱动视图是单向的数据绑定。
双向数据绑定
在填写表单时,双向数据绑定可以辅助开发者在不操作 DOM 的前提 下,自动把用户填写的内容同步到数据源中。示意图如下:
好处:开发者不再需要手动操作 DOM 元素来获取表单元素最新的值!
2.2.2 MVVM MVVM 是 vue 实现数据驱动视图和双向数据绑定的核心原理 。MVVM 指的是 Model、View 和 ViewModel,它把每个 HTML 页面都拆分成了这三个部分,如图所示:
在 MVVM 概念中:
Model 表示当前页面渲染时所依赖的数据源 。
View 表示当前页面所渲染的 DOM 结构。
ViewModel 表示 vue 的实例 ,它是 MVVM 的核心。
工作原理
ViewModel 作为 MVVM 的核心,是它把当前页面的数据源(Model)和页面的结构(View)连接在了一起。
当数据源发生变化时,会被 ViewModel 监听到,VM 会根据最新的数据源自动更新页面的结构。
当表单元素的值发生变化时,也会被 VM 监听到,VM 会把变化过后最新的值自动同步到 Model 数据源中。
2.2.3 vue版本 当前,vue 共有 3 个大版本,其中:
2.x 版本的 vue 是目前企业级项目开发中的主流版本
3.x 版本的 vue 于 2020-09-19 发布,生态还不完善,尚未在企业级项目开发中普及和推广
1.x 版本的 vue 几乎被淘汰,不再建议学习与使用
总结:
3.x 版本的 vue 是未来企业级项目开发的趋势;
2.x 版本的 vue 在未来(1 ~ 2年内)会被逐渐淘汰;
2.2 vue初体验
导入 vue.js 的 script 脚本文件
在页面中声明一个将要被 vue 所控制的 DOM 区域
创建 vm 实例对象 (vue 实例对象)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Document</title > </head > <body > <div id ="app" > {{ username }}</div > <script src ="./lib//vue-2.6.12.js" > </script > <script > const vm = new Vue({ el : '#app' , data : { username : 'Mark' } }); </script > </body > </html >
基本代码与 MVVM 的对应关系
2.3 vue调试工具 vue 官方提供的 vue-devtools
调试工具,能够方便开发者对 vue 项目进行调试与开发。
Chrome 浏览器在线安装 vue-devtools :下载地址
2.4 vue指令 2.4.1 指令概念 指令(Directives)是 vue 为开发者提供的模板语法,用于辅助开发者渲染页面的基本结构 。
vue 中的指令按照不同的用途可以分为如下 6 大类:
内容渲染指令
属性绑定指令
事件绑定指令
双向绑定指令
条件渲染指令
列表渲染指令
指令是 vue 开发中最基础、最常用、最简单的知识点。
2.4.2 内容渲染指令 内容渲染指令用来辅助开发者渲染 DOM 元素的文本内容。常用的内容渲染指令有如下 3 个:
v-text
插值表达式
v-html
① v-text 用法示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Document</title > </head > <body > <div id ="app" > <p v-text ="username" > </p > <p v-text ="gender" > 性别</p > </div > <script src ="./lib//vue-2.6.12.js" > </script > <script > const vm = new Vue({ el : "#app" , data : { username : "Mark" , gender : "男" } }); </script > </body > </html >
注意:v-text 指令会覆盖元素内默认的值。
② 插值表达式 vue 提供的插值表达式语法,专门用来解决 v-text 会覆盖默认文本内容的问题。这种语法的专业名称是插值表达式 (英文名为:Mustache)。注意只能放在元素的内容节点中,不能放在元素的属性节点中。
注意:相对于 v-text 指令来说,插值表达式在开发中更常用一些!因为它不会覆盖元素中默认的文本内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Document</title > </head > <body > <div id ="app" > <p > 姓名:{{ username }}</p > <p > 性别:{{ gender }}</p > </div > <script src ="./lib//vue-2.6.12.js" > </script > <script > const vm = new Vue({ el : "#app" , data : { username : "Mark" , gender : "男" } }); </script > </body > </html >
③ v-html v-text 指令和插值表达式只能渲染纯文本内容 。如果要把包含 HTML 标签的字符串渲染为页面的 HTML 元素,则需要用到 v-html 这个指令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Document</title > </head > <body > <div id ="app" > <div v-text ="info" > </div > <div > {{ info }}</div > <div v-html ="info" > </div > </div > <script src ="./lib/vue-2.6.12.js" > </script > <script > const vm = new Vue({ el : "#app" , data : { username : "Mark" , gender : "男" , info : '<h4 style="color: red; font-weight: bold;">Hello World!</h4>' } }); </script > </body > </html >
渲染结果:
2.4.3 属性绑定指令 如果需要为元素的属性动态绑定 属性值,则需要用到 v-bind
属性绑定指令,属于单向绑定,即数据源改变,则DOM元素值改变。用法示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Document</title > </head > <body > <div id ="app" > <input type ="text" v-bind:placeholder ="tips" > <hr > <img v-bind:src ="photo" style ="width: 150px;" > </div > <script src ="./lib/vue-2.6.12.js" > </script > <script > const vm = new Vue({ el : "#app" , data : { tips : "请输入用户名" , photo : "https://cn.vuejs.org/images/logo.svg" } }); </script > </body > </html >
渲染结果:
简写形式
由于 v-bind 指令在开发中使用频率非常高,因此,vue 官方为其提供了简写形式(简写为英文的:
)。
1 2 3 4 5 <div id ="app" > <input type ="text" :placeholder ="tips" > <hr > <img :src ="photo" style ="width: 150px;" > </div >
使用 Javascript 表达式
在 vue 提供的模板渲染语法中,除了支持绑定简单的数据值之外,还支持 Javascript 表达式的运算,例如:
1 2 3 4 5 {{number + 1}} {{ok ? "YES" : "NO"}} <div :id ="'list-' + id" > </div >
2.4.4 事件绑定指令 ① 基本使用 vue 提供了 v-on
事件绑定指令,用来辅助程序员为 DOM 元素绑定事件监听 。
原生 DOM 对象有 onclick、oninput、onkeyup 等原生事件,替换为 vue 的事件绑定形式后,分别为:v-on:click
、v-on:input
、v-on:keyup
通过 v-on 绑定的事件处理函数,需要在 methods
节点中进行声明。
由于 v-on 指令在开发中使用频率非常高,因此,vue 官方为其提供了简写形式(简写为英文的 @
)。
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Document</title > </head > <body > <div id ="app" > <p > count的值是: {{ count }}</p > <button v-on:click ="add" > +1</button > <button @click ="sub" > -1</button > </div > <script src ="./lib/vue-2.6.12.js" > </script > <script > const vm = new Vue({ el : "#app" , data : { count : 0 }, methods : { add ( ) { this .count += 1 ; }, sub ( ) { this .count -=1 ; } } }); </script > </body > </html >
② 事件参数对象 在使用 v-on 指令绑定事件时,可以使用 ( )
进行传参
在原生的 DOM 事件绑定中,可以在事件处理函数的形参处,接收事件参数对象 event
。同理,在 v-on 指令(简写为 @ )所绑定的事件处理函数中,同样可以接收到事件参数对象 event。
$event
是 vue 提供的内置特殊变量,用来表示原生的事件参数对象 event。
$event
可以解决事件参数对象 event 被覆盖的问题。不常用。
代码示例
需求:count是偶数,按钮颜色为红色,为奇数则取消背景颜色
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Document</title > </head > <body > <div id ="app" > <p > count的值是: {{ count }}</p > <button @click ="add(1, $event)" > +1</button > </div > <script src ="./lib/vue-2.6.12.js" > </script > <script > const vm = new Vue({ el : "#app" , data : { count : 0 }, methods : { add (n, e ) { this .count += n; if (this .count % 2 === 0 ) { e.target.style.backgroundColor = "red" ; } else { e.target.style.backgroundColor = "" ; } } } }); </script > </body > </html >
③ 事件修饰符 在事件处理函数中调用 event.preventDefault()
或 event.stopPropagation()
是非常常见的需求。因此,vue 提供了事件修饰符的概念,来辅助程序员更方便的对事件的触发进行控制。常用的 5 个事件修饰符如下:
事件修饰符
说明
.prevent
阻止默认行为(例如:阻止 a 连接的跳转、阻止表单的提交等)
.stop
阻止事件冒泡
.capture
以捕获模式触发当前的事件处理函数
.once
绑定的事件只触发1次
.self
只有在 event.target
是当前元素自身时触发事件处理函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Document</title > </head > <body > <div id ="app" > <a href ="http://www.baidu.com" @click.prevent ="show" > 跳转到百度首页</a > </div > <script src ="./lib/vue-2.6.12.js" > </script > <script > const vm = new Vue({ el : "#app" , data : {}, methods : { show ( ) { console .log("点击了超链接" ); } } }); </script > </body > </html >
2.4.5 双向绑定指令 vue 提供了 v-model
双向数据绑定指令,用来辅助开发者在不操作 DOM 的前提下,快速获取表单的数据,即双向绑定,只要DOM元素值或者数据源数据其中一个改变,相应地另一个就会改变,这与v-bind
单向绑定不同。可以和表单元素一起使用:input
,textarea
,select
…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Document</title > </head > <body > <div id ="app" > <p > 用户的名字是:{{ username }}</p > <input type ="text" v-model ="username" > <hr > <input type ="text" :value ="username" > <hr > <select v-model ="city" > <option value ="" > 请选择城市</option > <option value ="1" > 北京</option > <option value ="2" > 上海</option > <option value ="3" > 广州</option > </select > </div > <script src ="./lib/vue-2.6.12.js" > </script > <script > const vm = new Vue({ el : "#app" , data : { username : "Mark" , city : "" } }); </script > </body > </html >
为了方便对用户输入的内容进行处理,vue 为 v-model 指令提供了 3 个修饰符,分别是:
修饰符
作用
示例
.number
自动将用户的输入值转为数值类型
<input v-model.number="age" />
.trim
自动过滤用户输入的首尾空白字符
<input v-model.trim="msg" />
.lazy
在“change”时而非“input”时更新
<input v-model.lazy="msg" />
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Document</title > </head > <body > <div id ="app" > <input type ="text" v-model.number ="n1" > + <input type ="text" v-model.number ="n2" > = <span > {{ n1 + n2}}</span > </div > <script src ="./lib/vue-2.6.12.js" > </script > <script > const vm = new Vue({ el : "#app" , data : { n1 : 1 , n2 : 2 } }); </script > </body > </html >
2.4.6 条件渲染指令 条件渲染指令用来辅助开发者按需控制 DOM 的显示与隐藏。条件渲染指令有如下两个,分别是:
v-if
v-show
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Document</title > </head > <body > <div id ="app" > <p v-if ="flag" > 这是被v-if控制的元素</p > <p v-show ="flag" > 这是被v-show控制的元素</p > </div > <script src ="./lib/vue-2.6.12.js" > </script > <script > const vm = new Vue({ el : "#app" , data : { flag : true } }); </script > </body > </html >
将flag修改为false后,发现两者都不见了,但是:
二者的区别:
实现原理不同:
v-if 指令会动态地创建或移除 DOM 元素,从而控制元素在页面上的显示与隐藏;
v-show 指令会动态为元素添加或移除 style="display: none;"
样式,从而控制元素的显示与隐藏;
性能消耗不同:
v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此:
如果需要非常频繁地切换,则使用 v-show 较好
如果在运行时条件很少改变,则使用 v-if 较好
v-if配套的指令
v-if 可以单独使用,或配合 v-else 指令一起使用。注意:v-else 指令必须配合 v-if 指令一起使用,否则它将不会被识别!v-else-if 指令,顾名思义,充当 v-if 的“else-if 块”,可以连续使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Document</title > </head > <body > <div id ="app" > <p v-if ="type === 'A'" > 优秀</p > <p v-else-if ="type === 'B'" > 良好</p > <p v-else > 合格</p > </div > <script src ="./lib/vue-2.6.12.js" > </script > <script > const vm = new Vue({ el : "#app" , data : { type : "A" } }); </script > </body > </html >
2.4.7 列表渲染指令 ① 基本使用 vue 提供了 v-for 列表渲染指令,用来辅助开发者基于一个数组来循环渲染一个列表结构。v-for 指令需要使用 item in items
形式的特殊语法。
v-for 指令还支持一个可选的第二个参数,即当前项的索引。语法格式为 (item, index) in items
。
注意:v-for 指令中的 item 项和 index 索引都是形参,可以根据需要进行重命名。例如 (user, i) in userlist
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Document</title > <link rel ="stylesheet" href ="./lib/bootstrap.css" > </head > <body > <div id ="app" > <table class ="table table-bordered table-hover table-striped" > <th > 索引</th > <th > ID</th > <th > 姓名</th > <tbody > <tr v-for ="(item, index) in list" > <td > {{ index }}</td > <td > {{ item.id }}</td > <td > {{ item.name }}</td > </tr > </tbody > </table > </div > <script src ="./lib/vue-2.6.12.js" > </script > <script > const vm = new Vue({ el : "#app" , data : { list : [ {id : 1 , name : "Mark" }, {id : 2 , name : "Hongyi" }, {id : 3 , name : "Kisugi" } ] } }); </script > </body > </html >
② key值 当列表的数据变化时,默认情况下,vue 会尽可能的复用已存在的 DOM 元素,从而提升渲染的性能。但这种默认的性能优化策略,会导致有状态的列表无法被正确更新。
为了给 vue 一个提示,以便它能跟踪每个节点的身份,从而在保证有状态的列表被正确更新的前提下,提升渲染的性能。此时,需要为每项提供一个唯一的 key 属性。
key 的注意事项
key 的值只能是字符串或数字类型
key 的值必须具有唯一性(即:key 的值不能重复)
建议把数据项 id 属性的值作为 key 的值(因为 id 属性的值具有唯一性)
使用 index 的值当作 key 的值没有任何意义(因为 index 的值不具有唯一性)
建议使用 v-for 指令时一定要指定 key 的值 (既提升性能、又防止列表状态紊乱)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <div id ="app" > <table class ="table table-bordered table-hover table-striped" > <th > 索引</th > <th > ID</th > <th > 姓名</th > <tbody > <tr v-for ="(item, index) in list" :key ="item.id" > <td > {{ index }}</td > <td > {{ item.id }}</td > <td > {{ item.name }}</td > </tr > </tbody > </table > </div >
2.4.8 实践案例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta http-equiv ="X-UA-Compatible" content ="IE=edge" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > 品牌列表案例</title > <link rel ="stylesheet" href ="./lib/bootstrap.css" > <link rel ="stylesheet" href ="./css/brandlist.css" > </head > <body > <div id ="app" > <div class ="card" > <div class ="card-header" > 添加品牌 </div > <div class ="card-body" > <form @submit.prevent ="add" > <div class ="form-row align-items-center" > <div class ="col-auto" > <div class ="input-group mb-2" > <div class ="input-group-prepend" > <div class ="input-group-text" > 品牌名称</div > </div > <input type ="text" class ="form-control" placeholder ="请输入品牌名称" v-model.trim ="brand" > </div > </div > <div class ="col-auto" > <button type ="submit" class ="btn btn-primary mb-2" > 添加</button > </div > </div > </form > </div > </div > <table class ="table table-bordered table-hover table-striped" > <thead > <tr > <th scope ="col" > #</th > <th scope ="col" > 品牌名称</th > <th scope ="col" > 状态</th > <th scope ="col" > 创建时间</th > <th scope ="col" > 操作</th > </tr > </thead > <tbody > <tr v-for ="(item, index) in list" :key ="item.id" > <td > {{ index }}</td > <td > {{ item.name }}</td > <td > <div class ="custom-control custom-switch" > <input type ="checkbox" class ="custom-control-input" :id ="'cb' + item.id" v-model ="item.status" > <label class ="custom-control-label" :for ="'cb' + item.id" v-if ="item.status" > 已启用</label > <label class ="custom-control-label" :for ="'cb' + item.id" v-else > 已禁用</label > </div > </td > <td > {{ item.time }}</td > <td > <a href ="javascript:;" @click ="remove(item.id)" > 删除</a > </td > </tr > </tbody > </table > </div > <script src ="./lib/vue-2.6.12.js" > </script > <script > const vm = new Vue({ el : "#app" , data : { list : [ {id : 1 , name : "宝马" , status : true , time : new Date ()}, {id : 2 , name : "奔驰" , status : false , time : new Date ()}, {id : 3 , name : "大众" , status : true , time : new Date ()}, {id : 4 , name : "路虎" , status : true , time : new Date ()} ], brand : "" , nextId : 5 }, methods : { remove (id ) { this .list = this .list.filter(item => item.id !== id); }, add ( ) { if (this .brand === "" ) return alert("品牌名称不能为空" ); const obj = { id : this .nextId, name : this .brand, status : true , time : new Date () }; this .list.push(obj); this .brand = "" ; this .nextId += 1 ; } } }); </script > </body > </html >
2.5 vue侦听器 2.5.1 基本使用 watch
侦听器允许开发者监视数据的变化,从而针对数据的变化做特定的操作。
代码示例1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Document</title > </head > <body > <div id ="app" > <input type ="text" v-model ="username" > </div > <script src ="./lib/vue-2.6.12.js" > </script > <script > const vm = new Vue({ el : "#app" , data : { username : "" }, watch : { username (newVal, oldVal ) { console .log(newVal, oldVal); } } }); </script > </body > </html >
代码示例2
监听 username 值的变化,并使用 ajax 发起 Ajax 请求,检测当前输入的用户名是否可用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Document</title > </head > <body > <div id ="app" > <input type ="text" v-model ="username" > </div > <script src ="./lib/vue-2.6.12.js" > </script > <script src ="./lib/jquery-v3.6.0.js" > </script > <script > const vm = new Vue({ el : "#app" , data : { username : "" }, watch : { username (newVal ) { if (newVal === "" ) { return } $.get("https://www.escook.cn/api/finduser/" + newVal, function (res ) { console .log(res); }); } } }); </script > </body > </html >
方法格式的侦听器:
缺点1:无法在刚进入页面时,自动触发侦听器
缺点2:如果侦听的是一个对象,则对象的属性发生变化时不会触发侦听器
对象格式的侦听器:
好处1:可以通过immediate选项让侦听器自动触发一次,默认不开启
好处2:可以通过deep选项深度侦听对象的每个属性的变化,默认不开启
默认情况下,组件在初次加载完毕后不会调用 watch 侦听器。如果想让 watch 侦听器立即被调用,则需要使用 immediate
选项。
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Document</title > </head > <body > <div id ="app" > <input type ="text" v-model ="username" > </div > <script src ="./lib/vue-2.6.12.js" > </script > <script src ="./lib/jquery-v3.6.0.js" > </script > <script > const vm = new Vue({ el : "#app" , data : { username : "admin" }, watch : { username : { handler (newVal, oldVal ) { console .log(newVal, oldVal); }, immediate : true } } }); </script > </body > </html >
2.5.3 deep选项 如果 watch 侦听的是一个对象 ,如果对象中的属性值发生了变化,则无法被监听到。此时需要使用 deep 选项。
代码示例1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Document</title > </head > <body > <div id ="app" > <input type ="text" v-model ="infor.username" > </div > <script src ="./lib/vue-2.6.12.js" > </script > <script src ="./lib/jquery-v3.6.0.js" > </script > <script > const vm = new Vue({ el : "#app" , data : { infor : { username : "Mark" } }, watch : { infor : { handler (newVal ) { console .log(newVal); }, deep : true } } }); </script > </body > </html >
代码示例2
如果只想监听对象中单个属性的变化,则可以按照如下的方式定义 watch 侦听器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <script > const vm = new Vue({ el : "#app" , data : { infor : { username : "Mark" } }, watch : { infor.username: { handler (newVal ) { console .log(newVal); } } } }); </script >
2.6 vue计算属性 计算属性指的是通过一系列运算之后,最终得到一个属性值 。
这个动态计算出来的属性值可以被模板结构或 methods 方法使用。
计算属性的特点
虽然计算属性在声明的时候被定义为方法,但是计算属性的本质是一个属性
计算属性会缓存计算的结果,只有计算属性依赖的数据变化时,才会重新进行运算
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Document</title > <script src ="./lib/vue-2.6.12.js" > </script > <style > .box { width : 200px ; height : 200px ; border : 1px solid #ccc ; } </style > </head > <body > <div id ="app" > <div > <span > R: </span > <input type ="text" v-model.number ="r" > </div > <div > <span > G: </span > <input type ="text" v-model.number ="g" > </div > <div > <span > B: </span > <input type ="text" v-model.number ="b" > </div > <hr > <div class ="box" :style ="{ backgroundColor: rgb }" > {{ rgb }} </div > <button @click ="show" > 按钮</button > </div > <script > var vm = new Vue({ el : '#app' , data : { r : 0 , g : 0 , b : 0 }, methods : { show ( ) { console .log(this .rgb) } }, computed : { rgb ( ) { return `rgb(${this .r} , ${this .g} , ${this .b} )` ; } } }); </script > </body > </html >
优点
实现了代码的复用
只要依赖的数据源发生改变时,计算属性会自动重新求值
2.7 vue-cli 2.7.1 概念 单页面应用程序(英文名:Single Page Application)简称 SPA
,顾名思义,指的是一个 Web 网站中只有唯一的一个 HTML 页面,所有的功能与交互都在这唯一的一个页面内完成。
vue-cli
是 Vue.js 开发的标准工具。它简化了程序员基于 webpack 创建工程化的 Vue 项目的过程。
引用自 vue-cli 官网上的一句话:程序员可以专注在撰写应用上,而不必花好几天去纠结 webpack 配置的问题。
2.7.2 安装和使用 vue-cli 是 npm 上的一个全局包,使用 npm install 命令,即可方便的把它安装到自己的电脑上:
安装完成后,输入以下命令查看vue版本号:
基于 vue-cli 快速生成工程化的 Vue 项目:
2.7.3 目录结构
src的目录构成:
assets
:存放项目的静态资源,例如css样式表,图片资源
components
:存放封装的可复用的组件
main.js
:项目的入口文件,整个项目的执行需要先执行main.js
App.vue
:项目的根组件
2.7.4 运行流程 在工程化的项目中,vue 要做的事情很单纯:通过 main.js 把 App.vue 渲染到 index.html 的指定区域中。
其中:
1 2 3 <template> <h1>Hello World!</h1> </template>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <!DOCTYPE html > <html lang ="" > <head > <meta charset ="utf-8" > <meta http-equiv ="X-UA-Compatible" content ="IE=edge" > <meta name ="viewport" content ="width=device-width,initial-scale=1.0" > <link rel ="icon" href ="<%= BASE_URL %>favicon.ico" > <title > <%= htmlWebpackPlugin.options.title %></title > </head > <body > <noscript > <strong > We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong > </noscript > <div id ="app" > </div > </body > </html >
main.js 把 App.vue 渲染到了 index.html 所预留的区域中(直接替换掉)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import Vue from 'vue' import App from './App.vue' Vue.config.productionTip = false new Vue({ el : "#app" , render : h => h(App), }) new Vue({ render : h => h(App), }).$mount('#app' )
渲染结果:
发现:原来index.html中预留的待渲染位置<div id="app"></div>
已被替换 为了App.vue中的整个模板结构<h1>Hello World!</h1>
,二者都是main.js中render函数的功劳。
2.8 vue组件 2.8.1 组件化开发 组件化开发指的是:根据封装的思想,把页面上可重用的 UI 结构封装为组件,从而方便项目的开发和维护。
vue 是一个支持组件化开发的前端框架。
vue 中规定:组件的后缀名 是 .vue
。之前接触到的 App.vue 文件本质上就是一个 vue 的组件。
2.8.2 组件组成部分 每个 .vue 组件都由 3 部分构成,分别是:
template
-> 组件的模板结构
script
-> 组件的 JavaScript 行为
style
-> 组件的样式
其中,每个组件中必须包含 template 模板结构,而 script 行为和 style 样式是可选的组成部分。
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 <template> <div class="test-box"> <h1>Hello World! --- {{ username }}</h1> <button @click="changeName">修改用户名</button> </div> </template> <script> // 默认导出 es6语法,这是固定写法 export default { // 数据源 // 注意:组件内的data不能像之前那样指向对象,而是一个函数 data() { // 这个return出去的{ }中,可以定义数据 return { username: "Mark" } }, // 方法 methods: { changeName() { // 在组件中,this是当前组件的实例,原型是VueComponent this.username = "Hongyi" } } } </script> <style lang="less"> .test-box { background-color: pink; h1 { color: red; } } </style>
① template vue 规定:每个组件对应的模板结构,需要定义到 <template>
节点中。
注意:
template 是 vue 提供的容器标签,只起到包裹性质的作用,它不会被渲染为真正的 DOM 元素
template 中只能包含唯一的根节点
1 2 3 <template> // ... </template>
② script vue 规定:开发者可以在 <script>
节点中封装组件的 JavaScript 业务逻辑。
<script >
节点的基本结构如下:
1 2 3 4 5 6 7 <script> // 今后,当前组件相关的data数据源,methods方法,侦听器watch,计算属性computed等 // 都需要定义在export default所导出的对象中 export default { // ... } </script>
注意 :vue 规定:.vue 组件中的 data 必须是一个函数 ,不能直接指向一个数据对象。
③ style vue 规定:组件内的 <style>
节点是可选的,开发者可以在 <style>
节点中编写样式美化当前组件的 UI 结构。
<style>
节点的基本结构如下:
1 2 3 4 5 <style> h1 { color: red; } </style>
在 <style>
标签上添加 lang="less"
属性,即可使用 less 语法编写组件的样式。
2.8.3 组件父子关系 组件在被封装好之后,彼此之间是相互独立的,不存在父子关系 :
在使用组件的时候,根据彼此的嵌套关系,形成了父子关系、兄弟关系:
2.8.4 使用组件的三个步骤 在App.vue组件内:
1 import Left from '@/components/Left.vue'
这里的@/
就是./src/
的别名
1 2 3 4 5 export default { components : { Left } }
1 2 3 <div class ="box" > <Left > </Left > </div >
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 <template> <div class="app-container"> <h1>App 根组件</h1> <hr /> <div class="box"> <!-- 渲染 Left 组件 --> <!-- 3. 以标签形式,使用注册好的组件 --> <Left></Left> <Right></Right> </div> </div> </template> <script> // 1.导入需要使用的 .vue 组件 import Left from '@/components/Left.vue' import Right from '@/components/Right.vue' export default { // 2.注册组件 components: { Left, Right, Right } } </script> <style lang="less"> .app-container { padding: 1px 20px 20px; background-color: #efefef; } .box { display: flex; } </style>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <template> <div class="left-container"> <h3>Left 组件</h3> </div> </template> <script> export default {} </script> <style lang="less" scoped> .left-container { padding: 0 20px 20px; background-color: orange; min-height: 250px; flex: 1; } </style>
2.8.5 私有子组件和全局组件
tips:推荐使用插件vue 3 snippets
通过 components 注册的是私有子组件。
例如:在组件 A 的 components 节点下,注册了组件 F。则组件 F 只能用在组件 A 中;不能被用在组件 C 中。
如果某个组件被其他多个组件频繁地用到,则可以将这个组件注册为全局组件。在 vue 项目的 main.js 入口文件中,通过 Vue.component()
方法,可以注册全局组件 。
代码示例
Count.vue组件。该组件被Left和Right组件都要使用。此时将其注册为全局组件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <template> <div> <h5>Count组件</h5> </div> </template> <script> export default { } </script> <style lang="less"> </style>
1 2 3 4 5 6 7 8 9 10 11 12 13 import Vue from 'vue' import App from './App.vue' import Count from '@/components/Count.vue' Vue.component('MyCount' , Count) Vue.config.productionTip = false new Vue({ render : h => h(App), }).$mount('#app' )
1 2 3 4 5 6 7 8 9 10 11 <template> <div class="left-container"> <h3>Left 组件</h3> <hr> <MyCount></MyCount> </div> </template> <script> export default {} </script>
2.8.6 组件的props ① 基本使用 props 是组件的自定义属性 ,在封装通用组件的时候,合理地使用 props 可以极大的提高组件的复用性!
代码示例
需求:左右两个组件要各自自定义count的初始值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <template> <div> <h5>Count组件</h5> <p>count的值是: {{ init }}</p> <button @click="count += 1">+1</button> </div> </template> <script> export default { // 组件的自定义属性 props: ['init'], data() { return { count: 0 } } } </script>
1 2 3 4 5 6 7 8 <template> <div class="left-container"> <h3>Left 组件</h3> <hr> <!-- 初始化props --> <MyCount :init="9"></MyCount> </div> </template>
② props是只读的 vue 规定:组件中封装的自定义属性是只读的,程序员不能直接修改 props 的值,否则会直接报错。
要想修改 props 的值,可以把 props 的值转存到 data 中,因为 data 中的数据都是可读可写的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <template> <div> <h5>Count组件</h5> <p>count的值是: {{ count }}</p> <button @click="count += 1">+1</button> </div> </template> <script> export default { // 组件的自定义属性 props: ['init'], data() { return { // 使用组件里的自定义属性 count: this.init } } } </script>
③ default默认值 在声明自定义属性时,可以通过 default
来定义属性的默认值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <script> export default { // 对象形式的props,这样更加灵活和扩展性强 props: { init: { // 用default属性定义属性的默认值 default: 0 } }, data() { return { count: this.init } } } </script>
1 2 3 4 5 6 7 8 <template> <div class="left-container"> <h3>Left 组件</h3> <hr> <!-- 用户没有确定init的初始值 --> <MyCount></MyCount> </div> </template>
④ type值类型 在声明自定义属性时,可以通过 type 来定义属性的值类型。
1 2 3 4 5 6 7 8 9 10 11 <script> export default { props: { init: { default: 0, // 如果传递来的数据不符合该类型,则会在终端报错 type: Number } } } </script>
⑤ required 必填项 在声明自定义属性时,可以通过 required 选项,将属性设置为必填项,强制用户必须传递属性的值。
1 2 3 4 5 6 7 8 9 10 11 <script> export default { props: { init: { default: 0, type: Number, required: true } } } </script>
2.8.7 组件间的样式冲突 ① scoped属性 默认情况下,写在 .vue
组件中的样式会全局生效,因此很容易造成多个组件之间的样式冲突问题。
导致组件之间样式冲突的根本原因是:
单页面应用程序中,所有组件的 DOM 结构,都是基于唯一的 index.html 页面进行呈现的
每个组件中的样式,都会影响整个 index.html 页面中的 DOM 元素
解决方法1
为每个组件分配唯一的自定义属性 ,在编写组件样式时,通过属性选择器来控制样式的作用域。
代码:略
解决方法2
为了提高开发效率和开发体验,vue 为 style 节点提供了 scoped
属性,从而防止组件之间的样式冲突问题。
实质:为组件内的各元素分配了唯一的属性,即解决方法1
1 2 3 4 5 6 7 8 9 10 11 12 <style lang="less" scoped> .left-container { padding: 0 20px 20px; background-color: orange; min-height: 250px; flex: 1; } h3 { color: red; } </style>
② deep样式穿透 如果给当前组件的 style 节点添加了 scoped 属性,则当前组件的样式对其子组件是不生效的 。如果想让某些样式对子组件生效,可以使用 /deep/
深度选择器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <style lang="less" scoped> .left-container { padding: 0 20px 20px; background-color: orange; min-height: 250px; flex: 1; } h3 { color: red; } /* 只修改left组件下的count子组件的标题h5 */ /deep/ h5 { color: pink; } </style>
3 生命周期和数据共享 3.1 生命周期概念 生命周期(Life Cycle)是指一个组件 从创建 -> 运行 -> 销毁的整个阶段,强调的是一个时间段。
生命周期函数:是由 vue 框架提供的内置函数,会伴随着组件的生命周期,自动按次序执行。
注意:生命周期强调的是时间段,生命周期函数强调的是时间点。
组件生命周期函数的分类
生命周期图示
代码准备
创建Test组件,并在根组件App中使用
3.2 生命周期函数 3.2.1 beforeCreate和created
beforeCreate
这个函数使用的很少。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <template> <div class="test-container"> <h3>Test.vue组件</h3> </div> </template> <script> export default { props: ['info'], data() { return { message: 'hello world' } }, methods: { show() { console.log('调用了Test组件的show方法'); } }, // 创建阶段的第一个生命周期函数 beforeCreate() { console.log(this.info); console.log(this.message); this.show(); }, } </script> <style lang="less" scoped> .test-container { background-color: pink; height: 200px; } </style>
1 2 3 4 5 6 7 8 9 10 11 <template> <div class="app-container"> <h1>App根组件</h1> <Test info="你好"></Test> <hr /> <div class="box"> <Left></Left> <Right></Right> </div> </div> </template>
运行后报错:
created
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <script> export default { props: ['info'], data() { return { message: 'hello world' } }, methods: { show() { console.log('调用了Test组件的show方法'); } }, // 创建阶段的第一个生命周期函数 beforeCreate() { // 执行以下语句会报错 // console.log(this.info); // console.log(this.message); // this.show(); }, // 创建阶段的第二个生命周期函数 created() { console.log(this.info); console.log(this.message); this.show(); }, } </script>
打印结果:
1 2 3 你好 hello world 调用了Test组件的show方法
该函数可用于在页面尚未渲染时,发送ajax请求 提前获取数据,之后就可以将数据渲染在页面中。注意,该函数内部尚不能操作DOM ,因为页面尚未被渲染。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 <template> <div class="test-container"> <h3>Test.vue组件 --- 一共有 {{ books.length }} 本图书</h3> </div> </template> <script> export default { props: ['info'], data() { return { message: 'hello world', // 定义一个接收图书的数组 books: [] } }, methods: { show() { console.log('调用了Test组件的show方法'); }, // 使用ajax请求图书列表的数据 initBookList() { const xhr = new XMLHttpRequest(); xhr.addEventListener('load', () => { const res = JSON.parse(xhr.responseText); console.log(res); this.books = res.data; }); xhr.open('GET', 'http://www.liulongbin.top:3006/api/getbooks'); xhr.send(); } }, created() { // 经常使用该函数调用methods中的方法,向服务器请求数据 // 并将请求到的数据转存到数据源data中,供template模板渲染使用 this.initBookList(); }, } </script>
3.2.2 beforeMount和mounted 在此之前,已在内存 中编译生成了HTML结构。
beforeMount
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <template> <div class="test-container"> <h3 id="mark">Test.vue组件</h3> </div> </template> <script> export default { beforeMount() { const dom = document.querySelector('mark'); console.log(dom); } } </script>
打印结果:
说明该生命周期函数不能操作DOM。该函数并不常用。
mounted
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <template> <div class="test-container"> <h3 id="mark">Test.vue组件</h3> </div> </template> <script> export default { mounted() { const dom = document.querySelector('mark'); console.log(dom); } } </script>
打印结果:
1 <h3 data-v-dc87507c="" id="#mark">Test.vue组件 --- 一共有 8 本图书</h3>
3.2.3 beforeUpdate和updated 该生命周期函数位于组件运行阶段执行。
beforeUpdate
此时数据变化了,但是页面还未重新渲染。
updated
此时页面已经重新渲染。
3.2.4 beforeDestroy和destroyed 略。
3.3 数据共享 3.3.1 概念 在项目开发中,组件之间的最常见的关系分为如下两种:
父子组件之间的数据共享又分为:
3.3.2 父向子共享数据 父组件向子组件共享数据时,子组件需要使用自定义属性props 来接收父组件的数据。
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <template> <div class="app-container"> <h1>App根组件</h1> <hr> <!-- 注意这里使用了属性绑定 --> <Left :msg="message" :user="userinfo"></Left> </div> </template> <script> import Left from '@/components/Left.vue' export default { components: { Left }, data() { return { message: 'hello world', userinfo: { name: 'Mark', age: 24 } } } } </script>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <template> <div class="left-container"> <h3>Left 组件</h3> <p>msg的值是: {{ msg }}</p> <p>user的值是: {{ user }}</p> </div> </template> <script> export default { // 用于接收父组件传过来的数据 props: ['msg', 'user'] } </script>
3.3.3 子向父共享数据 子组件向父组件共享数据时,父组件需要使用自定义事件 。
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <template> <div class="right-container"> <h3>Right 组件 -- {{count}}</h3> <button @click="add">+1</button> </div> </template> <script> export default { data() { return { // 子组件自己的数据,把该值传给父组件 count: 0 } }, methods: { add() { this.count += 1; // 修改数据时,通过$emit()触发自定义事件,将结果数据传递过去 this.$emit('numchange', this.count); } }, } </script>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <template> <div class="app-container"> <h1>App根组件 --- {{ countFromSon }}</h1> <hr> <div class="box"> <Left :msg="message" :user="userinfo"></Left> <!-- 绑定自定义事件 --> <Right @numchange="getNewCount"></Right> </div> </div> </template> <script> import Left from '@/components/Left.vue' import Right from '@/components/Right.vue' export default { components: { Left, Right }, methods: { // 自定义事件处理函数 getNewCount(val) { this.countFromSon = val; } }, } </script>
3.3.4 兄弟组件间共享数据 在 vue2.x
中,兄弟组件之间数据共享的方案是 EventBus
。
使用步骤
创建 eventBus.js 模块,并向外共享一个 Vue 的实例对象
在数据发送方,调用 bus.$emit('事件名称', 要发送的数据)
方法触发自定义事件
在数据接收方,调用 bus.$on('事件名称', 事件处理函数)
方法注册一个自定义事件
代码示例
1 2 import Vue from 'vue' export default new Vue()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <template> <div class="left-container"> <h3>Left 组件</h3> <button @click="send">将str发给Right</button> </div> </template> <script> import bus from './eventBus.js' export default { data() { return { // 向右组件发送的数据 str: 'Hello Vue.js!' } }, methods: { send() { // 通过eventBus发送数据 bus.$emit('share', this.str); } }, } </script>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <template> <div class="right-container"> <h3>Right 组件 -- {{count}}</h3> <p>接收到左组件的str: {{ msgFromLeft }}</p> </div> </template> <script> import bus from './eventBus.js' export default { data() { return { // 定义要接受左组件的数据 msgFromLeft: '' } }, methods: { add() { this.count += 1; // 修改数据时,通过$emit()触发自定义事件,将结果数据传递过去 this.$emit('numchange', this.count); } }, created() { // 为bus绑定自定义事件 bus.$on('share', str => { this.msgFromLeft = str; }); }, } </script>
3.4 ref引用 3.4.1 概念 ref 用来辅助开发者在不依赖于 jQuery 的情况下,获取 DOM 元素或组件 的引用。
每个 vue 的组件实例上,都包含一个 $refs
对象,里面存储着对应的 DOM 元素或组件的引用。默认情况下,组件的 $refs
指向一个空对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <template> <div class="app-container"> <h1>App根组件 --- {{ countFromSon }}</h1> <button @click="getRef">打印this</button> </div> </template> <script> export default { methods: { getRef() { // this指向当前组件的实例对象,this.$ref默认指向空对象 console.log(this); } }, } </script>
3.4.2 使用ref引用DOM元素 如果想要使用 ref 引用页面上的 DOM 元素,则可以按照如下的方式进行操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <template> <div class="app-container"> <!-- 使用ref属性,为对应的DOM元素添加引用名称 --> <!-- 注意ref属性不要重复,否则后者会覆盖前者 --> <h1 ref="mark">App根组件 --- {{ countFromSon }}</h1> <button @click="getRef">打印this</button> </div> </template> <script> export default { methods: { getRef() { // this指向当前组件的实例对象,this.$ref默认指向空对象 console.log(this); // 通过this.$refs引用的名称,可以获取到DOM元素 console.log(this.$refs.mark); // 操作DOM元素 this.$refs.mark.style.color = 'red'; } } } </script>
3.4.3 使用 ref 引用组件实例 如果想要使用 ref 引用页面上的组件实例,则可以按照如下的方式进行操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <template> <!-- 使用ref属性,为组件添加引用名称 --> <Left ref="counterRef"></Left> </template> <script> export default { methods: { gerRef() { // 引用到组件的实例后,就可以调用该组件上的methods中的方法 // 例如假设Left组件的methods中有add方法 this.$ref.counter.add(); } } } </script>
3.5 购物车案例 后端传过来的数据格式:
代码略,详见代码仓库
4 动态组件&插槽&自定义指令 4.1 动态组件 4.1.1 基本使用 动态组件指的是动态切换组件的显示与隐藏。
vue 提供了一个内置的 <component>
组件,专门用来实现动态组件的渲染。
代码示例1——基本使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <template> <div class="app-container"> <h1>App 根组件</h1> <hr /> <div class="box"> <!-- 渲染 Left 组件和 Right 组件 --> <!-- 1.component是vue内置的 --> <!-- 2.is属性的值,是要渲染的组件的名字 --> <component :is="comName"></component> </div> </div> </template> <script> import Left from '@/components/Left.vue' import Right from '@/components/Right.vue' export default { data() { return { // comName表示要展示的组件的名字 comName: "Left" } }, components: { Left, Right } } </script>
代码示例2——动态展示组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <template> <div class="app-container"> <h1>App 根组件</h1> <hr /> <button @click="comName = 'Left'">展示Left</button> <button @click="comName = 'Right'">展示Right</button> <div class="box"> <!-- 渲染 Left 组件和 Right 组件 --> <!-- 1.component是vue内置的 --> <!-- 2.is属性的值,是要渲染的组件的名字 --> <component :is="comName"></component> </div> </div> </template> <script> import Left from '@/components/Left.vue' import Right from '@/components/Right.vue' export default { data() { return { // comName表示要展示的组件的名字 comName: "Left" } }, components: { Left, Right } } </script>
4.1.2 keep-alive ① 基本使用 默认情况下,切换动态组件时无法保持组件的状态。此时可以使用 vue 内置的 <keep-alive>
组件保持动态组件的状态。
keep-alive
可以把包裹的内部的组件进行缓存,而不是将组件进行销毁。
1 2 3 <keep-alive> <component :is="comName"></component> </keep-alive>
② keep-alive对应的生命周期函数 当组件被缓存时,会自动触发组件的 deactivated
生命周期函数。
当组件被激活时,会自动触发组件的 activated
生命周期函数。
当组件被第一次创建的时候,会执行created
函数,也会执行activated
函数。当组件被激活的时候,不再会执行前者,而只执行后者,因为该组件不是被创建出来的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <template> <div class="left-container"> <h3>Left 组件 --- {{ count }}</h3> <button @click="count += 1">+1</button> </div> </template> <script> export default { data() { return { count: 0 } }, activated() { console.log("组件被激活了"); }, deactivated() { console.log("组件被缓存了"); }, } </script>
③ include属性 include
属性用来指定:只有名称匹配的组件会被缓存。多个组件名之间使用英文的逗号分隔。
1 2 3 <keep-alive include="Left,Right"> <component :is="comName"></component> </keep-alive>
4.2 插槽 4.2.1 基本使用 插槽(Slot)是 vue 为组件的封装者提供的能力。允许开发者在封装组件时,把不确定的、希望由用户指定的部分定义为插槽。
可以把插槽认为是组件封装期间,为用户预留的内容的占位符。
代码示例
在封装组件时,可以通过 <slot>
元素定义插槽,从而为用户预留内容占位符。
1 2 3 4 5 6 7 8 9 10 11 12 <template> <div class="app-container"> <h1>App 根组件</h1> <hr /> <div class="box"> <Left> <!-- 默认情况下,提供的内容都会被填充到default的插槽中 --> <p>这是在Left组件的内容区域声明的p标签</p> </Left> </div> </div> </template>
1 2 3 4 5 6 7 8 9 10 <template> <div class="left-container"> <h3>Left 组件</h3> <hr> <!-- 声明一个插槽区域 --> <!-- vue官方规定,每一个插槽都要有一个名称name --> <!-- 默认为default --> <slot name="default"></slot> </div> </template>
如果在封装组件时没有预留任何 <slot>
插槽,则用户提供的任何自定义内容都会被丢弃。
封装组件时,可以为预留的 <slot>
插槽提供后备内容(默认内容)。如果组件的使用者没有为插槽提供任何内容,则后备内容会生效。
1 2 3 4 5 <slot name="default"> <h6> 这是default插槽的默认内容 </h6> </slot>
4.2.2 具名插槽 如果在封装组件时需要预留多个插槽节点,则需要为每个 <slot>
插槽指定具体的 name
名称。这种带有具体名称的插槽叫做“具名插槽”。
注意:没有指定 name 名称的插槽,会有隐含的名称叫做 “default”。
在向具名插槽提供内容的时候,我们可以在一个 <template>
元素上使用 v-slot
指令,并以 v-slot
的参数的形式提供其名称。
此时的template
的作用仅仅起一个包裹的作用,不会渲染成真实的元素
v-slot
不能用在元素上,只能用在template
上
跟 v-on 和 v-bind 一样,v-slot 也有缩写,即把参数之前的所有内容 (v-slot:) 替换为字符 #
。例如 v-slot:header
可以被重写为 #header
代码示例1——v-slot指令
1 <slot name="default"></slot>
1 2 3 4 5 6 <Left> <!-- 将内容放在名称为default的插槽中 --> <template v-slot:default> <p>这是在Left组件的内容区域声明的p标签</p> </template> </Left>
1 2 3 4 5 6 <Left> <!-- 将内容放在名称为default的插槽中 --> <template #default> <p>这是在Left组件的内容区域声明的p标签</p> </template> </Left>
代码示例2——具名插槽使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 <template> <div class="article-container"> <!-- 文章标题 --> <div class="header-box"> <slot name="title"></slot> </div> <!-- 文章内容 --> <div class="content-box"> <slot name="content"></slot> </div> <!-- 文章作者 --> <div class="footer-box"> <slot name="author"></slot> </div> </div> </template> <script> export default { // 组件的名称 name: "Article" } </script> <style lang="less" scoped> .article-container{ > div { min-height: 150px; } .header-box{ background-color: pink; } .content-box{ background-color: lightblue; } .footer-box{ background-color: lightsalmon; } } </style>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 <template> <div class="app-container"> <h1>App 根组件</h1> <hr> <Article> <template #title> <h3>一首诗</h3> </template> <template #content> <div> <p>大海全是水</p> <p>漫展全是腿</p> </div> </template> <template #author> <div> 作者:佚名 </div> </template> </Article> <hr> </div> </template> <script> import Article from '@/components/Article.vue' export default { data() { return { } }, components: { Article } } </script>
4.2.3 作用域插槽 在封装组件的过程中,可以为预留的 <slot>
插槽绑定 props 数据,这种带有 props
数据的 <slot>
叫做“作用域插槽”。
代码示例1——基本使用
1 2 3 4 5 <div class="content-box"> <!-- 绑定自定义属性 --> <!-- 此时的插槽又称为作用域插槽 --> <slot name="content" msg="hello vue.js"></slot> </div>
1 2 3 4 5 6 7 8 <!-- 接收作用域插槽的数据 --> <template #content="scope"> <div> <p>大海全是水</p> <p>漫展全是腿</p> <p>{{ scope.msg }}</p> </div> </template>
相当于子组件向父组件传递数据。
代码示例2——解构插槽
作用域插槽对外提供的数据对象,可以使用解构赋值简化数据的接收过程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <template> <div class="article-container"> <!-- 文章标题 --> <div class="header-box"> <slot name="title"></slot> </div> <!-- 文章内容 --> <div class="content-box"> <!-- 绑定自定义属性 --> <!-- 此时的插槽又称为作用域插槽 --> <slot name="content" msg="hello vue.js" :user="userinfo"></slot> </div> <!-- 文章作者 --> <div class="footer-box"> <slot name="author"></slot> </div> </div> </template> <script> export default { // 组件的名称 name: "Article", data() { return { // 用户的信息对象 userinfo: { name: "Mark", age: 24 } } }, } </script>
1 2 3 4 5 6 7 8 9 10 <!-- 接收作用域插槽的数据:解构赋值的形式 --> <template #content="{msg,user}"> <div> <p>大海全是水</p> <p>漫展全是腿</p> <p>{{ msg }}</p> <p>{{ user.name }}</p> <p>{{ user.age }}</p> </div> </template>
4.3 自定义指令 4.3.1 概念 vue 官方提供了 v-text、v-for、v-model、v-if 等常用的指令。除此之外 vue 还允许开发者自定义指令。
vue 中的自定义指令分为两类,分别是:
4.3.2 私有自定义指令 ① 基本使用 在每个 vue 组件中 ,可以在 directives
节点下声明私有自定义指令。 该指令只能在声明的组件中使用,其他组件不能使用。
在使用自定义指令时,需要加上 v-
前缀。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <template> <div class="app-container"> <h1 v-color>App 根组件</h1> </template> <script> export default { // 私有自定义指令的节点 directives: { // 定义名为color的指令 color: { // 当指令第一次被绑定到元素上的时候,会立即触发bind函数 // 形参中的el表示当前指令所绑定到的那个DOM对象 bind(el) { el.style.color = "blue"; } } } } </script>
② 动态绑定参数值 在 template 结构中使用自定义指令时,可以通过等号(=)的方式,为当前指令动态绑定参数值。
在声明自定义指令时,可以通过形参中的第二个参数(一般命名为binding
),来接收指令的参数值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <template> <div class="app-container"> <h1 v-color="color">App 根组件</h1> <p v-color="'red'">测试</p> <hr> </div> </template> <script> export default { data() { return { color: "green" } }, // 私有自定义指令的节点 directives: { color: { bind(el, binding) { console.log(binding) el.style.color = binding.value; } } } } </script>
打印的内容(binding对象
):
③ update函数 bind 函数只调用 1 次:当指令第一次绑定到元素时调用,当 DOM 更新时 bind 函数不会被触发 。
update 函数会在每次 DOM 更新时被调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <template> <div class="app-container"> <h1 v-color="color">App 根组件</h1> <p v-color="'red'">测试</p> <button @click="color = 'pink'">改变color的颜色</button> <hr> </div> </template> <script> export default { data() { return { color: "green" } }, // 私有自定义指令的节点 directives: { color: { bind(el, binding) { el.style.color = binding.value; }, // 每次DOM更新都会被触发 update(el,binding ) { el.style.color = binding.value; } } } } </script>
简写形式
如果 bind 和update 函数中的逻辑完全相同,则对象格式的自定义指令可以简写成函数格式。
1 2 3 4 5 6 7 8 9 10 <script> export default { // 私有自定义指令的节点 directives: { color(el, binding) { el.style.color = binding.value; } } } </script>
4.3.3 全局自定义指令 全局共享的自定义指令需要通过Vue.directive()
在main.js
中进行声明。
1 2 3 Vue.directive('color' , function (el, binding ) { el.style.color = binding.value; })
使用时,也需要加上v-
前缀。
5 路由 5.1 概念 路由(英文:router)就是对应关系。
SPA(单页面应用程序) 指的是一个 web 网站只有唯一的一个 HTML 页面,所有组件的展示与切换都在这唯一的一个页面内完成。此时,不同组件之间的切换需要通过前端路由来实现。
结论:在 SPA 项目中,不同功能之间的切换,要依赖于前端路由来完成!
前端路由
通俗易懂的概念:Hash 地址与组件 之间的对应关系。
工作方式:
用户点击了页面上的路由链接
导致了 URL 地址栏中的 Hash 值发生了变化
前端路由监听了到 Hash 地址的变化
前端路由把当前 Hash 地址对应的组件 渲染都浏览器中
实现简易的前端路由
略
5.2 vue-router的基本使用 5.2.1 概念 vue-router 是 vue.js 官方给出的路由解决方案。它只能结合 vue 项目进行使用,能够轻松的管理 SPA 项目中组件的切换。
5.2.2 安装和配置
1 npm i vue-router@3.5.2 -S
创建路由模块:在 src 源代码目录下,新建 router/index.js
路由模块,并初始化如下的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import Vue from "vue" import VueRouter from "vue-router" Vue.use(VueRouter) const router = new VueRouter()export default router
导入并挂载路由模块:在 src/main.js
入口文件中,导入并挂载路由模块。
1 2 3 4 5 6 7 8 9 10 11 12 import Vue from 'vue' import App from './App.vue' import router from "@/router" Vue.config.productionTip = false new Vue({ render : h => h(App), router : router }).$mount('#app' )
5.2.3 使用
声明路由链接和占位符:在 src/App.vue
组件中,使用 vue-router 提供的<router-view>
声明路由占位符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <template> <div class="app-container"> <h1>App根组件</h1> <a href="#/home">首页</a> <a href="#/movie">电影</a> <a href="#/about">关于</a> <hr> <!-- 定义路由链接,作用:占位符 --> <router-view></router-view> </div> </template> <script> export default { name: "App" } </script>
声明路由的匹配规则:在 src/router/index.js
路由模块中,通过 routes
数组声明路由的匹配规则。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import Vue from "vue" import VueRouter from "vue-router" import Home from "@/components/Home.vue" import Movie from "@/components/Movie.vue" import About from "@/components/About.vue" Vue.use(VueRouter) const router = new VueRouter({ routes : [ { path : "/home" , component : Home }, { path : "/movie" , component : Movie }, { path : "/about" , component : About } ] }) export default router
用路由链接<router-link>
代替a
标签
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <template> <div class="app-container"> <h1>App根组件</h1> <!-- <a href="#/home">首页</a> <a href="#/movie">电影</a> <a href="#/about">关于</a> --> <!-- 可以省略 # --> <router-link to="home">首页</router-link> <router-link to="movie">电影</router-link> <router-link to="about">关于</router-link> <hr> <!-- 定义路由链接,作用:占位符 --> <router-view></router-view> </div> </template> <script> export default { name: "App" } </script>
5.3 vue-router的常见使用 5.3.1 路由重定向 路由重定向指的是:用户在访问地址 A 的时候,强制用户跳转到地址 C ,从而展示特定的组件页面。
通过路由规则的 redirect 属性,指定一个新的路由地址,可以很方便地设置路由的重定向。
1 2 3 4 5 6 7 8 9 const router = new VueRouter({ routes : [ { path : "/" , redirect : "/home" }, { path : "/home" , component : Home }, { path : "/movie" , component : Movie }, { path : "/about" , component : About } ] })
5.3.2 嵌套路由 ① 概念 通过路由实现组件的嵌套展示,叫做嵌套路由。
模板内容中又有子级路由链接,点击子级路由链接显示子级模板内容
② 子路由链接和占位符 在 About.vue
组件中,声明 tab1
和 tab2
的子路由链接以及子路由占位符。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <template> <div class="about-container"> <h3>About 组件</h3> <!-- 子级路由链接 --> <router-link to="/about/tab1">tab1</router-link> <router-link to="/about/tab2">tab2</router-link> <hr /> <!-- 子级路由占位符 --> <router-view></router-view> </div> </template> <script> export default { name: 'About' } </script>
③ 子路由规则 在 src/router/index.js
路由模块中,导入需要的组件,并使用 children
属性声明子路由规则。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 import Vue from "vue" import VueRouter from "vue-router" import Home from "@/components/Home.vue" import Movie from "@/components/Movie.vue" import About from "@/components/About.vue" import Tab1 from "@/components/tabs/Tab1.vue" import Tab2 from "@/components/tabs/Tab2.vue" Vue.use(VueRouter) const router = new VueRouter({ routes : [ { path : "/" , redirect : "/home" }, { path : "/home" , component : Home }, { path : "/movie" , component : Movie }, { path : "/about" , component : About, children : [ { path : "tab1" , component : Tab1 }, { path : "tab2" , component : Tab2 } ] } ] }) export default router
默认子路由
如果children数组中,某个路由规则的path值为空字符串,则这条路由规则叫做默认子路由。
5.3.3 动态匹配路由 ① 概念
问题的提出
有如下 3 个路由链接:
1 2 3 <router-link to="/movie/1">电影1</router-link> <router-link to="/movie/2">电影2</router-link> <router-link to="/movie/3">电影3</router-link>
定义如下 3 个路由规则,是否可行?
1 2 3 {path : "/movie/1" , component : Movie} {path : "/movie/2" , component : Movie} {path : "/movie/3" , component : Movie}
缺点:路由规则的复用性差。
动态路由概念
动态路由指的是:把 Hash 地址中可变的部分定义为参数项 ,从而提高路由规则的复用性。
在 vue-router 中使用英文的冒号(:
)来定义路由的参数项。
1 {path : "/movie/:id" , component : Movie}
② $route.params 参数对象 在动态路由渲染出来的组件中,可以使用 this.$route.params
对象访问到动态匹配的参数值。不常用。
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <template> <div class="app-container"> <h1>App根组件</h1> <router-link to="/home">首页</router-link> <router-link to="/movie/1">洛基</router-link> <router-link to="/movie/2">雷神</router-link> <router-link to="/movie/3">复联</router-link> <router-link to="/about">关于</router-link> <hr> <!-- 定义路由链接,作用:占位符 --> <router-view></router-view> </div> </template> <script> export default { name: "App" } </script>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const router = new VueRouter({ routes : [ { path : "/" , redirect : "/home" }, { path : "/home" , component : Home }, { path : "/movie/:id" , component : Movie }, { path : "/about" , component : About, children : [ { path : "tab1" , component : Tab1 }, { path : "tab2" , component : Tab2 } ] } ] })
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <template> <div class="movie-container"> <!-- this.$route是路由的“参数对象” --> <!-- this.$router是路由的“导航对象” --> <h3>Movie 组件 --- {{ $route.params.id }}</h3> <button @click="showThis">打印 this</button> </div> </template> <script> export default { name: 'Movie', methods: { showThis() { console.log(this) } } } </script> <style lang="less" scoped> .movie-container { min-height: 200px; background-color: lightsalmon; padding: 15px; } </style>
③ 使用 props 接收路由参数 为了简化路由参数的获取形式,vue-router 允许在路由规则中开启 props 传参。常用。
1 2 3 4 5 6 7 8 const router = new VueRouter({ routes : [ { path : "/" , redirect : "/home" }, { path : "/home" , component : Home }, { path : "/movie/:id" , component : Movie, props : true } ] })
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <template> <div class="movie-container"> <!-- this.$route是路由的“参数对象” --> <!-- this.$router是路由的“导航对象” --> <h3>Movie 组件 --- {{ $route.params.id }} --- {{ id }}</h3> <button @click="showThis">打印 this</button> </div> </template> <script> export default { name: 'Movie', // 接收props数据 props: ["id"], methods: { showThis() { console.log(this) } } } </script>
注意:例如/movie/1?name=zs&age=24
:
在hash地址中,/
后面的参数项叫做“路径参数”
在路由参数对象中,需要使用this.$route.params
来访问路径参数
在hash地址中,?
后面的参数项叫做“查询参数”
在路由参数对象中,需要使用this.$route.query
来访问查询参数
5.3.4 导航 ① 概述 在浏览器中,点击链接实现导航的方式,叫做声明式导航。例如:普通网页中点击 <a>
链接、vue 项目中点击 <router-link>
都属于声明式导航。
在浏览器中,调用 API 方法实现导航的方式,叫做编程式导航。例如:普通网页中调用 location.href
跳转到新页面的方式,属于编程式导航。
vue-router 提供了许多编程式导航的 API,其中最常用的导航 API 分别是:
② $router.push 调用 this.$router.push()
方法,可以跳转到指定的 hash 地址,从而展示对应的组件页面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <template> <div class="home-container"> <h3>Home 组件</h3> <hr /> <button @click="gotoLk">通过 push 跳转到“洛基”页面</button> </div> </template> <script> export default { name: 'Home', methods: { gotoLk() { // 通过编程式导航 API,导航跳转到指定的页面 this.$router.push('/movie/1') } } } </script>
③ $router.replace 调用 this.$router.replace()
方法,可以跳转到指定的 hash 地址,从而展示对应的组件页面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <template> <div class="home-container"> <h3>Home 组件</h3> <hr /> <button @click="gotoLk">通过 push 跳转到“洛基”页面</button> <button @click="gotoLk2">通过 replace 跳转到“洛基”页面</button> </div> </template> <script> export default { name: 'Home', methods: { gotoLk() { // 通过编程式导航 API,导航跳转到指定的页面 this.$router.push('/movie/1') }, gotoLk2() { this.$router.replace('/movie/1') } } } </script>
④ $router.go 调用 this.$router.go()
方法,可以在浏览历史中前进和后退。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <template> <div class="movie-container"> <h3>Movie 组件 --- {{ $route.params.id }} --- {{ id }}</h3> <button @click="goback">后退</button> </div> </template> <script> export default { name: 'Movie', // 接收props数据 props: ["id"], methods: { goback() { // 负数表示后退几次 // 正数表示前进几次 this.$router.go(-1) } } } </script>
简化用法
在实际开发中,一般只会前进和后退一层 页面。因此 vue-router 提供了如下两个便捷方法:
$router.back()
$router.forward()
1 2 <button @click="this.$router.back()">后退</button> <button @click="this.$router.forward()">前进</button>
5.3.5 导航守卫 ① 概念 导航守卫可以控制路由的访问权限。
② 全局前置守卫 每次发生路由的导航跳转时,都会触发全局前置守卫。因此,在全局前置守卫中,程序员可以对每个路由 进行访问权限的控制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import Vue from "vue" import VueRouter from "vue-router" Vue.use(VueRouter) const router = new VueRouter({ }) router.beforeEach(() => { })
全局前置守卫的回调函数中接收 3 个形参,格式为:
1 2 3 4 5 6 7 8 router.beforeEach((to, from , next ) => { next() })
③ next 函数的 3 种调用方式 参考示意图,分析 next 函数的 3 种调用方式最终导致的结果:
当前用户拥有后台主页的访问权限,直接放行:next()
当前用户没有后台主页的访问权限,强制其跳转到登录页面:next('/login')
当前用户没有后台主页的访问权限,不允许跳转到后台主页:next(false)
④ 控制后台主页的访问权限
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 router.beforeEach((to, from , next ) => { if (to.path === "/main" ) { const token = localStorage .getItem("token" ) if (token) { next() } else { next("/login" ) } } else { next() } })
5.4 实践案例 略,代码见代码仓库
以下学习来源:尚硅谷 和黑马程序员
6 vuex 6.1 概述 Vuex是专门在 Vue 中实现集中式状态(数据)管理的一个 Vue 插件,对 vue 应用中多个组件的共享状态进行集中式的管理(读/写) ,也是一种组件间通信的方式,且适用于任意组件间通信。
多组件共享数据——全局事件总线实现
多组件共享数据——vuex实现
应用vuex的时机
多个组件依赖于同一状态
来自不同组件的行为需要变更同一状态
原理图:
优点
能够在Vuex中集中管理共享的数居,易于开发和后期维护
能够高效地实现组件之间的数据共享,提高开发效率
存储在vuex中的数据都是响应式的,能够实时保持数据与页面的同步
一般情况下,只有组件之间共享的数据,才有必要存储到vuex中;对于组件中的私有数据,依旧存储在组件自身的data中即可。
6.2 基本使用 6.2.1 环境搭建
安装(注意vue2对应的vuex版本为3,vue3对应的版本为4):
6.2.2 应用案例——准备
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <template> <div> <h3>当前最新的count值为: </h3> <button>+1</button> </div> </template> <script> export default { data() { return { } }, } </script>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <template> <div id="app"> <my-addition></my-addition> <p>------------------------------------------------</p> <my-subtraction></my-subtraction> </div> </template> <script> import Addition from '@/components/Addition.vue' import Subtraction from '@/components/Subtraction.vue' export default { name: 'App', components: { 'my-addition': Addition, 'my-subtraction': Subtraction } } </script>
运行结果
6.3核心概念 Vuex中的主要核心概念有:
State
Mutation
Action
Getter
6.3.1 State State提供唯一的公共数据源,所有共享的数据都要统一放到 Store的 State I中进行存储。
1 2 3 4 5 6 export default new Vuex.Store({ state : { count : 0 }, })
访问共享数据的方式
方式1:通过 this.$store.state.全局数据名称
访问,由于在模板字符串中,是不需要写this的,所以直接写this后边的。
1 2 3 4 5 6 <template> <div> <h3>当前最新的count值为: {{ $store.state.count }}</h3> <button>+1</button> </div> </template>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <template> <div> <h3>当前最新的count值为: {{ count }}</h3> <button>-1</button> </div> </template> <script> import { mapState } from 'vuex' export default { data() { return { } }, computed: { ...mapState(['count']) }, } </script>
6.3.2 Mutation Mutations用于变更 Store 中的数据。只有 mutations里的函数,才有权利修改 state 的数据,此外mutations里不能包含异步操作 。
通过Mutation更改状态
方式1:this.$store.commit()
触发 mutations
1 2 3 4 5 6 7 8 9 10 11 12 export default new Vuex.Store({ state : { count : 0 }, mutations : { add (state ) { state.count++ } } })
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <script> export default { data() { return { } }, methods: { add() { // commit触发某个mutation this.$store.commit('add') } }, } </script>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 export default new Vuex.Store({ state : { count : 0 }, mutations : { add (state ) { state.count++ }, addN (state, step ) { state.count += step } } })
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <template> <div> <h3>当前最新的count值为: {{ $store.state.count }}</h3> <button @click="add">+1</button> <button @click="addN">+n</button> </div> </template> <script> export default { data() { return { } }, methods: { add() { this.$store.commit('add') }, addN() { // 每次加3 this.$store.commit('addN', 3) } }, } </script>
方式2:在需要变更状态的组件内:
从vuex中按需导入 mapMutations
函数
通过刚才按需导入的 mapMutations
函数,映射为当前组件的methods
函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 export default new Vuex.Store({ state : { count : 0 }, mutations : { add (state ) { state.count++ }, addN (state, step ) { state.count += step }, sub (state ) { state.count-- }, subN (state, step ) { state.count -= step } } })
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 <template> <div> <h3>当前最新的count值为: {{ count }}</h3> <button @click="handler1">-1</button> <button @click="handler2">-n</button> </div> </template> <script> // 按需引入 import { mapState, mapMutations } from 'vuex' export default { data() { return { } }, computed: { ...mapState(['count']) }, methods: { ...mapMutations(['sub', 'subN']), handler1() { this.sub() }, handler2() { // 自减3 this.subN(3) } }, } </script>
6.3.3 Action Actions用于处理异步任务 。
如果通过异步操作变更数据,必须通过 Action,而不能使用Mutation,但是在 Action中还是要通过触发Mutation的方式间接变更数据。
注意: 在Actions 中不能直接修改 state中的数据,要通过 mutations修改。
不使用Action的情况
1 2 3 4 5 add (state ) { setTimeout (() => { state.count++ }, 1000 ) }
使用Action处理异步操作
方式1:使用this.$store.dispatch
触发 Actions
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 export default new Vuex.Store({ state : { count : 0 }, mutations : { add (state ) { state.count++ } }, actions : { addAsync (context ) { setTimeout (() => { context.commit('add' ) }, 1000 ) } } })
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <template> <div> <h3>当前最新的count值为: {{ $store.state.count }}</h3> <button @click="handler1">+1 async</button> </div> </template> <script> export default { methods: { // 异步自增1 handler1() { // dispatch用来触发某个action this.$store.dispatch('addAsync') } }, } </script>
执行结果:
1 2 3 4 5 6 7 8 9 actions: { addNAsync (context ) { setTimeout (() => { context.commit('addN' , step) }, 1000 ) } }
1 2 3 4 5 6 7 8 9 10 11 <script> export default { methods: { // 异步自增1 handler1() { // dispatch用来触发某个action this.$store.dispatch('addNAsync', 5) } }, } </script>
方式2:
从Vuex中按需导入 mapActions
函数
将指定的 actions 函数,映射为当前组件 methods 的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 export default new Vuex.Store({ state : { count : 0 }, mutations : { sub (state ) { state.count-- } }, actions : { subAsync (context ) { setTimeout (() => { context.commit('sub' ) }, 1000 ) } } })
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <template> <div> <h3>当前最新的count值为: {{ count }}</h3> <button @click="handler3">-1</button> </div> </template> <script> import { mapActions } from 'vuex' export default { methods: { ...mapActions(['subAsync']) handler3() { this.sub() } }, } </script>
同样该方法可以传递参数
6.3.4 Getter Getter 用于对 Store中的数据进行加工处理形成新的数据。
Getter 不会修改 Store 中的原数据 ,它只起到一个包装器的作用,将Store中的数据加工后输出出来。
Getter可以对 Store中已有的数据加工处理之后形成新的数据,类似Vue的计算属性。
Store中数据发生变化, Getter 的数据也会跟着变化。
1 2 3 4 5 6 7 8 9 10 11 export default new Vuex.Store({ state : { count : 0 }, getters : { showNum (state ) { return '当前最新的数量为: ' + state.count } } })
使用
方式1:通过 this.$store.getters.名称
访问
1 <h3 > {{ $store.getters.showNum }}</h3 >
1 2 3 4 5 import { mapGetters } from 'vuex' computed :{ ...mapGetters(['showNum' ]) }