Vue基础
Vue基本概念
Vue是什么
Vue是一个渐进式的JavaScript框架,它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型,帮助你高效地开发用户界面。
- 渐进式:各个特性可以根据项目需要逐渐引入和应用,如Vue Router、Vuex
- 框架:高度封装,拥有自己的规则和元素
- 声明式:Vue基于标准HTML拓展了一套模板语法,使得我们可以声明式地描述最终输出的HTML和JavaScript状态之间的关系
- 组件化:将应用划分为多个独立、可复用的模块
- 响应性:Vue 会自动跟踪JavaScript状态并在其发生变化时响应式地更新DOM
@vue/cli脚手架
@vue/cli脚手架是什么
@vue/cli是Vue官方提供的一个全局模块包,用于创建脚手架项目,它是一个自动化构建项目的工具,帮助开发者快速搭建Vue.js项目的开发环境,其具有开箱即用、无需配置webpack(如babel支持、css和less支持、开发服务器支持)等有点
@vue/cli生成的文件夹目录结构及其作用
-
node_modules:项目依赖的三方包
-
public:静态资源文件
- favicon.io:浏览器小图标
- index.html:单页面的HTML文件
-
src:业务文件夹
- assets:静态资源
- components:组件目录
- App.vue:应用根组件
- main.js:入口js文件
-
.gitignore:git提交忽略配置
-
babel.config.js:babel配置
-
package.js:项目依赖包列表
-
vue.config.js:webpack配置
-
yarn.lock:项目包版本锁定和缓存地址
index.html、main.js 、App.vue的引用关系
引用关系:index.html => main.js => App.vue
Vue指令基础
插值表达式
插值表达式 {{表达式}}
,也叫声明式渲染、文本插值。变量写在data
里,写在data的变量会被vue自动绑定this到当前组件
bind绑定
基础语法
bind绑定可以给标签绑定属性,其写法为:v-bind:属性名="vue变量名"
,简写为::属性名="变量名"
动态class
语法::class="{ 类名: 布尔变量 }
<template>
<div :class="{ redStr: bool }">
动态class,值为true时可以作为类名生效
{{ bool }}
</div>
</template>
<script>
export default {
data() {
return {
bool: false,
}
},
}
</script>
<style scoped>
.redStr {
color: red;
}
</style>
动态style
语法::style="css属性名:值"
<template>
<div :style="{ color: colorStr }">动态style,对style的值进行赋值</div>
</template>
<script>
export default {
data() {
return {
colorStr: 'red',
}
},
}
</script>
v-on事件绑定
vue通过v-on
给标签绑定事件,其写法为v-on:事件名="短代码/函数"
,简写为:@事件名=短代码/函数
,绑定的函数写在methods
里或使用箭头函数
获取事件对象
- 无实参:直接在事件处理函数中通过形参接收
- 有实参:通过
$event
实参指代事件对象传给事件处理函数
<template>
<div>
<!-- 阻止默认事件-无实参 -->
<a
:href="url"
@click="vuePreventDefault"
>baidu</a
><br />
<!-- 阻止默认事件-有实参 -->
<a
:href="url"
@click="vuePreventDefault2(1, $event)"
>baidu2</a
>
</div>
</template>
<script>
export default {
data() {
return {
url: 'https:\\www.baidu.com',
}
},
methods: {
// 阻止默认事件-无实参
vuePreventDefault(e) {
e.preventDefault()
},
// 阻止默认事件-有实参
vuePreventDefault2(num, e) {
console.log(num)
e.preventDefault()
},
},
}
</script>
v-on修饰符
给事件添加常用的功能,语法:@事件名.修饰符=函数名
,常用修饰符有:
.stop
阻止冒泡.prevent
阻止默认行为.once
程序运行期间只触发一次事件处理函数- 按键修饰符:
@keyup.enter
监测回车键@keyup.esc
监测返回键- 更多修饰符参考开发文档events介绍
<template>
<!-- 事件修饰符 -->
<a
:href="url"
@click.prevent="vuePreventTest"
>baidu</a
>
</template>
<script>
export default {
data() {
return {
url: 'https:\\www.baidu.com',
}
},
methods: {
vuePreventTest() {
console.log('超链接跳转失效了')
},
},
}
</script>
v-model双向绑定
通过v-model
可以实现数据变量与表单数据的双向绑定,其内部实现也是通过v-bind
数据绑定和v-on
事件绑定实现的,相对于一个语法糖
<template>
<input
type="text"
v-model="vueModel"
@change="print"
/>
</template>
<script>
export default {
data() {
return {
vueModel: '哈哈哈',
}
},
methods: {
print() {
console.log(this.vueModel)
},
},
}
</script>
复选框的情况
遇到复选款,若v-model
的值为非数组类型,则关联的是复选框的checked
属性,为数组时关联的才是value
值。
<template>
<div>
<input
type="checkbox"
v-model="hobby"
value="吃饭"
/>
吃饭
<input
type="checkbox"
v-model="hobby"
value="睡觉"
/>
睡觉
<input
type="checkbox"
v-model="hobby"
value="打豆豆"
/>
打豆豆
</div>
</template>
<script>
export default {
data() {
return {
hobby: [], //必须是数组,否则关联的是选中状态true/false
}
},
}
</script>
v-model修饰符
给v-model
添加常用功能,如类型转换、去除空白等
.number
以parseFloat转换成数字类型.trim
去除收费空白字符.lazy
在change时(失去焦点)触发而非input时触发
v-text和v-html
通过变量控制innerText
和innerHtml
<template>
<div>
<!-- 按innerText -->
<p v-text="str"></p>
<!-- 按innerHtml -->
<p v-html="str"></p>
</div>
</template>
<script>
export default {
data() {
return {
str: '<span>我是一个span标签<span/>',
}
},
}
</script>
v-if和v-show
通过变量控制标签的显示和隐藏,区别:v-show
采用display:none
的方式控制隐藏和显示,适合频繁切换,而v-if
直接将元素从DOM树添加和移除的方式控制显示和隐藏,在频繁切换时效率低下。v-if
可以搭配v-else-if
和v-else
使用
<template>
<div>
<!-- v-show -->
<div v-show="age >= 18">Enter</div>
<!-- v-if -->
<div v-if="age >= 60">Ban</div>
<div v-else-if="age >= 18">Enter</div>
<div v-else>Ban</div>
</div>
</template>
<script>
export default {
data() {
return {
age: 25,
}
},
}
</script>
v-for
基本用法
v-for
可以实现列表渲染,所在标签结构会按照数据数量循环生成,语法为v-for="(值变量,索引变量) in 目录结构" :key="唯一值"
<template>
<div>
<!-- v-for列表渲染 -->
<ul>
<li
v-for="(item, index) in arr"
:key="item"
>
{{ `item:${item}; index${index}` }}
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
arr: [1, 2, 3, 4, 5, 6, 7],
}
},
}
</script>
数组更新检测
数组便跟方法改变数组,导致v-for
更新,页面刷新,非数组便跟方法返回新数组,可以使用覆盖数组或this.$set()
更新
<template>
<div>
<!-- v-for列表渲染 -->
<ul>
<li
v-for="(item, index) in arr"
:key="item"
>
{{ `item:${item}; index${index}` }}
</li>
</ul>
<button @click="arr.push(1)">push</button>
</div>
</template>
<script>
export default {
data() {
return {
arr: [1, 2, 3, 4, 5, 6, 7],
}
},
}
</script>
v-for就地更新
虚拟DOM本质上时JS和DOM之间的应该映射,在形态上表现为应该能够描述DOM结构及其属性信息的JS对象,帮助我们在更爽更高效的研发模式下保证还有良好的性能。
当数据发生改变后,会先在内存中创建新的虚拟DOM,并与旧的虚拟DOM按一定规则进行比较,然后决定是否复用真实DOM。
传统算法对树形结构的比较通过递归对树节点进行一一对比,时间复杂度为O(n³),效率过于低下,而diff算法时间复杂度为O(n),其关键是:
- 分层对比:因为DOM 节点之间的跨层级操作并不多,同层级操作才是主流,Diff 过程直接放弃了跨层级的节点比较,它只针对相同层级的节点作对比,只需要从上到下的一次遍历,就可以完成对整棵树的对比,以此降低复杂度量级。
- 类型一致的节点才有进行Diff的必要性,只有同类型的组件,才有进一步对比的必要性
- 通过key属性的设置尽可能复用同一层级内的节点,通过识别key可能知道只是顺序发生了变化,就可以只进行插入或删除操作,大量降低成本
<template>
<div>
<!-- v-for列表渲染 -->
<ul>
<li
v-for="item in arr"
:key="item"
>
{{ `item:${item}` }}
</li>
</ul>
<!-- 更新 -->
<button @click="arr.splice(2, 0, arr.length + 1)">insert</button>
</div>
</template>
<script>
export default {
data() {
return {
arr: [1, 2, 3, 4, 5, 6, 7],
}
},
}
</script>
这里可以看到控制台中只有插入的元素闪动了,即只更新了插入部分
过滤器filter
过滤器是用来格式化数据的,其就是一个函数,传入值后返回处理过的值,只能用在插值表达式和v-bind动态属性里
全局过滤器
定义过滤器
// main.js
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
// 定义全局过滤器
Vue.filter('reverse', val => val.split('').reverse().join(''))
new Vue({
render: h => h(App),
}).$mount('#app')
使用过滤器
<!-- xx.vue -->
<template>
<div>
<div>{{ '过滤器的使用' + msg }}</div>
<!-- 使用翻转过滤器 -->
<div>{{ msg | reverse }}</div>
</div>
</template>
<script>
export default {
data() {
return {
msg: 'Hello World',
}
},
}
</script>
局部过滤器
<!-- xx.vue -->
<template>
<div>
<div>{{ '过滤器的使用' + msg }}</div>
<!-- 使用翻转过滤器 -->
<div>{{ msg | reverseLocal }}</div>
</div>
</template>
<script>
export default {
data() {
return {
msg: 'Hello World',
}
},
// 定义局部过滤器
filters: {
reverseLocal: val => val.split('').reverse().join(''),
},
}
</script>
过滤器可以同时使用多个,增加|
隔开即可,也可传递参数,使用方法同函数
<!-- xx.vue -->
<template>
<div>
<div>{{ '过滤器的使用' + msg }}</div>
<!-- 使用多个过滤器,并带有参数 -->
<div>{{ msg | toUp | reverseLocal('|') }}</div>
</div>
</template>
<script>
export default {
data() {
return {
msg: 'Hello World',
}
},
// 定义多个过滤器,并带有参数
filters: {
reverseLocal: (val, s) => val.split('').reverse().join(s),
toUp: val => val.toUpperCase(),
},
}
</script>
计算属性computed
基本用法
一个变量的值依赖另外一些变量计算而来就是计算属性,计算属性函数内的变量改变,会重新返回新的计算结果并渲染页面
<!-- xx.vue -->
<template>
<div>
<div>{{ '计算属性a+b:' + a + '+' + b }}</div>
<!-- 使用计算属性 -->
{{ addAB }}
</div>
</template>
<script>
export default {
data() {
return {
a: 1,
b: 2,
}
},
// 定义计算属性
computed: {
addAB() {
return this.a + this.b
},
},
}
</script>
特性
计算属性带有缓存机制,计算数学定义函数执行后会把return
值缓存起来,依赖项不点,多次调用也是从缓存中取值,当依赖项改变时才会自动重新执行并返回新的值
完整写法
在将计算属性直接通过v-model
关联到input
输入后发现,会报错,原因是无法直接通过v-model
双向绑定修改计算属性的值,这个时候时需要用到setter
的完整写法
<script>
export default {
computed: {
computedName: {
set(val) {
// 设置值时触发的代码
},
get() {
// 获取值时触发的代码
},
},
},
}
</script>
侦听器watch
基本语法
侦听器可以侦听data/computed属性值的改变
<script>
export default {
data() {
return {
// 被侦听的变量
name: '',
}
},
// 侦听器
watch: {
name(oldValue, newValue) {
console.log(newValue, oldValue)
},
},
}
</script>
完整写法
基本语法通过函数实现,但无法对复杂类型进行侦听,也无法设置执行时机,这个时候就要用到完整写法
<!-- xx.vue -->
<template>
<div>
姓:
<input
type="text"
v-model="name.lastName"
/>
名:
<input
type="text"
v-model="name.firstName"
/>
</div>
</template>
<script>
export default {
data() {
return {
// 被侦听的变量
name: {
lastName: '',
firstName: '',
},
}
},
// 侦听器
watch: {
name: {
immediate: true, // 页面一打开就执行一次侦听
deep: true, // 深度侦听
handler(newValue, oldValue) {
console.log(newValue)
console.log(oldValue)
},
},
},
}
</script>
Vue组件
组件是可复用的Vue实例,封装了标签、样式和JS代码,把页面上可复用的部分封装为组件,可以便捷的开发和维护项目。组件使用如下:
- 创建组件
xxx.vue
,封装标签、样式、js代码 - 注册组件:
- 全局注册:在
main.js
中Vue.component('组件名', 组件对象)
- 局部注册:在某
xx.vue
文件中,export default{components: {"组件名": 组件对象 }}
- 全局注册:在
- 引入并使用组件
组件样式scoped作用及其原理
scoped
可以让CSS样式只在当前组件生效,其作用原理是为组件添加机的哈希值data-v-hash
值属性,在获取标签时也会添加[data-v-hash]
的属性选择器,从而保证CSS类名只针对当前的组件生效
组件之间的通信
父传子——props
子组件内定义props接收数据,父组件传递props到子组件实现
儿子:
<!-- MyProduct.vue -->
<template>
<div class="my-product">
<!-- 使用变量 -->
<h3>标题: {{ title }}</h3>
<p>价格: {{ price }}元</p>
<p>{{ intro }}</p>
</div>
</template>
<script>
export default {
// 定义变量准备接收
props: ['title', 'price', 'intro'],
}
</script>
父亲:
<!-- app.vue -->
<template>
<div id="app">
<!-- 3.使用子组件 -->
<Product
v-for="(product, index) in productList"
:key="product.id"
:title="product.name"
:price="product.price"
:intro="product.intro"
:index="index"
/>
</div>
</template>
<script>
// 1.引入子组件
import Product from './components/MyProduct'
export default {
data() {
return {
productList: [
{
id: 1,
name: '钵钵鸡',
price: 1,
intro: '一元一串的钵钵鸡',
},
{
id: 2,
name: '肯德基',
price: 50,
intro: 'Crazy星期四,V我50',
},
{
id: 3,
name: '椰子鸡',
price: 100,
intro: '深圳特产海南椰子鸡',
},
],
}
},
components: {
// 2.注册子组件
Product,
},
}
</script>
**单向数据流:**指的是数据从父组件流向子组件的过程,这种单向数据流能保证数据易于追踪、减少组件之间的耦合度、提高性能。父传子的props
是只读的,不允许修改的。(注意Vue可以不是单向数据流,如eventBus,兄弟之间通信通过中介实现,所以Vue中的单向数据流特指的是直接的通信)
子传父$emit
父组件定义自定义事件,子组件提高$emit
主动触发事件实现
父亲:
<!-- app.vue -->
<template>
<div id="app">
<!-- 父组件中给子组件绑定自定义事件——砍价函数 -->
<Product
v-for="(product, index) in productList"
:key="product.id"
:title="product.name"
:price="product.price"
:intro="product.intro"
:index="index"
@subPrice="subPrice"
/>
</div>
</template>
<script>
import Product from './components/MyProduct_sub'
export default {
data() {
return {
productList: [
{
id: 1,
name: '钵钵鸡',
price: 1,
intro: '一元一串的钵钵鸡',
},
{
id: 2,
name: '肯德基',
price: 50,
intro: 'Crazy星期四,V我50',
},
{
id: 3,
name: '椰子鸡',
price: 100,
intro: '深圳特产海南椰子鸡',
},
],
}
},
methods: {
// 定义砍价函数
subPrice(index) {
this.productList[index].price *= 0.9
},
},
components: {
Product,
},
}
</script>
儿子:
<!-- MyProduct.vue -->
<template>
<div class="my-product">
<h3>标题: {{ title }}</h3>
<p>价格: {{ price }}元</p>
<p>{{ intro }}</p>
<!-- 子组件触发父组件绑定的自定义事件,父组件绑定的函数执行 -->
<button @click="subFn">PDD大宝刀,一刀999</button>
</div>
</template>
<script>
export default {
props: ['index', 'title', 'price', 'intro'],
methods: {
subFn() {
this.$emit('subPrice', this.index)
},
},
}
</script>
跨组件通信eventBus
父组件管理数据
兄弟组件之间的通信实际上通过上面的学习已经可以实现了,其实很简单,把全部数据都丢给父组件管理,通过父子之间的通信转化为兄弟之间的通信,但是这种方法依赖于父组件的介入,可能会使得组件之间的耦合度增加。、
evebntBus
当需要在两个兄弟组件之间进行通信时,可以创建一个eventBus
实例,并在两个组件中都通过$on
来监听事件,通过$emit
来触发事件。这样,当一个组件触发事件时,另一个组件就可以接收到这个事件并进行相应的处理。
首先:先要创建一个空白的Vue对象并导出:src/EventBus/index.js
import Vue from 'vue'
export default new Vue()
接收方要引入空白Vue对象eventBus
并通过$on
监听事件
<!-- List.vue -->
<template>
<ul class="my-product">
<li
v-for="(item, index) in arr"
:key="index"
>
<span>{{ item.name }}</span>
<span>{{ item.price }}</span>
</li>
</ul>
</template>
<script>
// 引入eventBus
import eventBus from '../eventBus'
export default {
props: ['arr'],
// 组件创建完毕时,监听send事件
created() {
eventBus.$on('send', index => {
this.arr[index].price *=0.5
})
}
}
</script>
发送方也要引入eventBus
,然后通过eventBus触发事件
<!-- MyProduct.vue -->
<template>
<div class="my-product">
<h3>标题: {{ title }}</h3>
<p>价格: {{ price }}元</p>
<p>{{ intro }}</p>
<button @click="subFn2">PDD大宝刀,一刀打骨折</button>
</div>
</template>
<script>
// 引入eventBus
import eventBus from '../eventBus'
export default {
props: ['index', 'title', 'price', 'intro'],
methods: {
subFn() {
this.$emit('subPrice', this.index)
},
subFn2() {
eventBus.$emit('send', this.index)
}
},
}
</script>
Vue组件的生命周期
Vue的生命周期指的是Vue组件从创建到销毁的过程,Vue框架内置了钩子函数,随着组件生命周期阶段自动执行。Vue中生命周期共4个阶段,8个方法
生命周期 | 钩子函数 | 钩子函数 |
---|---|---|
初始化 | beforeCreate | created |
挂载 | beforeMount | mounted |
更新 | beforeUpdate | updated |
销毁 | beforeDestory | destoryed |
生命周期如下图:
初始化阶段
- new Vue():Vue组件实例化
- Init Events & Lifecycle:初始化事件和生命周期函数
- beforeCreate:生命周期钩子函数被执行,此时是访问不到data和method的
- Init injections & reactivity:Vue内部添加data和methods等
- created:生命周期钩子函数被执行,实例创建
挂载阶段
- 编译模板阶段:开始分析Has "el"option:检查是否有el选项(如
#App
):- 没有:调用$mount方法
- 有:继续检查有无template选项
- 有:编译template返回render函数
- 无:找到并编译
el
选项对应的标签作为要渲染的模板template
- beforeMount:生命周期钩子函数被执行,此时虚拟DOM还没有被挂载成为真实DOM
- Create vm.$el and replace “el” with it:把虚拟DOM挂载成为真实的DOM
- mounted:生命周期钩子函数被执行,真实DOM挂载完毕,此时可以访问到真实DOM
更新阶段
- 修改数据进入更新阶段
- beforeUpdate:生命周期钩子函数被执行,此时DOM还没被更新(这里不能访问DOM,因为Vue的响应式是异步的,可能还是取到更新后的DOM)
- Virtual DOM re-render and patch:虚拟DOM重新渲染,对真实DOM进行打补丁
- updated:生命周期钩子函数被执行,此时虚拟DOM已更新
销毁阶段
此时要移除一些组件占用的全局资源,如定时器、计时器、全局事件等
- vm.$destory()被调用,比如组件被移除(view-if)
- beforeDestroy:生命周期钩子函数被执行
- 拆卸数据监视器、子组件、事件侦听器
- destroyed:生命周期钩子函数被执行
ref 和nextTick
ref
通过ref获取元素:
<template>
<!-- 设置ref -->
<div
id="h"
ref="myRef"
>
ref
</div>
</template>
<script>
export default {
mounted() {
// 获取DOM的两种方法
console.log(document.getElementById('h'))
console.log(this.$refs.ref)
},
// 后面通过ref获取组件可以
methods: {
sonFn() {
console.log('son的方法执行了')
},
},
}
</script>
通过ref获取组件:
<template>
<div id="app">
// 给组件添加ref
<Ref ref="myComRef" />
</div>
</template>
<script>
import Ref from './components/Ref.vue'
export default {
components: {
Ref,
},
mounted() {
// 获取组件
console.log(this.$refs.myComRef)
// 获取的组件可以调用组件内的方法了
this.$refs.myComRef.sonFn()
},
}
</script>
$nextTick
DOM更新后挨个触发$nextTick
中的函数体执行,而直接调用this.$nextTrick()
返回的是一个Promise对象
<template>
<div id="app">
<button
@click="btnFn"
ref="btnRef"
>
{{ count }}
</button>
</div>
</template>
<script>
export default {
data() {
return {
count: 0,
}
},
methods: {
btnFn() {
this.count++
console.log(this.$refs.btnRef.innerHTML) // 这里点击按钮还是0
this.$nextTick(() => {
console.log(this.$refs.btnRef.innerHTML) // 这里可以拿到数字1了
})
},
},
}
</script>
动态组件和组件缓存
<component :is="componentName">
和<keep-alive>
component+is可以实现组件的动态切换,会根据is后面的字符串匹配组件名展示组件,配合keep-alive标签包裹的组件可以缓存到内存中,不会被立即销毁,实现动态组件切换
<template>
<div id="app">
<div>动态组件</div>
<!-- keep-alive标签包裹实现组件缓存 -->
<keep-alive>
<!-- component+is 实现动态组件 -->
<component :is="comName"></component>
</keep-alive>
<button @click="comName = 'Life'">切换组件Life</button>
<button @click="comName = 'Ref'">切换组件Ref</button>
</div>
</template>
<script>
import Life from './components/Life.vue'
import Ref from './components/Ref.vue'
export default {
data() {
return {
comName: 'Life',
}
},
components: {
Life,
Ref,
},
}
</script>
activated和deactivated钩子函数
activated在组件激活时触发,deactivated在组件失去激活状态时触发,注意组件的created
和mounted
钩子函数只会执行一次,而当组件再次被激活时,会触发activated
钩子函数,而不是再次执行created
或mounted
。
<script>
export default {
data() {
return {
msg: 'hello world',
arr: [1, 1, 1, 1, 1],
}
},
activated() {
console.log('被激活了')
},
deactivated() {
console.log('失活了')
},
}
</script>
组件插槽
基本语法
通过slot标签,让组件内可以接受不同的标签结构显示,组件插入什么标签就显示什么标签
<template>
<div id="container">
<div id="app">
<h3>案例:折叠面板</h3>
<!-- 组件插入内容 -->
<PannelSlot>
<p>寒雨连江夜入吴,</p>
<p>平明送客楚山孤。</p>
<p>洛阳亲友如相问,</p>
<p>一片冰心在玉壶。</p>
</PannelSlot>
</div>
</div>
</template>
<template>
<div>
<div class="title">
<h4>芙蓉楼送辛渐</h4>
<span
class="btn"
@click="isShow = !isShow"
>
{{ isShow ? '收起' : '展开' }}
</span>
</div>
<div
class="container"
v-show="isShow"
>
<!-- 插槽 -->
<slot></slot>
</div>
</div>
</template>
设置默认内容
不给slot标签放置内容,slot标签内的内容作为默认内容显示
<!-- 插槽 -->
<slot>
<p>寒雨连江夜入吴,</p>
<p>平明送客楚山孤。</p>
<p>洛阳亲友如相问,</p>
<p>一片冰心在玉壶。</p>
</slot>
具名插槽
一个组件内有多出需要外部传入标签的地方,使用多个插槽时就需要用到具名插槽,通过name区分slot
名字,并通过template配合v-slot:name
区分插入的地方
<template>
<div id="container">
<div id="app">
<h3>案例:折叠面板</h3>
<!-- 具名插槽 -->
<PannelSlot>
<template v-slot:title>
<h4>芙蓉楼送辛渐</h4>
</template>
<template v-slot:content>
<p>寒雨连江夜入吴,</p>
<p>平明送客楚山孤。</p>
<p>洛阳亲友如相问,</p>
<p>一片冰心在玉壶。</p>
</template>
</PannelSlot>
</div>
</div>
</template>
<template>
<div>
<div class="title">
<!-- 具名插槽 -->
<slot name="title"></slot>
<span
class="btn"
@click="isShow = !isShow"
>
{{ isShow ? '收起' : '展开' }}
</span>
</div>
<div
class="container"
v-show="isShow"
>
<!-- 具名插槽 -->
<slot name="content"> </slot>
</div>
</div>
</template>
作用域插槽
使用插槽时需要使用到子组件内的变量就要用到作用域插槽。在子组件中的slot
标签上绑定属性和子组件内的之,然后再使用组件时传入自定义标签,通过template
标签和v-slot="自定义变量名"
获取变量.
<template>
<div>
<!-- 作用域插槽:下拉内容 -->
<slot
name="scopedSlot"
:row="defaultObj"
>{{ defaultObj.one }}</slot
>
</div>
</template>
<script>
export default {
data() {
return {
isShow: false,
defaultObj: {
one: '1',
two: 2,
},
}
},
}
</script>
<template>
<div id="container">
</PannelSlot>
<!-- 使用作用域插槽 -->
<!-- 这里我们任意取名scope,它会绑定slot上所有属性和值 -->
<template v-slot:scopedSlot="scope"> {{ scope.row.two }}</template>
</PannelSlot>
</div>
</template>
自定义指令
自定义指令可以给组件拓展额外功能,如自动获取焦点
全局注册
Vue.directive('gfocus', {
inserted(el) { // inserted:标签被插入网页时才触发,还有update
// 可以对el标签扩展额外功能
el.focus() // 触发标签事件方法
}
})
局部注册
<script>
export default {
directives: { // 自定义组件
focus: {
inserted(el) {
// 对el进行操作
el.focus()
}
}
}
}
</script>
使用自定义指令
<template>
<input type="text" v-focus>
</template>
自定义指令传值
Vue.directive('color', {
inserted(el, bindingColor) { // inserted:标签被插入网页时才触发
// 可以对el标签扩展额外功能
el.style.color = bindingColor.value // 触发标签事件方法
}
})
自定义指令触发方法
inserted:标签被插入网页时才触发
update:自定义指令所在标签刷新时执行
路由Router
基本使用
vue-router基本使用
- 下载vue_router模块到当前工程
- 在main.js中引入VueRouter函数
- 添加到Vue.use()身上:注册全局RouterLink和RouterView组件
- 创建路由规则数组:路径和组件名的对应关系
- 用规则生成路由对象
- 把路由对象注入到new Vue实例中
- 用router-view作为挂载点切换不同路由页面
// main.js
import Vue from 'vue'
import App from './App.vue'
import Find from './views/Find/Find.vue'
import Mine from './views/Mine/Mine.vue'
import Friends from './views/Friends/Friends.vue'
// 在main.js中引入VueRouter函数
import VueRouter from 'vue-router'
// 2. 注册全局组件
Vue.use(VueRouter)
// 3. 定义规则数组
const routes = [
{
path: '/find',
component: Find,
},
{
path: '/mine',
component: Mine,
},
{
path: '/friends',
component: Friends,
},
]
// 4. 生成路由对象
const router = new VueRouter({
routes: routes, //routes是固定key,传入规则数组,同名可以简写直接写routes
})
Vue.config.productionTip = false
// 5. 将路由对象注入到vue实例中,this可以访问$route和$router
new Vue({
router,
render: h => h(App),
}).$mount('#app')
<!-- App.vue -->
<template>
<div>
<a href="#/find">发现音乐</a>
<br />
<a href="#/mine">我的音乐</a>
<br />
<a href="#/friends">我的朋友</a>
<!-- 6.设置挂载点,当url的hash值路径切换,显示规则里对应的组件到这里 -->
<router-view></router-view>
</div>
</template>
声明式导航
router-link基本使用
vue-router提供了一个全局组件router-link来代替a标签,其实质上最终会渲染成a链接,其to
属性等价于href
,但to
使用时无需用到#
(自动添加),它还通过自带类名router-link-active
、router-link-exact-active
提供了声明式导航高亮的功能。router-link-active
会在当前路由匹配到的路由及其子路由上添加,而 router-link-exact-active
仅在当前路由完全匹配时添加
<!-- App.vue -->
<template>
<div>
<!-- 这里改成了router-link和to -->
<router-link to="/find">发现音乐</router-link>
<br />
<router-link to="/mine">我的音乐</router-link>
<br />
<router-link to="/friends">我的朋友</router-link>
<router-view></router-view>
</div>
</template>
router-link跳转传参
- 通过
/path?参数名=值
,通过$route.query.参数名
获取值
<router-link to="/find?name=哈哈">发现音乐</router-link>
<template>
<div>
<div>发现音乐</div>
<div>发现传入值:{{ $route.query.name }}</div>
</div>
</template>
- 通过路由设置
path/:参数名
,再经path/值
传递参数,在组件中使用$route.params.参数名
接收参数
const routes = [
{
path: '/friends/:name', // 通过冒号接收具体值
component: Friends,
},
]
<router-link to="/friends/小vue">我的朋友</router-link>
<div>发现传入值:{{ $route.params.name }}</div>
路由重定向
通过redirect
实现路由重定向
const routes = [
{
path: '/',
redirect: '/find',
},
]
404
const routes = [
// 404一定要在规则数组的最后
{
path: '*',
component: NotFound,
},
]
hash路由和history路由切换
hash路由是带#
的,如http://localhost:3000/#/friends
,切换为history模式为http://localhost:3000/friends
,但这在上线时需要服务器端支持,否则寻找到的是文件夹
const router = new VueRouter({
routes,
mode:"history"
})
编程式导航基本使用
编程式导航即通过js实现路由的跳转,以下path
、name
二者选一即可。这里vue-router要么安装3.0以下版本,要么再传递两个回调函数,作为成功和失败的回调,否则重复push到同一个路由报错Avoided redundant navigation to current location
,因为3.0以上版本返回的是Promise,需要增加两个回调
this.$router.push({
// path: '路由路径',
name: '路由名' // 使用路由名字在规则数组中需要添加name属性
})
query和params的区别:
query
:通过URL的查询字符串传递参数,它会显示在URL中,且不会影响路由的匹配。params
:通过name
的路由参数传递,它不会显示在URL中,且必须与name
的路由规则匹配。
编程式导航传参
this.$router.push({
// path: '路由路径',
name: '路由名',
query: {
'参数'
},
params: { // 使用params只能用name
'参数'
}
})
二级路由
二级路由通过配置规则数组路由的``children`实现
const routes = [
{
path: '/find',
name: 'find',
component: Find,
// 配置children
children: [
{
path: 'second', // 这里不需要/
component: Second,
},
],
},
]
<template>
<div>
<div>发现音乐</div>
<!-- 这里还是要加上/find/ -->
<router-link to="/find/second">链接到Second</router-link>
<!-- 用来展示组件 -->
<router-view></router-view>
</div>
</template>
<script>
export default {}
</script>
路由守卫
路由守卫可以让路由跳转前先触发一个函数,进行守护,主要用于路由权限判断
router.beforeEach((to, from, next) => {
// to: Object 要跳转到的路由对象信息
// from: Object 从哪里跳转的路由对象信息
// next: Function next()路由正常切换 next(false)原地停留 next("路径")强制修改到另一个路由上 不调用next也停留在原地
})
riends`,但这在上线时需要服务器端支持,否则寻找到的是文件夹
const router = new VueRouter({
routes,
mode:"history"
})
编程式导航基本使用
编程式导航即通过js实现路由的跳转,以下path
、name
二者选一即可。这里vue-router要么安装3.0以下版本,要么再传递两个回调函数,作为成功和失败的回调,否则重复push到同一个路由报错Avoided redundant navigation to current location
,因为3.0以上版本返回的是Promise,需要增加两个回调
this.$router.push({
// path: '路由路径',
name: '路由名' // 使用路由名字在规则数组中需要添加name属性
})
query和params的区别:
query
:通过URL的查询字符串传递参数,它会显示在URL中,且不会影响路由的匹配。params
:通过name
的路由参数传递,它不会显示在URL中,且必须与name
的路由规则匹配。
编程式导航传参
this.$router.push({
// path: '路由路径',
name: '路由名',
query: {
'参数'
},
params: { // 使用params只能用name
'参数'
}
})
二级路由
二级路由通过配置规则数组路由的``children`实现
const routes = [
{
path: '/find',
name: 'find',
component: Find,
// 配置children
children: [
{
path: 'second', // 这里不需要/
component: Second,
},
],
},
]
<template>
<div>
<div>发现音乐</div>
<!-- 这里还是要加上/find/ -->
<router-link to="/find/second">链接到Second</router-link>
<!-- 用来展示组件 -->
<router-view></router-view>
</div>
</template>
<script>
export default {}
</script>
路由守卫
路由守卫可以让路由跳转前先触发一个函数,进行守护,主要用于路由权限判断
router.beforeEach((to, from, next) => {
// to: Object 要跳转到的路由对象信息
// from: Object 从哪里跳转的路由对象信息
// next: Function next()路由正常切换 next(false)原地停留 next("路径")强制修改到另一个路由上 不调用next也停留在原地
})