共计 31049 个字符,预计需要花费 78 分钟才能阅读完成。
Vue 基础用法
一、Vue 的指令与过滤器
一、指令的概念
指令是 vue 为开发者提供的 模板语法,用于 辅助开发者渲染页面的基本结构。
vue 中的指令 按照不同的用途 分为6大类:
- 内容渲染 指令
- 属性绑定 指令
- 事件绑定 指令
- 双向绑定 指令
- 条件渲染 指令
- 列表渲染 指令
1.内容渲染指令
内容渲染指令 用来辅助开发者 渲染 DOM 元素的文本内容。常用内容渲染指令有三个:
v-text{{}}v-html
v-text
用法示例:
<!-- 把 username 对应的值,渲染到第一个 P 标签中-->
<p v-text="username"></p>
<!-- 把 gender 对应的值,渲染到第二个 p 标签中-->
<!-- 注意:第二个 p 标签中,默认的文本 “性别” 会被 gender 的值覆盖掉-->
<p v-text="gender">性别</p>
{{}} 插值表达式
{{}} 语法专门用来解决 v-text 覆盖原本内容的问题:
<!-- 使用 {{}} ,将对应的值渲染到元素的内容节点中-->
<p>姓名: {{ username }}</p>
<p>性别: {{ gender }}</p>
注意:插值表达式只能用在内容节点,不能用在属性节点。
v-html
v-text 指令和插值表达式只能渲染 纯文本内容,若要把 包含 HTML 标签的字符串 渲染为页面的 HTML 元素,则需要用到 v-html:
<!-- data 中定义了名为 discription 的数据:
<h5 style="color: red;">我在学习 Vue</h5> -->
<p v-html="discription"></p>
2.属性绑定指令
v-bind
如果要为 元素的属性 动态绑定 属性值,则需要用到 v-bind 属性绑定指令:
<!-- 假设有如下的 data 数据:
data: {
inputValue: '请输入内容',
imgSrc:'https://cn.vue.js.org/images/logo.png'
}
-->
<!-- 使用 v-bind 指令,为 input 的 placeholder 动态绑定属性值 -->
<input type="text" v-bind:placeholder="inputValue" />
<br/>
<!-- 使用 v-bind 指令,为 img 的 src 动态绑定属性值 -->
<img v-bind:src="imgSrc" alt=""/>
属性绑定指令的简写形式:(简写为 ‘:’ )
<!-- 假设有如下的 data 数据:
data: {
inputValue: '请输入内容',
imgSrc:'https://cn.vue.js.org/images/logo.png'
}
-->
<!-- 使用 v-bind 指令,为 input 的 placeholder 动态绑定属性值 -->
<input type="text" :placeholder="inputValue" />
<br/>
<!-- 使用 v-bind 指令,为 img 的 src 动态绑定属性值 -->
<img :src="imgSrc" alt=""/>
使用 JavaScript 表达式
在 vue 提供的模板渲染语法中,除了支持 绑定简单的数据值,还支持 JavaScript 表达式的运算。
3.事件绑定指令
vue 提供了 v-on 事件绑定 指令,用来辅助为 DOM 元素绑定事件监听:
<h3>count 的值为:{{count}}</h3>
<button v-on:click="addCount">+1</button>
注意: 原生 DOM 对象有 onclick、oninput、onkeyup 等,替换为 vue 的事件绑定形式后,分别为:v-on:click、v-on:input、v-on:keyup。
通过 v-on 绑定的事件处理函数,需要在 methods 节点 中进行声明:
const vm = new Vue({
el: '#app',
data: { count: 0},
methods: { // v-on 绑定的事件处理函数,需声明在 method 节点中
addCount() {
// this 表示当前 new 出来的 vm 实例对象,
// 通过 this 可以访问到 data 中的数据
this.count += 1
}
},
})
事件绑定的简写形式(简写为 ‘@’ ):
<div id="app">
<h3>count 的值为:{{count}}</h3>
<button @click="addCount"></button>
</div>
事件对象 event
在 v-on 指令所绑定的事件处理函数中,可以接收到事件对象 event:
<h3>count 的值为:{{count}}</h3>
<button @click="addCount">+1</button>
----------------------------------------------------
methods: {
addCount(e) { // 接收事件参数对象 event,简写为 e
const nowBgColor = e.target.style.backgroundColor
e.taget.style.backgroundColor = nowBgColor === 'red'?'' : 'red'
this.count += 1
}
}
绑定事件并传参
在使用 v-on 指令绑定事件时,可以使用 ‘ () ’ 进行传参:
<h3>count 的值为:{{count}}</h3>
<button @click="addNewCount(2)">+2</button>
-----------------------------------------------------
methods: {
// 在形参处用 step 接收传递过来的参数值
addNewCount(step) {
this.count += step
}
}
$event 自定义事件
$event 是 Vue 提供的 特殊变量,用来表示 原生的事件参数对象 event。$event 可以解决事件参数对象 event 被覆盖的问题:
<h3>count 的值为: {{count}}</h3>
<buttom @click="addNewCount(2,$event)">+2</buttom>
-------------------------------------------------------
methods: {
// 在形参出用 e 接收传递过来的原生事件参数对象 $event
addNewCount(step,e) {
const nowBgColor = e.target.style.backgroundColor
e.target.style.backgroundColor = nowBgColor === 'cyan'?'' : 'cyan'
this.count += step
}
}
事件修饰符
在事件处理函数中调用 preventDefault() 或 stopPropagation() 是很常见的需求。因此 vue 提供了 事件修饰符,来辅助 对事件的触发进行控制。常用的 5 个事件修饰符如下:
| 事件修饰符 | 说明 |
|---|---|
| .prevent | 阻止默认行为(例如:阻止 a链接的跳转,表单的提交等) |
| .stop | 阻止事件冒泡 |
| .capture | 以捕获模式触发当前的事件处理函数 |
| .once | 绑定的事件只触发一次 |
| .self | 只有在 event.target 是当前元素自身时触发事件处理函数 |
<!-- 触发 click 点击事件时,阻止 a链接的默认跳转行为 -->
<a href="https://www.baidu.com" @click.prevent="onLinkClick">百度首页</a>
按键修饰符
在监听 键盘事件 时,我们需要 判断详细的按键,此时,可以为 键盘相关的事件 添加 按键修饰符,例如:
<!-- 只有在 'key' 是 'Enter' 时调用 'vm.submit()' -->
<input @keyup.enter="submit">
<!-- 只有在 'key' 是 'Esc' 时调用 'vm.clearInput()' -->
<input @keyup.esc="clearInput">
4.双向绑定指令
vue 提供了 v-model 双向数据绑定 指令,用来辅助开发者在 不操作 DOM 的前提下,快速获取表单的数据。
<p>用户名是: {{username}}</p>
<input type="text" v-model="username" />
<p>选中的省份是: {{province}}</p>
<select v-model="province">
<option value="">请选择</option>
<option value="1">北京</option>
<option value="2">河北</option>
<option value="3">黑龙江</option>
</select>
注意: v-model 指令 只能配合表单元素 一起使用。
为了方便对用户输入的内容进行处理, vue 为 v-model 指令提供了 3 个修饰符:
| 修饰符 | 作用 | 示例 |
|---|---|---|
| .number | 自动将用户的输入值转为数值类型 | <input v-model.number="age" /> |
| .trim | 自动过滤用户输入的首位空白字符 | <input v-model.trim="msg" /> |
| .lazy | 在 ‘change’ 时而非 ‘input’ 时更新 | <input v-model.lazy="msg" /> |
5.条件渲染指令
条件渲染指令 用来辅助开发者 按需控制 DOM 的显示与隐藏。
- v-if
- v-show
v-if 和 v-show 的区别
实现原理不同:
- v-if 指令会 动态的创建或移除 DOM 元素,从而控制元素在页面上的显示与隐藏
- v-show 指令会动态为元素 添加或移除 style=“diplay:none;” 样式,从而控制元素的显示与隐藏
性能消耗不同:
v-if 有更高的 切换开销,而 v-show 有更高的 初始渲染开销。
- 如果需要 非常频繁的切换,则使用 v-show
- 如果在 运行时条件很少改变,则使用 v-if
v-else
v-if 可以单独使用,或配合 v-else 指令一起使用:
<div v-if="Math.random() > 0.5">随机数大于 0.5</div>
<div v-else>随机数小于或等于 0.5</div>
v-else-if
v-else-if 指令,充当 v-if 的 “else-if 块” ,可以连续使用:
<div v-if="type === 'A'">优秀</div>
<div v-else-if="type === 'B'">良好</div>
<div v-else-if="type === 'C'">一般</div>
<div v-else>差</div>
6.列表渲染指令
vue 提供了 v-for 指令,用来辅助开发者 基于一个数值来循环渲染相似的 UI 结构。
v-for 指令需要使用 item in items 的特殊语法:
- items 是 待循环的数组
- item 是 当前的循环项
data: {
list: [ // 列表数据
{ id: 1, name: 'zs'},
{ id: 2, name: 'ls'}
]
}
------------------------------------------------------
<ul>
<li v-for="item in list">姓名是:{{item,name}}</li>
</ul>
v-for 中的索引
v-for 指令还支持一个 可选的 第二个参数,即 当前项的索引。
data: {
list: [ // 列表数据
{ id: 1, name: 'zs'},
{ id: 2, name: 'ls'}
]
}
------------------------------------------------------
<ul>
<li v-for="(item, index) in list">索引是: {{index}}, 姓名是: {{item,name}}</li>
</ul>
注意: v-for 指令中的 item 项 和 index 索引 都是形参,可以根据需要进行 重命名。
使用 key 维护列表的状态
当 列表的数据变化时,默认下,vue 会 尽可能的复用 已存在的 DOM 元素,从而 提升渲染的性能。但这默认的优化策略,会导致 有状态的列表无法被正确更新。
为了给 vue 一个提示,以便它能跟踪每个节点的身份,从而在保证 有状态的列表被正确更新的前提下,提示渲染的性能。此时需要为每项提供一个 唯一的 key 属性。
<!-- 用户列表区域 -->
<ul>
<li v-for="user in userlist" :key="user.id">
<input type="checkbox" />
姓名: {{user.name}}
</li>
</ul>
key 的注意事项
- key 的值只能是 字符串 或 数字 类型。
- key 的值 必须具有唯一性。
- 建议把 数据项的 id 属性值 作为 key 的值。
- 建议使用 v-for 指令时 一定要指定 key 的值。
二、过滤器(仅限 Vue 2)
过滤器 常用于 文本的格式化,可以用于 插值表达式 和 v-bind 属性绑定。
过滤器应该被添加在 JavaScript 表达式的尾部,由 管道符 进行调用:
<!-- 在 {{}} 中通过 “管道符” 调用 capitalize 过滤器,对 message 的值进行格式化 -->
<p>{{ message | capitalize }}</p>
<!-- 在 v-bind 中通过 “管道符” 调用 formatId 过滤器,对 rawId 的值进行格式化-->
<div v-bind:id="rawId | formatId"></div>
1.定义过滤器
可以在 filters 节点 中定义过滤器:
const vm = new Vue({
el: '#app',
data: {
message: 'hello vue.js',
info: 'title info'
},
filters: { // 在 filters 节点下定义过滤器
capitalize(str) { // 把首字母转为大写的过滤器,形参(str),为管道符前面的值
// 一定要有返回值
return str.charAt(0).toUpperCase() + str.slice(1)
}
}
})
2.私有过滤器 和 全局过滤器
在 filters 节点下定义的过滤器称为 私有过滤器,因为它 只能在当前 vm 实例所控制的 el 区域内使用。
如果希望在 多个 vue 实例之间共享过滤器, 则可以按照如下的格式定义全局过滤器:
// 全局过滤器 独立于每个 vm 实例之外
/* Vue.filter() 方法接收两个参数:
第一个参数为全局过滤器的 名字,
第二个参数为全局过滤器的 处理函数*/
Vue.filter('capitalize', (str) => {
return str.charAt(0).toUpperCase() + str.slice(1) + '~~'
})
3.连续调用多个过滤器
过滤器可以 串联的 进行调用:
/* 把 message 的值,交给 filterA 进行处理
把 filterA 处理的结果,再交给 filterB 进行处理
最后把 filterB 处理的结果,作为最终值渲染到页面上 */
{{ message | filterA | filterB }}
4.过滤器传参
过滤器本质是 JavaScript 函数:
<!-- 调用 maxLength 过滤器时传参 -->
<p>{{ text | capitalize | maxLength(5) }}</p>
Vue.filter('capitalize', (str) => {
return str.charAt(0).toUpperCase() + str.slice(1) + '~~'
})
// 第一个参数为管道符前面待处理的值,第二个参数之后,才是调用过滤器时传递的参数。
Vue.filter('maxLength', (str,len = 10) => {
if(str.length <= len) return str
return str.slice(0,len) + '...'
})
二、侦听器与计算属性
一、侦听器
1.什么是 watch 侦听器
watch侦听器 允许开发者监听数据的变化,从而 针对数据的变化做特定的操作:
const vm = new Vue({
el: '#app',
data: { username: ''},
watch: {
// 监听 username 值的变化
// newVal 是变化后的新值,oldVal 是变化之前的旧值
username(newVal,oldVal) {
console.log(newVal,oldVal)
}
}
})
2.侦听器的格式
1.方法格式的侦听器
- 缺点:
- 无法在刚进入页面的时候自动触发
- 如果侦听的是一个对象,如果对象中的属性发生了变化,则不会触发侦听器
2.对象格式的侦听器
- 好处:
- 可以通过 immediate 选项,自动触发
- 可以通过 deep 选项,让侦听器深度监听对象中每个属性的变化
const vm = new Vue({
el: '#app',
data: {
info: {
username: 'admin'
}
},
watch: {
// 定义对象格式的侦听器
username: {
// 侦听器的处理函数
handler(newVal,oldVal) {
console.log(newVal,oldVal)
},
// immediate 选项的默认值为 false
immediate: true,
// 开启深度监听,对象中任何一个属性变化都会触发侦听器
deep: true
}
// 如果要侦听的是对象的子属性的变化,则必须包裹一层单引号
'info.username'(newVal) {
console.log(newVal)
}
}
})
二、计算属性
1.什么是计算属性
计算属性指 通过一系列运算 之后,最终得到一个 属性值。
这个 动态计算出来的属性值 可以被模板结构或 methods 方法使用:
var vm = new Vue({
el: '#app',
data: {
r: 0, g: 0, b: 0
},
computed: {
rgb() {
return `rgb( ${this.r}, ${this.g}, ${this.b} )`
}
},
methods: {
show() { console.log(this.rgb) }
},
})
特点:
- 定义的时候,要被定义为 方法
- 在 使用 计算属性时,当 普通属性 使用即可
好处:
- 实现了代码的复用
- 只要计算属性中依赖的 数据源变化 了,则 计算属性会自动重新求值
三、axios
- 调用 axios 之后,使用 async / await 进行简化
- 使用解构赋值,从 axios 封装的大对象中,把 data 属性解构出来
- 把解构出来的 data 属性,使用冒号进行重命名,一般重命名为 (data : res)
一、axios 的基础语法
axios({
method: '请求的类型',
url: '请求的 URL 地址'
}).then((result) => {
// .then 用来指定请求成功之后的回调函数
// 形参中的 result 是请求成功之后的结果
})
注意: axios 在请求到数据之后,在外面会套一层其他数据:
// 接口服务器
{
status: '',
msg: '',
data: []
}
// axios
{
config: {},
data: { 真实的数据 },
headers: {},
request: {},
status: xxx,
statusText: ''
}
二、结合 async 和 await 调用 axios
axios({
// 请求方式
method: 'GET',
// 请求地址
url: 'http://www.xxx',
// URL 中的查询参数 (GET)
params: {
id: 1
},
// 请求体参数 (POST)
data: {}
}).then(function(result) {
console.log(result)
})
<button id="btnPost">发起POST请求</button>
<script>
document.querySelector('#btnPost').addEventListener('click', async function() {
// 如果调用某个方法的返回值是 Promise 实例,则前面可以添加 await
// await 只能用在被 async 修饰的方法中
const result = await axios({
method: 'POST',
url: 'http://www.xxx',
data: {
name: 'zs',
age: 20
}
})
console.log(result)
})
</script>
三、使用解构赋值
<button id="btnPost">发起POST请求</button>
<script>
document.querySelector('#btnPost').addEventListener('click', async function() {
// 解构赋值的时候,使用 : 进行重命名
const {data : res} = await axios({
method: 'GET',
url: 'http://www.xxx'
})
console.log(res.data)
})
</script>
四、基于 axios.get 和 axios.post 发起请求
<body>
<button id="btnGET">GET</button>
<button id="btnPOST">POST</button>
<script src="axios文件路径"></script>
<script>
document.querySelector("#btnGET").addEventListener('click', async function() {
const { data : res } = await axios.get("http://www.xxx", {
params: { id: 1 }
})
console.log(res)
})
document.querySelector('#btnPOST').addEventListener('click', async function() {
const { data : res } = await axios.post('url', { name: 'zs', gender: '女' })
console.log(res)
})
</script>
</body>
五、把 axios 挂载到 Vue 的原型上并配置请求根路径
Left 组件
<template>
<button @click="getInfo">发起 GET 请求</button>
</template>
<script>
export default {
methods: {
async getInfo() {
const { data: res } =await this.$http.get('/api/get')
console.log(res)
}
}
}
</script>
main.js
import axios from 'axios'
import .....
// 全局配置 axios 的请求根路径
axios.defaults.baseURL = 'http://www.xxxx:8000'
// 把 axios 挂载到 Vue.prototype 上,供每个 .vue 组件实例直接使用
Vue.prototype.$http = axios
// 今后在每个 .vue 组件中要发起请求,直接调用 this.$http.xxx
new Vue({
render: h => h(App)
}).$mount("#app")
缺点: 无法实现 api 接口的复用
四、vue-cli
vue-cli 是 Vue.js 开发的标准工具,它简化了基于 webpack 创建工程化的 Vue 项目的过程。
一、安装和使用
vue-cli 是 npm 上的一个 全局包,使用 npm install 命令,即可安装到自己的电脑上:
npm install -g @vue/cli
1.基于 vue-cli 快速生成工程化的 Vue 项目:
vue create 项目名 (路径最好全英文)
2.选择安装项目
Please pick a preset: (Use arrow keys)
Default([Vue2] babel, eslint) // Vue 2 立即创建
Default(Vue 3) ([Vue 3] babel, eslint) // Vue 3 立即创建
Manually select features // 手动选择
// 选择第三项后
Please pick a preset: Manually select features
Check the features needed for your project: (Press <space> to select, <a> to toggle all, <i> to invenrt selection) //
(*) Chooose Vue version // 选择 Vue 版本
(*) Babel // 安装 Babel (用于解决 js 兼容性)
() TypeScript // 微软的脚本语言 (ts)
() Progressive // 渐进式框架
() Router // 路由
() Vuex
(*) CSS Pre-processors // CSS 预处理器
() Linter / Formatter // 强制规范化化代码 (不规范不给运行且报错)
() Unit Testing // 单元测试
() E2E Testing // E2E 测试
// 版本选择 略
// CSS 预处理器 略
// 将文件放入哪
Where do you prefer placing config for Babel, ESLint, etc.?
>In dedicated config files // 独立文件 (选择)
In package.json // 放入 package.json 包里
// 将选择存储作为下一次预设? 略
// 预设名字
最后使用
cd 项目名
npm run serve
运行
2.介绍项目的目录结构
- node modules // 第三方包
- public
- favicon.ico // 网页图标
- index.html // 首页
- src // 我们写的源码
- .browerslistrc
- .gitignore
- babel.config.js
- package-lock.json
- package.json // 包管理配置文件
- README.md
src 源代码目录构成
- src
- assets // 一些第三方静态资源例如图片,样式表
- components // 程序员封装的、可复用的组件
- main.js // 项目的入口文件,整个项目的运行要先执行 main.js
- App.vue // 定义 UI 结构
3. vue 项目的运行流程
vue 通过 main.js 把 App.vue 渲染到 index.html 的指定区域中。
- main.js
// 导入 vue 包,得到 Vue 构造函数
import Vue from 'vue'
// 导入 App.vue 根组件, 将来要把 App.vue 中的模板结构渲染到 HTML 页面中
import App from './App.vue'
Vue.config.productionTip = false
// 创建 Vue 的实例对象
new Vue({
//el: '#app',
// 把 render 函数指定的组件,渲染到 HTML 页面中
render: h => h(App),
}).$mount('#app') // 与 el 作用相同
- App.vue
<template>
<div id="app"> <!-- 替换index.html 中的app div -->
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
export default {
name: 'App',
components: {
HelloWorld
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
- index.html
<!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> <!-- 将来会被替换 -->
<!-- built files will be auto injected -->
</body>
</html>
其中:
- App.vue 用来编写待渲染的 模板结构
- index.html 中需要预留一个 el区域
- main.js 把 App.vue 渲染到了 index.html 所预留的区域中
五、vue 组件
一、组件的三个组成部分
1.什么是组件化开发
组件化开发 指的是:根据 封装 的思想,把页面上可重用的 UI 结构封装为组件,从而方便项目开发和维护。
2.vue 项目中的组件化开发
vue 是一个 支持组件化开发 的前端框架。
vue 中规定:组件的后缀名是 .vue。
3.vue 组件的三个组成部分
- template -> 组件的 模板结构
- script -> 组件的 JavaScript 行为
- style -> 组件的 样式
// .vue 文件中的 script
// 默认导出,固定写法
export default {
// data 数据源
// 注意: .vue 组件中的 data 不能指向对象,必须是一个函数
/*data {
username: 'zs' 错误
}*/
data() {
// 这个 return 出去的 { } 中,可以定义数据
return {
username: 'zs'
}
}
}
4.在组件中定义 methods
export default {
// data 数据源
// 注意: .vue 组件中的 data 不能指向对象,必须是一个函数
/*data {
username: 'zs' 错误
}*/
// name 组件的名称,在使用 vue-devtools 调试时,自定义组件名称可以区分每个组件
name: 'Myapp',
data() {
// 这个 return 出去的 { } 中,可以定义数据
return {
username: 'zs'
}
},
methods: {
changeName() {
// 在组件中,this 表示当前组件的实例对象
console.log(this)
this.username = 'admin'
}
},
// 当前组件中的侦听器
watch: {},
// 当前组件中的计算属性
computed: {},
// 当前组件中的过滤器
filters: {}
}
5.启用 less 语法以及唯一根节点
在 .vue 文件中,<template></template>中不能含有两个根节点例如两个 <div>。
注意:vue 3.0 可以使用多个根节点。
一般采取:
<template>
<div>
<div>
123
</div>
<div>
456
</div>
</div>
</template>
要使用 less 语法,则需:
<style lang="less">
h3 {
color: red;
}
</style>
二、组件的基本使用
1.注册组件
1.注册组件的两种方式
vue 中注册组件的方式分为 “全局注册” 和 “局部注册 两种:
- 被 全局注册 的组件,可以在全局任何一个组件内使用
- 被 局部注册 的组件,只能在当前注册的范围内使用
应用场景:
- 若组件在开发期间 使用频率较高,则进行 全局注册
- 若组件 只在特定的情况下用到,则进行 局部注册
全局注册组件
在 vue 项目的 main.js 入口文件中,通过 Vue.component() 方法,可以注册全局组件:
import { createApp } from 'vue'
import App from './App.vue'
// 导入 Swiper 和 Test 两个组件
import Swiper from './components/MySwiper.vue'
import Test from './components/MyTest.vue'
const app = createApp(App)
// 调用 app 实例的 component() 方法,在全局注册 my-swiper 和 my-test 两个组件
// 参数1: 字符串格式,表示组件的 “注册名称”
// 参数2: 需要被全局注册的组件
app.component('my-swiper', Swiper)
app.component('my-test', Test)
app.mount('#app')
使用 app.component() 方法注册的全局组件,直接以标签的形式进行使用:
<template>
<h1>这是 App 根组件</h1>
<my-swiper></my-swiper>
<my-test></my-test>
</template>
局部注册组件
<template>
<h1>这是 App 根组件</h1>
<my-swiper></my-swiper>
<my-search></my-search>
</template>
<script>
import Search from './components/MySearch.vue'
export default {
components: { // 通过 components 节点,为当前组件注册私有子组件
'my-search': Search,
},
}
</script>
2.通过 name 属性注册组件
在注册组件期间,除了可以 直接提供组件的注册名称 之外,还可以把 组件的 name 属性 作为注册后 组件的名称:
<template>
<h3>轮播图组件</h3>
</template>
<script>
export default {
name: 'MySwiper' // name 属性为当前组件的名字
}
</script>
import Swiper from './components/MySwiper.vue'
app.component(Swiper.name, Swiper) // 相当于 app.component('MySwiper', Swiper)
3.使用组件的三个步骤
- 使用 import 语法 导入需要的组件
import Left from "@/components/Left.vue"
- 使用 components 节点注册组件(私有子组件)
export default {
components: {
Left
}
}
- 以 标签形式 使用刚才注册的组件
<div> class='box'>
<Left></Left>
</div>
2.组件之间的样式冲突问题
默认情况下,写在 .vue 组件中的样式会全局生效,因此很容易造成多个组件之间的样式冲突问题。导致组件
之间样式冲突的根本原因是:
- 单页面应用程序中,所有组件的 DOM 结构,都是基于唯一的 index.html 页面进行呈现的
- 每个组件中的样式,都会 影响整个 index.html 页面 中的 DOM 元素
1.自定义属性
为每个组件 分配唯一的自定义组件,在编写组件样式时,通过 属性选择器 来控制 样式的作用域:
<template>
<div class="container" data-v-001>
<h3 data-v-001>轮播图组件</h3>
</div>
</template>
<style>
/* 通过中括号“属性选择器”,来防止组件之间的样式冲突问题 */
.container[data-v-001] {
border: 1px solid red;
}
</style>
2.style 节点的 scoped 属性
为了提高开发效率和体验, vue 为 style 节点 提供了 scoped 属性,从而防止组件之间的样式冲突问题:
<template>
<div class="container">
<h3>轮播图组件</h3>
</div>
</template>
<style scoped>
/* style 节点的 scoped 属性,用来自动为每个组件分配唯一的 “自定义属性” */
.container[data-v-001] {
border: 1px solid red;
}
</style>
3./deep/ 样式穿透
如果给当前组件的 style 节点添加了 scoped 属性,则 当前组件的样式对其子组件是不生效的。如果想让某些样
式对子组件生效,可以使用 /deep/ 深度选择器。
<style lang="less" scoped>
.title {
color: blue; /* 不加 /deep/ 时,生成的选择器格式为 .title[data-v-052242de] */
}
/deep/ .title {
color: blue; /* 加上 /deep/ 时,生成选择器格式为 [data-v-052242de] .title */
}
</style>
注意: /deep/ 是 vue2.x 中实现样式穿透的方案。在 vue3.x 中推荐使用 :deep() 替代。
当使用第三方组件库时,若有修改组件样式的需求,则需要用到 /deep/。
三、组件的 props (自定义属性)
为了提高组件的 复用性,在封装 vue 组件时需要遵守如下的原则:
- 组件的 DOM 结构、Style 样式 要尽量复用
- 组件中要 展示的数据,尽量由组件的使用者提供
为了方便 使用者 为组件提供要展示的数据,vue 组件提供了 props 的概念。
1.什么是组件的 props
props 是组件的 自定义属性,组件的使用者可以通过 props 把数据传递到子组件内部,供子组件内部进行使
用:
<!-- 通过自定义 props,把文章的标题和作者,传递到 my-article 组件中 -->
<my-article title="面朝大海,春暖花开" author="海子"></my-article>
props 的 作用: 父组件通过 props 向子组件传递要展示的数据
props 的 好处: 提高了组件的 复用性
2.在组件中声明 props
在封装 vue 组件时,可以把 动态的数据项 声明为 props 自定义属性,自定义属性可以在当前组件的模板结构中被直接使用:
<!-- my-article 组件的定义如下 -->
<template>
<h3>标题:{{ title }}</h3>
<h5>作者:{{ author }}</h5>
</template>
<script>
export default {
props: ['title', 'author'], // 父组件传递给 my-article 组件的数据,必须在 props 节点中声明。
}
</script>
注意: 如果父组件给子组件 传递了未声明的 props 属性,则这些属性会被忽略,无法被子组件使用。
3.动态绑定 props 的值
可以使用 v-bind 属性绑定 的形式,为组件动态绑定 props 的值:
<!-- 通过 v-bind 属性绑定,为 title 动态赋予一个变量的值 -->
<!-- 通过 v-bind 属性绑定,为 author 动态赋予一个表达式的值-->
<my-article :title="info.title" :author="'post by' + info.author"></my-article>
4.props 是只读的
vue 规定:组件中封装的自定义属性是 只读的,不能直接修改 props 的值,否则会直接报错。
想修改 props 的值,可以 把 props 的值转存到 data 中。
props: ['init'],
data() {
return {
count: this.init // 把 this.init 的值转存到 count
}
}
5.props 的 default 默认值
在声明自定义属性时,可以通过 default 来 定义属性的默认值:
export default {
props: {
init: {
default: 0
}
}
}
6.props 的 type 值类型
在声明自定义属性时,可以通过 type 来 定义属性的值类型:
export default {
props: {
init: {
default: 0,
type: Number
}
}
}
7.props 的 required 必填项
export default {
props: {
init: {
default: 0,
type: Number,
// 必填项校验(若不填则报错)
required: true
}
}
}
四、 Class 与 Style 绑定
在实际开发中经常会遇到 动态操作元素样式 的需求。因此,vue 允许开发者通过 v-bind 属性绑定指令,为元
素动态绑定 class 属性的值 和 行内的 style 样式。
1.动态绑定 HTML 的 class
可以通过 三元表达式,动态的为元素绑定 class 的类名:
<h3 class="thin" :class="isItalic ? 'italic' : ''">MyDeep 组件</h3>
<button @click="isItalic=!isItalic">Toggle Italic</button>
data() {
return { isItalic: true }
}
.thin { // 字体变细
font-weight: 200;
}
.italic { // 倾斜字体
font-style: italic;
}
2.以数组语法绑定 HTML 的 class
如果元素需要动态 绑定多个 class 的类名,此时可以使用 数组的语法格式:
<h3 class="thin" :class="[isItalic ? 'italic' : '', isDelect ? 'delect' : '']">MyDeep 组件</h3>
<button @click="isItalic=!isItalic">Toggle Italic</button>
data() {
return {
isItalic: true,
isDelete: false,
}
}
3.以对象语法绑定 HTML 的 class
使用数组语法动态绑定 class 会导致 模板结构臃肿 的问题,此时可以使用 对象语法 进行 简化:
<h3 class="thin" :class="classObj">MyDeep 组件</h3>
<button @click="classObj.italic = !classObj.italic">Toggle Italic</button>
<button @click="classObj.delete = !classObj.delete">Toggle Delete</button>
data() {
return {
classObj: { // 对象中,属性名是 class 类名,值是布尔值
italic: true,
delete: false,
}
}
}
4.以 对象语法绑定内联的 style
:style 的 对象语法 十分直观,看着像 CSS ,其实是一个 JavaScript 对象:
<div :style="{color: active, fontSize: fsize + 'px', 'background-color': bgcolor}">文本</div>
<button @click="fsize += 1">字号 + 1</button>
<button @click="fsize -= 1">字号 - 1</button>
data() {
return {
active: 'red',
fsize: 30,
bgcolor: 'pink',
}
}
五、组件的生命周期
1.生命周期 & 生命周期函数
生命周期 是指一个组件从 创建 -> 运行 -> 销毁 整个阶段,强调的是一个时间段。
生命周期函数:是由 vue 框架提供的 内置函数,会伴随着组件的生命周期 自动按次序执行。
例:
- 当组件 在内存中被创建完毕 之后,会自动调用 created 函数
- 当组件被成功的 渲染到页面上 之后,会自动调用 mounted 函数
- 当组件 被销毁完毕 之后,会自动调用 unmounted 函数
| 生命周期函数 | 执行时机 | 所属阶段 | 执行次数 | 应用场景 |
|---|---|---|---|---|
| beforeCreate | 在内存中开始创建组件前 | 创建阶段 | 唯一 1 次 | – |
| create | 组件在内存中创建完毕后 | 创建阶段 | 唯一 1 次 | 发送 ajax 请求初始数据 |
| beforeMount | 把组件初次渲染到页面之前 | 创建阶段 | 唯一 1 次 | – |
| mounted | 组件初次在页面渲染完毕之后 | 创建阶段 | 唯一 1 次 | 操作 DOM 元素 |
| beforeUpdate | 在组件被重新渲染之前 | 运行阶段 | 0 或多次 | – |
| updated | 组件在页面中被重新渲染完毕之后 | 运行阶段 | 0 或多次 | – |
| beforeUnmount | 在组件被销毁之前 | 销毁阶段 | 唯一 1 次 | – |
| unmounted | 组件销毁后 | 销毁阶段 | 唯一 1 次 | – |
六、组件之间的数据共享
1.组件之间的关系
组件关系分为 3 种:
- 父子关系
- 兄弟关系
- 后代关系
2.父子组件之间的数据共享
父组件向子组件共享数据
父组件通过 v-bind 属性绑定 向子组件共享数据。同时,子组件需要用 props 接收数据:
// 父组件 在子组件标签上绑定要传递的数据
<my-test-1 :msg="message" :user="userinfo"></my-test-1>
data() {
return {
massage: 'hello vue',
userinfo: { name: 'zs', age: 20 },
}
}
// 子组件
<template>
<h3>测试父子传值</h3>
<p>{{ msg }}</p>
<p>{{ user }}</p>
<button @click="user.name='zs'">修改user</button> // 修改父组件(不建议)
</template>
<script>
export default {
props: ['msg', 'user'], // 不可更改
}
</script>
子组件向父组件共享数据
子组件通过 自定义事件 的方式向父组件共享数据:
// 子组件
<script>
export default {
emits: ['nichange'], // 1.声明自定义事件
data () { return { n1: 0 } },
methods: {
addN1() {
this.n1++
// 2.数据变化时,触发自定义事件
this.$emit('n1change', this.n1) // this 为传参,到父组件
},
},
}
</script>
// 父组件
// 1.监听子组件的自定义事件 n1change
<my-test-1 @nichange='getn1'></my-test-1>
export default {
data() {
methods: {
getn1(n1) { // 2.通过形参,接收子组件传递过来的数据 n1 即 this.n1
this.n1FromSon = n1
},
},
}
}
3.兄弟组件之间的数据共享
在 vue2.x 中,兄弟组件之间数据共享的方案是 EventBus。
- 创建 eventBus.js 模块,并向外共享一个 Vue 的实例对象
- 在数据 发送方,调用 bus.$emit (‘事件名称’, 要发送的数据)方法 触发自定义事件
- 在数据 接收方,调用 bus.$on(‘事件名称’,事件处理函数) 方法 注册一个自定义事件
七、ref 引用
在 vue 中,不需要操作 DOM, 只需维护数据即可,不建议安装和使用 jQuery。
1.什么是 ref 引用
ref 用来辅助开发者在 不依赖于 jQuery 的情况下,获取 DOM 元素或组件的引用。
每个 vue 的组件实例上,都包含一个 $refs 对象,里面存储着对应的 DOM 元素或组件的引用,默认下,组件的 $refs 指向一个空对象。
2.使用 ref 引用 DOM 元素
<template>
<div>
<h1 ref="myh1">App 根组件</h1>
<button @click="showThis">打印this</button>
</div>
</template>
<script>
export default {
methods: {
showThis() {
console.log(this.$refs.myh1)
}
}
}
</script>
3.使用 ref 引用组件实例
<!-- 使用 ref 属性,为对应的组件添加引用名称 -->
<my-counter ref="counterRef"></my-counter>
<button @click="getRef">获取 $refs 引用</button>
methods: {
getRef() {
// 通过 this.$refs.引用名 可以引用组件的实例
console.log(this.$refs.counterRef)
// 引用到组件的实例后,可以调用组件上的 methods 方法
this.$refs.counterRef.add()
},
}
4.控制文本框和按钮的按需切换
通过布尔值 inputVisible 来控制组件中的文本框与按钮的按需切换:
<template>
<input type="text" v-if="inputVisible">
<button v-else @click="showInput">展示 Input 输入框</button>
</template>
<script>
export default {
data() {
return {
// 控制文本框和按钮的按需切换
iniputVisible: false,
}
},
methods: {
showInput() { // 切换布尔值,显示文本框
this.inputVisible = true
},
},
}
</script>
5.让文本框自动获得焦点
当文本框展示出来之后,如果希望它立即获得焦点,则可以为其添加 ref 引用,并调用原生 DOM 对象的
.focus() 方法即可:
<template>
<input type="text" v-if="inputVisible" ref="ipt">
<button v-else @click="showInput">展示 Input 输入框</button>
</template>
<script>
methods: {
showInput() {
this.inputVisible = true
// 获取文本框的 DOM 引用,并调用 .focus() 使其自动获得焦点
this.$refs.ipt.focus()
},
}
</script>
6.this.$nextTick(cb) 方法
组件的 $nextTick(cb) 方法,会把 cb 回调 推迟到下一个 DOM 更新周期之后执行。通俗的理解是:等组件的
DOM 异步地重新渲染完成后,再执行 cb 回调函数。从而能保证 cb 回调函数可以操作到最新的 DOM 元素。
<template>
<input type="text" v-if="inputVisible" ref="ipt">
<button v-else @click="showInput">展示 Input 输入框</button>
</template>
<script>
methods: {
showInput() {
this.inputVisible = true
// 把对 input 文本框的操作,推迟到下次 DOM 更新之后,否则页面上不存在文本框元素(生命周期)
this.$nextTick(() => {
this.$refs.ipt.focus()
})
},
}
</script>
八、动态组件
1.什么是动态组件
动态组件指的是 动态切换组件的显示与隐藏。
2.如何实现动态组件的渲染
<component> 组件 专门用来实现动态组件的渲染:
data() {
// 1.当前要渲染的组件名称
return { comName: 'Left' }
}
<!-- 2.通过 is 属性,动态指定要渲染的组件 -->
<!-- is 属性的值,表示要渲染的组件的名字 -->
<component :is='comName'></component> <!-- 占位 -->
<!-- 3.点击按钮,动态切换组件的名称 -->
<button @click="comName = 'Left'">展示 Left 组件</button>
<button @click="comName = 'Right'">展示 Right 组件</button>
3.使用 keep-alive 保持状态
默认情况下,切换动态组件时 无法保持组件的状态。此时可以用 <keep-alive> 组件保持组件的状态。
keep-alive 可以防止组件在动态切换时被销毁:
<keep-alive>
<component :is="comName"></component>
</keep-alive>
4. keep-alive 对应的生命周期函数
当组件 被缓存 时,会自动触发组件的 deactivated 生命周期函数。
当组件 被激活 时,会自动触发组件的 activated 生命周期函数。
当组件 第一次被创建时 会执行 created 和 avtivated 生命周期。
5. keep-alive 的 include 属性
include 属性用来指定,只有名称匹配的组件会被缓存,
exclude 属性指定哪些不需要被缓存,注意:不要同时使用 include 和 exclude :
<keep-alive include="MyLeft, MyRight">
<component :is="comName"></component>
</keep-alive>
九、插槽
1.什么是插槽
插槽 (slot) 是 vue 为 组件的封装者 提供的能力。允许开发者在封装组件时,把 不确定的、希望由用户指定的部分 定义为插槽。
可以把插槽认为是组件封装期间,为用户预留的 内容的占位符。
2.插槽的基础用法
在封装组件时,可以通过 <slot> 元素 定义插槽,从而 为用户预留内容占位符:
<template>
<p>这是 MyCom1 组件的第 1 个 p 标签</p>
<!-- 通过 slot 标签,为用户预留内容占位符(插槽) -->
<slot></slot>
<p>这是 MyCom1 组件的最后一个 p 标签</p>
</template>
<my-com-1>
<!-- 在使用 MyCom1 组件时,为插槽指定具体的内容 -->
<p>---用户自定义的内容---</p>
</my-com-1>
2.1 没有预留插槽的内容会被丢弃
如果在封装组件时 没有预留任何的 <slot> 插槽,则用户提供的任何 自定义内容都会被丢弃:
<template>
<p>这是 MyCom1 组件的第 1 个 p 标签</p>
<!-- 封装组件时,没有预留任何插槽 -->
<p>这是 MyCom1 组件的第 2 个 p 标签</p>
</template>
<my-com-1>
<!-- 自定义内容会被丢弃 -->
<p>---用户自定义内容---</p>
</my-com-1>
2.2 后备内容
封装组件时,可以为预留的 <slot> 插槽提供 后备内容(默认内容),如果组件没有为插槽提供任何内容,则后备内容会生效:
<template>
<p>这是 MyCom1 组件的第 1 个 p 标签</p>
<slot>这是后备内容</slot>
<p>这是 MyCom1 组件的最后一个 p 标签</p>
</template>
3.具名插槽
如果在封装组件时 需要预留多个插槽节点,则需要为每个 <slot> 插槽指定 具体的 name 名称。这种 带具体名称的插槽 叫做 具名插槽:
<div class="container">
<header>
<!-- 我们希望把页头放这里 -->
<slot name="header"></slot>
</header>
<main>
<!-- 我们希望把主要内容放这里 -->
<slot></slot>
</main>
<footer>
<!-- 我们希望把页脚放这里 -->
<slot name="footer"></slot>
</footer>
</div>
注意: 没有指定 name 名称的插槽默认名称为 default。
3.1 为具名插槽提供内容
在向具名插槽提供内容时,可以在 <template> 元素上使用 v-slot 指令,并以 v-slot 的参数形式提供其名称:
<my-com-2>
<template v-slot:header>
<h1>滕王阁序</h1>
</template>
<template v-slot:default>
<p>豫章故郡,洪都新府。</p>
</template>
<template v-slot:footer>
<p>落款:王勃</p>
</template>
</my-com-2>
3.2 具名插槽的简写形式
把参数前的内容(v-slot:) 替换为字符 #,例 v-slot:header= #header
4.作用域插槽
在封装组件的过程中,可以为预留的 <slot> 插槽绑定 props 数据,这种 带有 props 数据的 <slot> 叫做 “作用域插槽”:
<tbody>
<!-- 下面的 slot 是一个作用域插槽 -->
<slot v-for="item in list" :user="item"></slot>
</tbody>
4.1 使用作用域插槽
可以使用 v-slot: 的形式,接收作用域插槽对外提供的数据:
<my-com-3>
<!-- 1.接收作用域插槽对外提供的数据 -->
<template v-slot:default="scope">
<tr>
<!-- 2.使用作用域插槽的数据 -->
<td>{{scope}}</td>
</tr>
</template>
</my-com-3>
4.2 解构插槽 Prop
作用域插槽对外提供的数据对象,可以使用 解构赋值 简化数据的接收过程:
<my-com-3>
<!-- v-slot: 可以简写成 # -->
<!-- 作用域插槽对外提供的数据对象,可以通过 解构赋值 简化接收的过程 -->
<template #default="{user}">
<tr>
<td>{{user.id}}</td>
<td>{{user.name}}</td>
<td>{{user.state}}</td>
</tr>
</template>
</my-com-3>
十、自定义指令
1.自定义指令的分类
- 私有 自定义指令
- 全局 自定义指令
2.私有自定义指令
在每个 vue 组件中,可以在 directives 节点下声明 私有自定义指令 :
directives: {
color: {
//为绑定到的 HTML 元素设置红色的文字
// 当指令第一次被绑定到元素上的时候,回立即触发 bind 函数
bind(el) {
//形参中的 el 是绑定了此指令的、原生的 DOM 对象
el.style.color = 'red'
}
}
}
3.使用自定义指令
在使用自定义指令时,需要加上 v- 前缀:
<!-- 声明自定义时,指令的名字是 color-->
<!-- 使用自定义指令时,需要加上 v- 指令前缀 -->
<h1 v-color>App 组件</h1>
4.为自定义指令 动态绑定参数值
在 template 结构中 使用自定义指令 时,可以通过等号的方式,为当前指令 动态绑定参数值:
<script>
data() {
return {
color: 'red' // 定义 color 颜色值
}
}
</script>
<!-- 在使用指令时,动态为当前指令绑定参数值 color -->
<h1 v-color="color">App 组件</h1>
<!-- 若在双引号中包含单引号,则是传递一个值 -->
<p v-color="'red'">测试</p>
5.通过 binding 获取指令的参数值
在声明自定义指令时,可以通过形参中的 第二个参数,来接收指令的参数值:
directives: {
color: {
bind(el, binding) {
//通过 binding 对象的 .value 属性,获取动态的参数值
el.style.color = binding.value
}
}
}
6.update 函数
bind 函数 只调用 1 次:当指令第一次绑定到元素时调用,当 DOM 更新时 bind 函数不会被触发,update 函数会在 每次 DOM 更新时 被调用:
directives: {
color: {
//当指令第一次被绑定到元素时被调用
bind(el, binding) {
el.style.color = binding.value
},
// 每次 DOM 更新时被调用
update(el, binding) {
el.style.color = binding.value
}
}
}
7.函数简写
如果 bind 和 update 函数中的 逻辑完全相同,则 对象格式 的自定义指令可以简写成 函数格式:
directives: {
// 在 insert 和 update 时,会触发相同的业务逻辑
color(el, binding) {
el.style.color = binding.value
}
}
8.全局自定义指令
全局共享的自定义指令需要通过 Vue.directive() 进行声明:
// 参数1:字符串,表示全局自定义指令的名字
// 参数2:对象,用来接收指令的参数值
Vue.directive('color', function(el, binding) {
el.style.color = binding.value
})
复习:数组中的方法
一、some 循环
foreach 循环一旦开始就无法在中间停止,性能较低。
const arr = ['a', 'b', 'c', 'd']
arr.some((item, index) => {
console.log('ok')
if (item === 'c') {
console.log(index)
// 在找到对应的项之后,可以通过 return true 来停止 some 循环
return true
}
})
二、every 循环
const arr = [
{ id: 1, name: '西瓜', state: true },
{ id: 2, name: '榴莲', state: true },
{ id: 3, name: '草莓', state: true },
]
// 需求:判断数组中,水果是否被全选,可用于全选按钮
const result = arr.every(item => item.state)
console.log(result)
三、 reduce 循环基本用法
reduce 循环 即累加
// 语法: .reduce((累加的结果, 当前循环值) => {}, 初始值)
const arr = [
{ id: 1, name: '西瓜', state: true, price: 10, count: 1 },
{ id: 2, name: '榴莲', state: true, price: 80, count: 2 },
{ id: 3, name: '草莓', state: true, price: 20, count: 3 },
]
// 需求:把购物车数组中,已勾选的水果,总价累加起来
/*let amt = 0 // 总价
arr.filter(item => item.state).forEach(item => {
amt += item.price * item.count
})
console.log(amt) */
const result = arr.filter(item => item.state).reduce((amt, item) => {
return amt += item.price * item.count
}, 0) // 这里的 0 为 amt 的初始值
console.log(result)
六、路由
一、前端路由的概念与原理
1.什么是路由
路由 (router) 就是 对应关系。
2.SPA 与前端路由
SPA 指的是一个 web 网站只有唯一的一个 HTML 页面, 所有组件的展示与切换 都在这唯一的一个页面内完成。
此时,不同组件之间的切换 都需要 前端路由 来完成。
前端路由: Hash 地址与组件之间的对应关系。
3.前端路由的工作方式
- 用户 点击了 页面上的路由链接
- 导致了 URL 地址栏 中的 Hash 值 发生了变化
- 前端路由监听到了 Hash 地址的变化 (URL 中 # 后都属于 Hash 地址)
- 前端路由把当前 Hash 地址对应的组件 渲染到浏览器中
4.实现简易的前端路由
- 通过
<component>标签,结合 comName 动态渲染组件:<!– 通过 is 属性,指定要展示的组件的名称 –> <component :is=”comName”></component> <script> export default { name: ‘App’, data() { return { //要展示的组件的名称 comName: ‘Home’ } } } </script> - 在 App.vue 组件中,为
<a>链接 添加对应的 hash 值:<a href=”#/home”>Home</a> <a href=”#/movie”>Movie</a> <a href=”#/about”>About</a> - 在 created 生命周期函数中,监听浏览器地址栏中 hash 地址的变化,动态切换要展示的组件的名称:<script> export default { created() { window.onhashchange = () => { switch (location.hash) { case ‘#/home’: // 点击了‘首页’的链接 this.comName = ‘Home’ break case ‘#/movie’: // 点击了‘电影’的链接 this.comName = ‘Movie’ break } } } } </script>
二、vue-router 的基本使用
1.什么是 vue-router
vue-router 是 vue.js 给出的 路由解决方案。能够管理 SPA 项目中组件的切换。
2.vue-router 安装和配置
2.1 在项目中安装 vue-router
在 vue2 项目中,安装 router 命令如下:
npm i vue-router@3.5.2 -S
2.2 创建路由模块
在 src 源代码目录下,新建 router/index.js 路由模块,并初始化如下代码:
// 导入 Vue 和 VueRouter 的包
import Vue from 'vue'
import VueRouter from 'vue-router'
// 调用 Vue.use() 函数, 把 VueRouter 安装为 Vue 的插件
Vue.use(VueRouter)
// 创建路由的实例对象
const router = new VueRouter()
// 向外共享路由的实例对象
export default router
2.3 导入并挂载路由模块
在 src/main.js 入口文件中,导入并挂载路由模块:
import Vue from 'vue'
import App from './App.vue'
// 导入路由模块
import router from './router'
new Vue({
render: h => h(App),
// 挂载路由模块
router: router
}).$mount('#app')
2.4 声明 路由链接 和 占位符
在 src/App.vue 组件中,使用 vue-router 提供的 <router-link> 和 <router-view> 声明路由链接和占位符:
<template>
<div class="app-container">
<h1>App 组件</h1>
<!-- 定义路由链接 -->
<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>
3.声明路由的 匹配规则
在 src/router/index.js 路由模块中,通过 routes 数组 声明路由的匹配规则:
// 导入需要使用路由切换展示的组件
import Home from '@/components/Home.vue'
import Movie from '@/components/Movie.vue'
import About from '@/components/About.vue'
// 创建路由的实例对象
const router = new VueRouter({
routes:[
// 在 routers 数组中,声明路由的匹配规则
// path 表示要匹配的 hash 地址,component 表示要展示的路由组件
{ path: '/home', component: Home },
{ path: '/movie', component: Movie },
{ path: '/about', component: About }
]
})
三、vue-router 的常见用法
1.路由重定向
路由重定向 指的是:用户在访问 地址 A 的时候, 强制用户跳转 到地址 C ,从而展示特定的组件页面。
通过路由规则的 redirect 属性,指定一个新的路由地址,可以方便的设置路由的重定向:
const router = new VueRouter({
// 在 routes 数组中,声明路由的匹配规则
routes: [
// 当用户访问 / 的时候,通过 redirect 属性跳转到 /home 对应的路由规则
{ path: '/', redirect: '/home'},
{ path: '/home', component: Home },
{ path: '/movie', component: Movie },
{ path: '/about', component: About }
]
})
2.嵌套路由
通过路由实现 组件的嵌套展示,叫做嵌套路由
3.子路由
3.1 声明 子路由链接 和 子路由占位符
在 About.vue 组件中,声明 tab1 和 tab2 的 子路由链接 以及 子路由占位符:
<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>
3.2 通过 children 属性声明 子路由规则
在 src/router/index.js 路由模块中,导入需要的组件,并使用 children 属性 声明子路由规则:
import Tab1 from '@/components/tabs/Tab1.vue'
import Tab2 from '@/components/tabs/Tab2.vue'
const router = new VueRouter({
routes: [
{ // about 页面的路由规则 (父级路由规则)
path: '/about',
component: About,
children: [ // 通过 children 属性,嵌套声明子级路由规则
// 官方建议path 部分不加斜线
{ path: 'tab1', component: Tab1 }, // 访问 /about/tab1 时,展示 Tab1 组件
{ path: 'tab2', component: Tab2 } // 访问 /about/tab2 时,展示 Tab2 组件
]
}
]
})
如果 children 数组中,某个路由规则的 path 值为空字符串,则这条路由规则叫做 默认子路由。可在一定程度上替代重定向。
4.动态路由匹配
4.1动态路由的概念
动态路由指的是:把 Hash 地址中 可变的部分 定义为 参数项,从而 提高路由规则的复用性。
在 vue-router 中使用 : 来定义路由的参数项:
{ path: '/movie/:id', component: Movie }
// 将以下 3 个路由规则合并为一个,提高了路由规则的复用性
{ path: '/movie/1', component: Movie }
{ path: '/movie/2', component: Movie }
{ path: '/movie/3', component: Movie }
4.2 $route.params 参数对象
在 动态路由 渲染出来的组件中,可以使用 this.$route.params 对象访问到 动态匹配的参数值:
<template>
<div class="movie-container">
<!-- this.$route 是路由的参数对象 -->
<h3>Movie 组件 -- {{ this.$route.params.id }}</h3>
</div>
</template>
<script>
export default {
name: 'Movie'
}
</script>
4.3 使用 props 接收路由参数
为了简化路由参数的获取形式, vue-router 允许在 路由规则 中 开启 props 传参 :
// 在定义路由规则时,声明 props: true 选项
// 即可在 Movie 组件中,以 props 的形式接收到路由规则匹配到的参数项
{ path: '/movie/:id', component: Movie, props: true }
<template>
<!-- 直接使用 props 中接收的路由参数 -->
<h3>MyMovie 组件 --- {{id}}</h3>
</template>
<script>
export default {
props: ['id'] // 使用 props 接收路由规则中匹配到的参数项
}
</script>
5. 声明式导航 & 编程式导航
在浏览器中, 点击链接 实现导航的方式,叫做 声明式导航:
- 普通网页中点击
<a>链接、vue 项目中点击<router-link>都属于声明式导航
在浏览器中,调用 API 方法 实现导航的方式看,叫做 编程式导航:
- 普通网页中调用 location.href 跳转到新页面的方式,属于编程式导航
5.1 vue-router 中的编程式导航 API
vue-router 提供了许多编程式导航的 API,其中最常用的导航 API 分别是:
this.$router.push('hash 地址')跳转到指定 hash 地址,并 增加 一条历史记录this.$router.replace('hash 地址')跳转到指定 hash 地址,并 替换掉当前的 历史记录this.$router.go(数值n)实现导航历史前进、后退
5.2 $router.push
调用 this.$router.push() 方法,可以跳转到指定的 hash 地址,从而展示对应的组件页面:
<template>
<div class="home-container">
<h3>Home 组件</h3>
<button @click='gotoMovie'>跳转到 Movie 页面</button>
</div>
</template>
<script>
export default {
methods: {
getoMovie() m{ this.$router.push('/movie/1') }
}
}
</script>
5.3 $router.replace
调用 this.$router.replace() 方法,可以跳转到指定的 hash 地址,从而展示对应的组件页面。
push 和 replace 的区别:
- push 会 增加一条历史记录
- replace 不会增加历史记录,而是 替换掉当前的历史记录
5.4 $router.go
调用 this.$router.go() 方法,可以在浏览历史中前进和后退:
<template>
<h3>MyMovie 组件 --- {{id}}</h3>
<button @click="goBack">后退</button>
</template>
<script>
export default {
props: ['id'],
methods: {
goBack() { this.$router.go(-1) } // 后退到之前的组件页面
},
}
</script>
5.5 $router.go 的简化用法
在实际开发中,一般只会前进和后退一层页面,因此提供了两个便捷方法:
$router.back()在历史记录中,后退 到上一个页面$router.forward()在历史记录中,前进 到下一个页面
6. 导航守卫
导航守卫 可以 控制路由的访问权限:
6.1 全局前置守卫
每次发生路由的 导航跳转 时,都会触发 全局前置守卫。因此,在全局前置守卫中,可以对每个路由进行 访问权限 的控制:
// 创建路由实例对象
const router = new VueRouter({ ... })
// 调用路由实例对象的 beforeEach 方法,即可声明 “全局前置守卫”
// 每次发生路由导航的跳转的时候,都会自动触发 fn 这个 “回调函数”
router.beforeEach(fn)
6.2 守卫方法的 3 个形参
全局前置守卫 的回调函数中接收 3 个形参,格式为:
// 创建路由实例对象
const router = new VueRouter({ ... })
// 全局前置守卫
router.beforeEach((to, from, next) => {
// to 是将要访问的路由的信息对象
// from 是将要离开的路由的信息对象
// next 是一个函数,调用 next() 表示放行,允许这次路由导航
})
6.3 next 函数的 3 种调用方式
- 当前用户 拥有 后台主页的访问权限,直接放行: next()
- 当前用户 没有 后台主页的访问权限,强制其跳转到登录页面: next(‘/login’)
- 当前用户 没有 后台主页的访问权限,不允许跳转到后台主页: next(false)
6.4 控制后台主页的访问权限
router.beforeEach((to, from, next) => {
if(to.path === '/main') {
const token = localStorage.getItem('token')
if (token) {
next() // 访问的是后台主页,且有 token 的值
} else {
next('/login') // 访问的是后台主页,但是没有 token 的值
}
} else {
next() // 访问的不是后台主页,直接放行
}
})