Vue介绍
vue概念
vue:一套用于构建用户界面的前端框架
- 构建用户界面
- 用vue往html页面添加数据
- 框架
- 框架是一套现成的解决方案,程序员只能遵守框架的规范,去编写自己的业务功能!
- vue 的指令、组件(是对 UI 结构的复用)、路由、Vuex、vue 组件库
vue两个特性
-
数据驱动视图:
- 数据的变化会驱动视图自动更新
-
双向绑定数据:
在网页中,form表单负责采集数据,Ajax负责提交数据
- js数据的变化,会被自动渲染到页面
- 页面上表单采集的数据发生变化时,会被vue自动获取到,并更新到js数据中
MVVM
MVVM
是 vue 实现数据驱动视图和双向数据绑定的核心原理。MVVM 指的是 Model、View 和 ViewModel, 它把每个 HTML 页面都拆分成了这三个部分,如图所示:
工作原理:
基本使用
//被 Vue 控制的 DOM 区域
<div id="app">{{ }}</div>
//导入 Vue 的库文件,可以使用 Vue 这个构造函数
<script src="./lib/vue-2.6.12.js"></script>
<script>
//创建 Vue 实例对象
const vm = new Vue({
//el 属性固定写法,表示当前 vm 实例要控制的区域,接收值为选择器
el: '#app',
//data 对象就是需要渲染的数据
data: {
username: 'zhangsan'
}
})
</script>
Vue指令
1.内容渲染指令
-
v-text
缺点:会覆盖元素内部原有的内容//从 data 对象中获取 username 渲染到内容中 <p v-text="username"></p>
-
{{ }}
插值表达式:在实际开发中用的最多,只是内容的占位符,不会覆盖原有的内容!<p>姓名:{{ username }}</p>
-
v-html
作用:可以把带有标签的字符串,渲染成真正的 HTML 内容!
2.属性绑定指令
注意:插值表达式只能用在元素的内容节点中,不能用在属性节点
-
在 Vue 中,可以使用
v-bind:
指令,为元素的属性动态绑定值<input type="text" v-bind:placeholder="tips">//tips为 data 对象中属性
v-bind:
可以简写成:
<input type="text" :placeholder="tips">
-
在使用
v-bind:
属性绑定期间,如果绑定内容需要动态拼接,则字符串需要用单引号包裹<div :title="'box' + index">div标签</div>
3.事件绑定指令
-
v-on:
简写@
-
语法格式:
<button @click="add(2)">点击加2</button>// add 为function, 需要在methods对象中定义 //methods 的作用:定义事件处理函数 methods: { add(n) { //this === vm 结果为 true //如果要修改 data 中的数据,可以通过 this 来访问 this.count += n } }
-
$event
: 如果默认的事件对象e被覆盖了,则可以手动传递一个$event<button @click="add(3, $event)"></button> methods: { add(n, e) { //三元运算符 count 为偶数则将 backgroundCOlor 改成 red e.target.style.backgroundColor = count % 2 === 0 ? 'red' : '' this.count += 1 } }
-
事件修饰符:
-
.prevent
: 阻止默认行为(如:阻止 a 链接跳转,阻止表单提交等)<a href="https://www.baidu.com" @click.prevent="onLinkClick">百度</a>
-
.stop
: 阻止冒泡<button @click.stop="xxx">按钮</button>
-
-
按键修饰符:在监听键盘事件时,我们经常需要判断详细的按键。
//只有在 key 是 Esc 时调用 vm.clearInput() //只有在 key 是 Enter 时调用 vm.commitAjax() <input type="text" @keyup.esc="clearInput" @keyup.enter="commitAjax">
4.双向绑定指令
Vue 提供了 v-model 双向数据绑定指令,用来辅助开发者在不操作 DOM 的前提下,快速获取表单的数据
//通过修改文本内容从而修改数据源内容
//v-model 绑定覆盖了默认属性value
<input type="text" v-model="username">
v-model
修饰符
修饰符 | 作用 |
---|---|
.number | 自动将用户的输入值转为数值类型 |
.trim | 自动过滤用户输入的首尾空白字符 |
.lazy | 在“change”时而非“input”时更新 |
.number
<input v-model.number="age" />
.trim
<input v-model.trim="msg" />
.lazy
<input v-model.lazy="msg" />
5.条件渲染指令
条件渲染指令用来辅助开发者按需控制 DOM 的显示与隐藏。条件渲染指令有如下两个
v-show
的原理是:动态为元素添加或移除display: none
样式,来实现元素的显示和隐藏
- 如果要频繁的切换元素的显示状态,用 v-show 性能会更好
v-if
的原理是:每次动态创建或移除 DOM 元素,实现元素的显示和隐藏- 如果刚进入页面的时候,某些元素默认不需要被展示,而且后期这个元素很可能也不需要被展示出来,此时 v-if 性能更好
在实际开发中,绝大多数情况,不用考虑性能问题,直接使用 v-if 就好了!!!
//直接给定 true(显示) 或 false(不显示)
<p v-if="true">被 v-if 控制的元素</p>
<p v-if="type === 'A'">良好</p>
6.列表渲染指令
vue 提供了v-for
列表渲染指令,用来辅助开发者基于一个数组来循环渲染一个列表结构。
v-for
指令需要使用 (item, index) in list形式的特殊语法,其中:
- item: 被循环数组的每一项
- index:当前项索引
- list:待循环的数组
建议使用 v-for 指令时一定要指定 key 的值(既提升性能、又防止列表状态紊乱)
key 的注意事项:
- key 的值只能是字符串或数字类型
- key 的值必须具有唯一性(即:key 的值不能重复)
- 建议把数据项 id 属性的值作为 key 的值(因为 id 属性的值具有唯一性)
- 使用 index 的值当作 key 的值没有任何意义(因为 index 的值不具有唯一性,可能发生改变)
data: {
list: [ //待循环数组
{id: 1, name: 'zs'},
{id: 2, name: 'ls'}
]
}
<ul>
<li v-for="(item, index) in list" :key="item.id">索引:{{ index }},姓名:{{ item.name}}</li>
</ul>
v-for 指令中的 item 和 index 都是形参
Vue过滤器
过滤器(Filters)是 vue 为开发者提供的功能,常用于文本的格式化。
适用场景:
插值表达式
v-bind属性绑定
//通过“ | ”调用 capitalize 过滤器,对 message 值格式化,结果为函数返回值
<p>{{ message | capitalize }}</p>
私有过滤器
在 filters 节点下定义的过滤器,称为“私有过滤器”,只能在当前 vm 实例所控制的 el 区域内使用。
const vm = new Vue({
el: '#app',
data: {
username: 'zs'
}
//在 filters 节点下定义过滤器
filters: {
//把首字母转换为大写的过滤器
capitalize(value) {
return value.charAt(0).toUpperCase() + value.slice(1)
}
}
})
全局过滤器
可以在多个 Vue 实例之间共享过滤器
Vue.filter()
方法接收两个参数:
- 参数1:全局过滤器的"名字"
- 参数2:全局过滤器的"处理函数"
Vue.filter('capitalize', (str) => {
return value.charAt(0).toUpperCase() + value.slice(1)
})
可以调用多个过滤器:
//依次调用过滤器 filterA 和 过滤器 filterB
{{ message | filterA | filterB }
过滤器传参
过滤器处理函数参数中:
- 第一个永远是" | "前面待处理的值
- 从第二个参数起才是处理函数中参数
//将arg1 和 arg2 作为参数传到 filterA 中
<p>{{ message | filterA(arg1, arg2) }}</p>
Vue.filter('filterA', (value, arg1, arg2) => {
//......
})
过滤器注意点
- 本质是一个函数,要定义在 filters 节点下
- 过滤器函数中,一定要有 return 返回值
- 如果全局过滤器和私有过滤器名字一致,此时按照“就近原则”,调用的是”私有过滤器“
watch侦听器
watch 侦听器允许开发者监视数据的变化,从而针对数据的变化做特定的操作。
本质上是一个函数,都应该被定义到watch节点下
侦听器格式
- 方法格式的侦听器
- 缺点1:无法在刚进入页面时刷新
- 缺点2:如果侦听的是一个对象,当对象中属性发生变化时,不会触发侦听器
- 对象格式的侦听器
- 优点1:可以通过 immediate 选项,让侦听器自动触发!!!
- 优点2:可以通过 deep 选项,让侦听器深度监听对象中每个属性的变化!!!
方法格式的侦听器
<script>
const vm = new Vue({
el: '#app',
data: {
username: 'zs'
}
watch: {
//需要把监视的数据名,作为方法名
//新值在前,旧值在后
username(newValue, oldValue) {
//.....
}
}
})
</script>
对象格式的侦听器
<script>
const vm = new Vue({
el: '#app',
data: {
info: {
username: 'admin',
address: {
city: '北京'
}
}
},
watch: {
info: {
//侦听器的处理函数
handler(newValue, oldValue){
console.log(newValue, oldValue)
},
// immediate 选项的默认值是 false
// immediate 的作用是:控制侦听器是否自动触发一次!
immediate: true,
//开启深度监听,只要对象中任何一个属性变化了,都会触发“对象的侦听器”
deep: true
}
}
})
</script>
注意:如果要侦听对象的子属性,则必须用单引号包裹(方法格式)
'info.username'(newVal, oldVal){
console.log(newVal)
}
计算属性
实时监听
data 中数据的变化,并 return 一个计算后的新值
, 供组件渲染 DOM 时使用。
这个动态计算出来的属性值可以被模板结构(插值,v-bind)
或 methods
方法使用。
特点:
- 所有的计算属性,都要定义到
computed
节点之下 - 计算属性在定义的时候,要定义成**“方法格式”,但是计算属性的本质是一个属性**
用法
<div id="app">
<h2>总价格:{{ totalPrice }}</h2>
</div>
const vm = new Vue({
el: '#app',
data: {
books:[
{id: 1001, name: 'Unix编程艺术',price: 119},
{id: 1002, name: '代码大全',price: 105},
{id: 1003, name: '深入理解计算机原理',price: 99},
{id: 1004, name: '现代操作系统',price: 109}
]
},
computed: {
totalPrice () {
let totalPrice = 0;
for (let i in this.books) {
totalPrice += this.books[i].price;
}
// 也可以使用 for of
for (let book of this.books) {
totalPrice += book.price;
}
return totalPrice;
}
}
methods: {
test: {
//将计算属性 totalprice 看成属性来访问
console.log(this.totalprice)
}
}
})
计算属性 vs 方法:
methods
和 computed
看起来都可以实现我们的功能,那么为什么还要多一个计算属性
methods
: 每次使用都会调用方法
computed
: 计算属性会缓存计算的结果, 不变的情况下只调用一次, 除非原属性发生改变,才会重新调用.
计算属性 vs 侦听器:
侧重的应用场景不同侧重的应用场景不同:
计算属性侧重于监听多个值的变化,最终计算并返回一个新值。
侦听器侧重于监听单个数据的变化,最终执行特定的业务处理,不需要有任何返回值。
Vue-cli
vue-cli 是 Vue.js 开发标准工具,它简化了程序员基于 webpack 创建工程化的 Vue 项目的过程
-
在终端下运行如下的命令,创建指定名称的项目:
vue create 项目的名称
-
vue 项目中 src 目录的构成:
assets 文件夹:存放项目中用到的静态资源文件,例如:css 样式表、图片资源 components 文件夹:程序员封装的、可复用的组件,都要放到 components 目录下 main.js 是项目的入口文件。整个项目的运行,要先执行 main.js App.vue 是项目的根组件。
vue 项目工作流程:
在工程化项目中,vue 通过 main.js
把App.vue
渲染到 index.html
的指定区域(用 App.vue 内容替换 index.hmtl 指定区域)
App.vue
用来编写待渲染的模板模块index.html
中需要预留一个 el 区域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({
//把 render 函数指定组件(App),渲染到 HTML 页面中
render: h => h(App)
}).$mount('#app') //Vue 实例的$mount()方法 指定了 index.html 中预留区域 作用与 el 属性一样
执行 npm run serve 时,会将 main.js 转换成 bundle.js 自动导入到 index.html 中
vue组件
组件化开发指的是:根据封装的思想,把页面上可重用的 UI 结构封装为组件,从而方便项目的开发和维护
vue 中规定:组件的后缀是 .vue
组件构成
每个 .vue 组件都由三个部分构成:
template
:组件的模板结构script
:组件的 JavaScript 行为style
:组件的样式
注意:每个组件中必须包含 template 模板结构,而 script 行为和 style 样式是可选的组成部分
template:
-
template 是 vue 提供的容器标签,只起到包裹性质的作用,它不会被渲染为真正的 DOM 元素
-
template 中只能包含唯一的根节点
script:
- vue 组件的 data 必须是函数
<script>
//组件相关的 data 数据,methods 方法等,都需要定义到 export default 所导出的对象中
export default {
//data 数据源
data() {
//return 出去的{ }中,可以定义数据
return {
username: 'zs'
}
},
methods: {}, //事件相关函数
watch: {}, //侦听器
computed: {}, //计算属性
filters: {} //过滤器
//this 表示当前 vue 组件实例对象
}
</script>
style:
- 可以在
style
结点中编写样式美化当前组件的 UI 结构 - 在
style
标签上添加 lang=“less” 属性,即可使用 less 语法编写组件的样式
<style lang="less">
h1 {
font-weight: normal;
span{
color: red;
}
}
</style>
组件的使用
组件在被封装好之后,彼此之间是相互独立的,不存在父子关系
在使用组件的时候,根据彼此的嵌套关系,形成了父子关系、兄弟关系
注册私有组件
通过 components 注册的是私有组件,被注册的组件只能在当前组件使用
-
使用
import
导入 需要的组件import left from '@/components/Left.vue'
-
在 script 标签中使用
components
节点注册组件<script> export default { components: { Left } } </script>
-
以
标签
形式使用刚才注册的组件<template> <div> <Left></Left> </div> </template>
注册全局组件
在 vue 项目的
main.js
入口文件中,通过Vue.component()
方法,可以注册全局组件
import Vue from 'vue'
import App from './App.vue'
// 导入需要被全局注册的那个组件
import Count from '@/components/Count.vue'
// 参数一: 组件的 '注册名称',将来以标签形式使用时要求和这个名称一样。
// 参数二: 需要被全局注册的那个组件。
Vue.component('MyCount', Count)
// 消息提示的环境配置,设置为开发环境或者生产环境
Vue.config.productionTip = false
new Vue({
// render 函数中,渲染的是哪个 .vue 组件,那么这个组件就叫做 “根组件”
render: h => h(App)
}).$mount('#app')
组件的props
props 是组件的自定义属性,允许使用者通过自定义属性,为当前组件指定初始值
优点:不同组件使用同一个注册的组件时,可以传不同的初始值
注意:
props
数组里面是字符串
组件封装者:
<script>
export default {
props: ['initA', 'initB'],
data() {
return {
count: this.initA
}
}
}
</script>
组件使用者:
<template>
//初始化 initA 得到的 initA 值是字符’9‘
<MyCount initA="9"></MyCount>
</template>
v-bind
动态绑定自定义属性
动态绑定自定义属性后,双引号里面是JS语句,因此初始化的结果不带双引号
<template>
//初始化 initA 得到的 initA 值是数值 9
<MyCount :initA="9"></MyCount>
</template>
props只读
vue 规定:组件中封装的自定义属性是只读的,程序员不能直接修改 props 的值。否则会直接报错
如果要修改 props
的值,可以把 props 的值转存到 data 中
default默认值
组件使用者有可能没有对自定义属性初始化,那么会显示 undefined ,需要用 default 来定义属性的默认值
props
指向对象
export default{
props: {
init: {
//设定默认值
default: 0
}
}
}
type值类型
在自定义属性时,可以通过
type
来定义属性的值类型
export default {
props: {
init: {
//用 default 属性定义属性默认值
default: 1,
//用 type 属性定义属性的值类型
//如果传递的值不符合该类型,终端报错
type: Number
}
}
}
required必填项
在声明自定义属性时,可以通过
required
属性,将自定义属性设置成必填项
export default {
props: {
init: {
required: true
}
}
}
组件间样式冲突
默认情况下,.vue 组件中的样式会全局生效,会造成多个组件间样式冲突
原因:
- 单页面应用程序,所有组件的 DOM 结构,都基于唯一的 index.html 页面进行呈现
- 每个组件的样式,都会影响整个 index.html 页面的 DOM 元素
解决办法:Vue 提供在 style 节点中添加 scoped
属性
让当前组件样式对其他组件不生效
原理:添加 scoped
后,Vue 底层会为组件中每个标签添加自定义属性(data-v-xxx),同一个组件的自定义属性一样。在编写组件样式时,通过属性选择器来控制样式的作用域。
示例:
<style scoped>
//当查找当前组件标签添加样式,通过 h3[data-v-xxx],既要满足是 h3 ,又要满足是自定义属性 [data-v-xxx]
h3 {
color: red;
}
</style>
通过浏览器查看:
/deep/ 样式穿透
样式穿透目的:
通过在当前组件
style
节点下添加样式,让子组件生效
如果给当前组件的 style
节点添加了 scoped
属性,当前组件的样式不会对子组件生效,所以需要添加 /deep/
进行样式穿透
示例:
<style lang="less" scoped>
/*不加 /deep/ 时,生成的选择器格式为 .title[data-v-052242de]*/
//既要求是 title 类,又要求是自定义属性[data-v-052242de]
.title{
color: blue;
}
/*加/deep/ 时,生成的选择器格式为 [data-v-052242de] .title*/
//自定义属性父元素 [data-v-052242de] 下的子元素 title
/deep/ .title {
color: pink;
}
</style>
生命周期
created() {
//经常在里面,调用 methods 的方法,用 Ajax 向服务器请求数据
//并且把请求的数据,转存到 data 中,供 template 模板渲染使用
//由于模板还未创建好,所以不能创建 DOM
}
组件的数据共享
父传子
父组件通过
v-bind
绑定属性向子组件共享数据,子组件通过使用props
接收数据
//父组件
<template>
//使用子组件
<div>
<Son :msg="message" :user="userinfo"></Son>
</div>
</template>
<script>
//导入子组件
import Son from '@/components/Son.vue'
export default {
data() {
return {
message: 'hello vue.js',
userinfo: {
name: 'zs',
age: 18
}
}
},
//注册子组件
components: {
Son
}
}
</script>
//子组件
<template>
<div>
<p>父组件传过来的msg值: {{ msg }}</p>
<p>父组件传过来的user值: {{ user }}</p>
</div>
</template>
<script>
export default {
props: ['msg', 'user']
}
</script>
父组件的
userinfo
跟子组件的user
指向同一个对象
子传父
自定义事件
//子组件
<template>
<div>
<p>
count 值:{{ count }}
</p>
<button @click="add">
按钮
</button>
</div>
</template>
<script>
export default{
methods: {
add() {
this.count += 1
//修改数据时,通过 $emit() 触发自定义事件
this.$emit('numchange', this.count)
}
},
data() {
return {
count: 0
}
}
}
</script>
//父组件
<template>
<div>
<Son @numchange="getNew"></Son>
<p>
子组件传的值:{{ countFromSon }}
</p>
</div>
</template>
<script>
import Son from '@/components/Son.vue'
export default {
data() {
return {
countFromSon: ''
}
},
methods: {
getNew(val) {
this.countFromSon = val
}
},
components: {
Son
}
}
</script>
兄弟组件共享
在 vue2 中,兄弟组件之间数据共享的方案是
EventBus
。
注意:A 和 C 共享同一个 bus
EventBus 的使用步骤:
- 创建
eventBus.js
模块,并向外共享一个 Vue 的实例对象
- 在数据发送方,调用
bus.$emit('事件名称', 要发送的数据)
方法触发自定义事件 。 - 在数据接收方,调用
bus.$on('事件名称', 事件处理函数)
方法注册一个自定义事件。
ref引用操作 DOM
ref 用来辅助开发者在不依赖 jQuery 和 DOM api 的情况下 ,
获取 DOM 元素或组件的引用
每个 vue 的组件实例上,都包含一个 $refs 对象
,里面存储着对应的 DOM 元素或组件的引用。默认情况下,组件的 $refs 指向一个空对象。
<template>
<div>
<h1 ref="color1">App</h1>
<button @click="changeColor">this</button>
</div>
</template>
<script>
export default {
methods: {
changeColor() {
this.$refs.color1.lstyle.color = 'red'
}
}
}
</script>
注意
ref
也可以加在组件的标签上
this.$nextTick(cb)方法
this.$nextTick(cb)
将 cb 回调函数推迟到下一个 DOM 更新周期之后执行
(即组件 DOM 更新完成后执行)
this.$nextTick(() => {
console.log('hello')
})
动态组件
动态组件指的是
动态切换组件的显示与隐藏
基本使用:
Vue 提供了一个内置的 <component>
组件,专门实现动态组件的渲染
-
component
标签时 Vue 内置的,作用:组件占位符 -
通过
:is
属性,动态指定要渲染的组件- is 属性值,表示要渲染的组件名字
-
使用
keep-alive
标签保持组件状态(避免切换时需要重新创建)- keep-alive 会把内部的组件进行缓存,而不是销毁
include
属性指定哪些组件需要被缓存(多个组件用英文逗号
隔开,不能加空格)exclude
属性指定哪些组件不被缓存
注意: include 跟 exclude 属性不能同时出现
示例:
<template>
<div class="app-container">
<div class="box">
<keep-alive include="Left,Right">
<component :is="isName"></component>
</keep-alive>
<button @click="tarnsLeft">切换成Left</button>
<button @click="tarnsRight">切换成Right</button>
</div>
</div>
</template>
<script>
import Left from '@/components/Left.vue'
import Right from '@/components/Right.vue'
export default {
components: {
Left,
Right
},
data() {
return {
// 表示要展示组件名称
isName: 'Left'
}
},
methods: {
tarnsLeft() {
this.isName = 'Left'
},
tarnsRight() {
this.isName = 'Right'
}
}
}
</script>
插槽slot
概念
封装组件中将不确定的内容称为插槽
可以把插槽认为是组件封装期间,为用户预留的内容的占位符
用法
在封装组件期间,通过 <slot>
标签定义插槽,从而为用户预留内容占位符
<!-- Count 组件 -->
<template>
<!-- 通过 slot 标签,为用户预留占位符 -->
<slot></slot>
</template>
<!-- 在App.vue 中使用Count组件标签 -->
<Count>
<p>组件标签里面内容会插入到 slot 标签中</p>
</Count>
后备内容
在
slot
标签内添加的内容会被作为后备内容。
// 子组件 Count
<template>
<slot>这是后备内容</slot>
</template>
// 使用插槽
<Count>
// 如果用户没有提供内容,上面 slot 标签内定义的内容会生效,此时页面会有 "这是后备内容"
// 如过提供了,下面的 img 将会被渲染到页面上
<img src="../assets/log.png" alt="">
</Count>
具名插槽
每个 slot
插槽,都要有一个 name
属性(指定插槽名称),如果省略了 slot 的 name 属性,会有一个默认名称 “default”
<slot name="default"></slot>
v-slot指令
为指定插槽提供内容
-
v-slot:
后面跟上插槽的名称 -
v-slot:
可以简写#
。例如 v-slot:header 可以被重写为 #header。 -
v-slot
属性只能放在组件标签
和<template>
元素上 (否则会报错)。
<Count>
<template #default>
<p>组件标签里面内容会插入到指定名称 slot 标签中</p>
</template>
</Count>
作用域插槽
在封装组件的过程中,可以为预留的 插槽绑定 props 数据的 <slot>
,这种带有 props 数据的 叫做“作用 域插槽”
//message 是自定义属性,通过 v-bind 进行绑定
<slot name="content" :message="str" :user="user"></slot>
//.....
export default {
data() {
return {
str: 'hello vue.js',
user: {
name: 'zs',
age: 18
}
}
}
}
可以使用 v-slot:
的形式,接收作用域插槽对外提供的数据。
<Count>
// content 表示指定的插槽名称,scope 表示接收作用域插槽提供的数据
<template #content="scope">
//注意 scope 是一个对象,可以通过对象解构获取其中内容
<p>{{ scope }}</p>
</template>
</Count>
进行解构
<template #content="{message, user}">
<p>传过来的str: {{ msg }}</p>
<p>传过来的user: {{ user }}</p>
</template>
自定义指令
-
使用自定义组件时需要加
v-指令前缀
即:声明成 color ,使用时要写成 v-color
私有自定义指令
在每个 Vue 组件中,可以在
directives
节点下声明私有自定义指令
directives: {
//定义名为 color 的指令,指向一个配置对象
color: {
//当指令第一次被绑定到元素上时,会触发 bind 函数
bind(el) {
// el 固定写法,是指绑定了此指令的原生 DOM 对象
el.style.color = 'red'
}
}
}
注意:使用需要写成
v-xxx
为自定义指令绑定动态参数:
在 template
结构中 使用自定义指令时,可以通过等号 =
方式,为当前指令动态绑定参数值
<template>
<!--在使用指令时,动态为当前指令绑定参数值 color-->
<h1 v-color="color">App组件</h1>
</template>
data(){
return{
color:'red’
}
通过 binding
获取指令参数值:
通过
binding
对象的value
属性,动态获取参数值
//传的 color 是变量,需要在 data 数据中找
<h1 v-color="color">App组件</h1>
// 此时传入的是字符串,不会在 data 数据中查找
<p v-color="'blue'" ></p>
directives:{
color:{
bind(el,binding){
//通过binding对象的.value属性,获取动态的参数值
el.style.color=binding.value
}
注意:当 data 中的 color 值发生变化时,
bind 函数
不会再次触发,因此el.style.color
的值不会改变
update 函数:
bind 函数只调用一次
:当指令第一次绑定到元素时调用,当 DOM 更新时,bind 函数不会触发
update 函数
会在每次 DOM 更新
时调用
directives: {
color: {
bind(el, binding) {
el.style.color = binding.value
},
update(el, binding) {
el.style.color = binding.value
}
}
}
bind 函数 需要跟 update 函数一起使用
简写形式:
如果 bind
和 update
函数中的 逻辑完全相同
,则对象格式的自定义指令可以简写成函数格式:
directives:{
//在 bind 和 update 时,会触发相同的业务逻辑
color( el,binding ){
el.style.color = binding.value
}
// color 后面是 function 函数,表明函数里面在 bind 跟 update 一样
全局自定义指令
通过
Vue.directive()
进行声明
Vue.directive('color', function(el, binding) {
el.style.color = binding.value
})
//参数1:字符串,自定义指令名称
//参数2:对象(里面包含 bind 跟 update 函数),用来接收指令的参数值(上面写的函数时简写形式)
axios
在 Vue-cli 中使用 axios:
// 在组件内
<template>
<button @click="postInfo">发起 POST 请求</button>
</template>
<script>
// 1. 导入 axios
import axios from 'axios'
export default {
// 2. 在 methods 定义 axios请求方法
methods: {
// 如果调用某个方法的返回值是 Promise 实例,则前面可以添加 await!
// await 只能用在被 async “修饰”的方法中
// 调用 axios 后,用 async/await 进行简化
// { data: res} 是采用了对象解构
// { name: 'zs', age: 20} 是传的body
async postInfo() {
const { data: res } = await axios.post('http://http://www.liulongbin.top:3006/api/post', { name: 'zs', age: 20 })
console.log(res)
}
}
}
</script>
把 axios 挂载到 Vue原型上并配置请求根路径
避免重复导入axios 和重复写入完整请求地址。
在 main.js
中配置
import Vue from 'vue'
import App from './App.vue'
import axios from 'axios'
Vue.config.productionTip = false
// 全局配置 axios 的请求根路径 (官方提供配置项)
axios.defaults.baseURL = 'http://www.liulongbin.top:3006'
// 把 axios 挂载到 Vue.prototype 上,供每个 .vue 组件的实例直接使用
Vue.prototype.$http = axios
// 今后,在每个 .vue 组件中要发起请求,直接调用 this.$http.xxx
// 但是,把 axios 挂载到 Vue 原型上,有一个缺点:不利于 API 接口的复用!!!
new Vue({
render: h => h(App)
}).$mount('#app')
使用:
<template>
<button @click="btnGetBooks">
获取图书列表的数据
</button>
</template>
<script>
export default {
methods: {
// 点击按钮,获取图书列表的数据
async btnGetBooks() {
const { data: res } = await this.$http.get('/api/getbooks')
console.log(res)
}
}
}
</script>
缺点:
无法实现API接口的复用
: 在多个组件想使用同一个请求方法(API)的时候,只能在每个组件重新定义一次。
路由
前端路由:
Hash 地址
与组件
间的对应关系
hash 地址:#后面的地址
前端路由工作原理
- 点击
路由链接
- 导致了 URL 地址栏中
Hash 值发生变化
- 前端路由监听 Hash 地址变化
- 前端路由把
当前 Hash 地址对应组件
渲染到浏览器
Vue-router
vue-router
是 vue.js 官方给出的路由解决方案,它只能结合 vue 项目进行使用,能够轻松的管理 SPA 项目中组件的切换。
基本使用
- 安装 vue-router 包
npm i vue-router@3.5.2
此时在 src 目录下新增一个 router 文件夹
-
创建路由模块
在 router 文件夹中
新建 index.js 路由模块
// 1.导入 vue 和 vue-router 包 import Vue from 'vue' import VueRouter from 'vue-router' // 2.调用 Vue.use() 函数,把VueRouter 安装为 Vue 的插件 Vue.use(VueRouter) // 3.创建路由对象实例对象 const router = new VueRouter() //4.向外共享路由的实例对象 export default router
-
导入并挂载路由模块
在 main.js
文件中,导入并挂载路由:
import Vue from 'vue'
import App from '@/App.vue'
// 导入路由模块
import router from '@router/index.js'
// index.js 文件可以省略,因为会自动查找 router 文件夹下的 .js 文件
new Vue({
render: h => h(App),
// 挂载路由模块
router: router
}).$mount('#app')
- 声明路由链接和占位符
在 src/App.vue 组件中,使用 vue-router 提供的 <router-link>
和 <router-view>
声明路由链接和占位符
<tcmplate>
<div class="app-container">
<h1>App组件</h1>
<!--1.定义路由链接 不用加#-->
<router-link to="/home">首页</router-link>
<router-link to="/movie">电影</router-link>
<router-link to="/about">关于</router-link>
<!--2.定义路由的占位符-->
<router-view></router-view>
</div>
</template>
- 声明路由
匹配规则
在 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 数组中,声明路由匹配规则
routes: [
// path 表示要匹配的 hash 地址;component 表示要展示的路由
{ path: '/home', component: Home},
{ path: '/movie', component: Movie},
{ path: '/about', component: About}
]
})
路由重定向
当访问地址 A 的时候,
强制用户跳转
到地址C, 从而展示特定的组件页面。
- 通过路由规则的
redirect
属性,指定一个新的路由地址来实现路由的重定向。
const router = new VueRouter({
//routes 数组中,声明路由匹配规则
routes: [
// path 表示要匹配的 hash 地址;component 表示要展示的路由
{ path: '/', redirect: '/home'},
{ path: '/home', component: Home},
{ path: '/movie', component: Movie},
{ path: '/about', component: About}
]
})
嵌套路由
通过路由实现组件的嵌套
-
声明子路由链接和子路由占位符
我们在
About.vue
组件中套娃了 tab1 和 tab2 组件 ,如果我们想展示它,则需要声明子路由链接
以及子路由占位符
:<template> <div class="about-container"> <h3>About 组件</h3> <!--1.在关于页面中,声明两个子路由链接--> <router-link to="/about/tab1">tab1</router-link> <router-link to="/about/tab2">tab2</router-link> <hr/> <!--2.在关于页面中,声明子路由的占位符--> <router-view></router-view> </div> </template>
-
通过 children 属性声明子路由规则
在
src/router/index.js
路由模块中,导入需要的组件,并使用children
属性声明子路由规则:const router = new VueRouter({ routes: [ // path 表示要匹配的 hash 地址;component 表示要展示的路由 { path: '/', redirect: '/home' }, { path: '/home', component: Home }, { path: '/movie', component: Movie }, { path: '/about', component: About, children: [ { ptah: '/', redirect: 'tab1'} //重定向 { path: 'tab1', component: Tab1 }, // 访问/about/tab1时,展示Tab1组件 { path: 'tab2', component: Tab2} // 访问/about/tab2时,展示Tab2组件 ] } ] })
注意:使用
children
属性嵌套声明子级路由规则时,path 名称不需要加/
动态路由
动态路由指的是:把 Hash 地址中
可变的部分
定义为参数项
,从而提高路由规则的复用性
。
- 在 vue-router 中使用英文冒号(
:
)来定义路由动态参数项
// src/router/index.js 文件
// : 后面是动态参数名称
{ path: '/movie/:id', component: Movie}
$route.params
访问动态匹配参数值:
<template>
<div class="movie-container">
<!--this.$route是路由的“参数对象”-->
<h3> Movie 组件-- {{this.$route.params.id}} </h3>
</div>
</template>
<script>
export default{
name:'Movie'
}
</script>
使用 props 接收路由参数
为了简化路由参数获取形式,在路由规则时,声明 props: true
选项
// 在 router/index.js 文件
{ path: '/movie/:id', component: Movie, props: true}
定义好后,即可在Movie组件中,以props的形式接收到路由规则匹配到的参数项。
<template>
<!-- 3、直接使用props中接收的路由参数 -->
<h3> MyMovie组件--{{id}}</h3>
</template>
<script>
export default{
//2、使用props接收路由规则中匹配到的参数项
props:['id']
</script>
编程式导航 API
vue-router 提供了许多编程式导航的 API,其中最常用的导航 API 分别是:
this.$router.push(‘hash 地址’)
- 跳转到指定 hash 地址,并
增加
一条历史记录 。
- 跳转到指定 hash 地址,并
this.$router.replace(‘hash 地址’)
- 跳转到指定的 hash 地址,并
替换掉当前
的历史记录 。
- 跳转到指定的 hash 地址,并
this.$router.go(数值 n)
- 实现导航历史前进、后退。
- $router.
back()
,后退到上一个页面。 - $router.
forward()
,前进到下一个页面。
调用 this.$router.push()
或者 $router.replace()
方法,可以跳转到指定的 hash 地址,展示对应的组件页面 :
<template>
<div class="home-container">
<h3> Home组件 </h3>
<button @click="gotoMovie">跳转到Movie页面</button>
</div>
</template>
<script>
export default{
methods:{
gotollovie(){
this.$router.push("/movie/1")
}
}
</script>
push 和 replace 的区别:
- push 会
增加一条历史记录
- replace 不会增加历史记录,而是
替换掉当前的历史记录
。
调用 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>
导航守卫
每次发生路由的导航跳转时,都会触发全局前置守卫。因此,在全局前置守卫中,我们可以对每个路由进行访问权限的控制:
//创建路由实例对象
const router = new VueRouter({..…})
//调用路由实例对象的beforeEach方法,即可声明“全局前置守卫"
//每次发生路由导航跳转的时候,都会自动触发这个“回调函数”
router.beforeEach((to,from,next) =>{
/* 必须调 next 函数 */
})
守卫方法的 3 个形参:
to
是将要访问的路由的信息 (对象)from
是将要离开的路由的信息对象next
是一个函数,一定要调用next ()
才表示放行,允许这次路由导航 。
注意:
- 在守卫方法中如果
不声明 next
形参,则默认允许用户访问每一个路由
! - 在守卫方法中如果
声明了 next 形参
,则必须调用 next() 函数
,否则不允许用户访问任何一个路由!
next 函数的 3 种调用方式
- 直接放行:
next()
强制其停留在当前页面
:next(false
)强制其跳转到登录页面
:next(’/login’)
router.beforeEach((to,from,next) => {
//获取浏览器缓存的用户信息
const userInfo = window.localStorage.getItem('token');
if (to.path === '/main' && !userInfo ){ //访问的是 main 页面且 token 不存在
// 访问的是后台主页,但是没有token的值,跳转到登入页
next('/login')
} else{
next()//访问的不是后台主页,直接放行
})
拓展
- 在 hash 地址中,
/
后面的参数项,叫做 “路径参数”- 在路由“参数对象”中,需要使用
this.$route.params
来访问路径参数 - 但是我们一般会使用
props
传参来接收路径参数。
- 在路由“参数对象”中,需要使用
- 在 hash 地址中,
?
后面的参数项,叫做**“查询参数”**- 在路由“参数对象”中,需要使用
this.$route.query
来访问查询参数
- 在路由“参数对象”中,需要使用
- 在
this.$route
中,path
只是路径部分;fullPath
是完整的地址。
要想从一个地址跳到另一个地址 (如: 登录后立即跳转到首页,并且首页里设置默认的展示的页面)
this.$router.push(‘hash地址’)