vue2知识点
这里写目录标题
- vue2知识点
- 1.插值语法 Mustache语法
- 2.Vue指令
- (3)属性绑定指令
- 3.key属性
- 4.数组的响应式监听
- 5.对象增加属性和删除属性
- 6.计算属性computed
- 7.watch侦听 / 监听
- 8.生命周期(钩子)
- 9.注册全局组件
- 10.vm vc vm 管理 vc
- 11.单文件写法,为脚手架做准备了
- 12.下载脚手架工具
- 13.组件ref属性--父组件来修改子组件的值
- 14.单文件组件需要注意的几个点:
- 15.动态组件
- 16.缓存动态组件
- 17.在style中控制元素---用于修改css框架的默认样式
- 18.父子组件通信
- 19.props的接受写法
- 20.props未接受的属性
- 21.深究js中导入导出
- 22.父子传值--- 自定义事件 子组件改变父组件中的值
- 23.插槽
- 24.自定义指令
- 25.全局事件总线-任意组件通信(利用自定义事件**)**
- 26.利用自定义指令实现图片的懒加载
- 27.按钮节流操作
- 28.mixin混入 复用 配置项
- 29.plugin插件
- 30.生命周期中nextTick
- 31.过渡
- 32.v-model用到`组件`上
- 33.vuex
- 34.获取state数据,actions函数,mutations函数的其他方式
- 35.getters
- 36.modules
- 37.在一个模块中想要触发或访问 其他模块 中数据或函数时
- 38.router(现在没怎么使用router了)
1.插值语法 Mustache语法
解析 标签体 的内容
{{js表达式}} 不能是js语句
<div id='app'>
{{msg}}
</div>
修改差值写法
在配置项中
delimiters:['${','}']
<div id='app'>
${msg}
</div>
2.Vue指令
1.内容渲染指令
2.条件渲染指令
3.属性绑定指令
4.事件绑定指令
5.列表渲染指令
6.双向数据绑定
(1)内容渲染指令
1.1 v-html
<div id='app' v-html="msg"></div>
data() {
return {
msg: `<span style="color:red;">我是span</span>`
}
},
不建议用:如果用户输入个脚本,解析成html就完蛋了!
1.2 v-text
将值放到指定节点中 不识别为dom结构
(2)条件渲染指令
2.1 v-if 条件渲染
<span v-if="表达式">显示</span>
表达式会被转换成boolean值,true显示,false不显示span(浏览器压根不渲染,在dom结构中也不存在)
2.2 v-else
与v-if搭配使用
<span v-if="false">显示1 if中的</span>
<span v-else>显示2 else中的</span>
两者只能显示一个
2.3 v-else-if
多个判断分支
<span v-if="score >= 60 && score < 80">及格</span>
<span v-else-if="score >= 80">优秀</span>
<span v-else-if="score < 60">回炉重造</span>
2.4 v-show
<div v-show="bool">显示</div>
2.5 v-if和v-show的区别
v-if是加载渲染,而v-show改变状态改的是css样式。
2.6 v-once
使用此指令的元素 只会渲染一次(一次性指令)
后续数据的更新 不会引起视图的变化
<div v-once>{{bool}}</div>
(3)属性绑定指令
v-bind 动态绑定属性
<!-- 普通dom属性绑定 -->
<!-- <img src="01.jpg" alt=""> -->
<img v-bind:src='imgData' v-bind:title="alt">
<!-- 简写方式 -->
<img :src='imgData' :title="alt">
给挂载的入口节点dom身上操作不行
基础写法
.active {
background-color: orange;
}
<h2 v-bind:class="activeClass">我是h2标签</h2>
activeClass: 'active'
对象写法
<h2 v-bind:class="{ active:bool }">我是h2标签</h2>
数组语法写法
<h2 :class="['active','line']">我是h2标签</h2>
<h2 :class="['active',{line:bool}]">我是h2标签</h2>
style属性的绑定
对象写法
<p :style="{backgroundColor:bgColor,height:heightValue + 'px'}">p</p>
数组写法
<p :style="[
{backgroundColor:bgColor,height:heightValue + 'px'},
{opacity:opacityValue}
]"></p>
(4)事件绑定指令
v-on 事件绑定
v-on:type='事件函数'
type 点击 拖拽 划入 滑出 键盘 鼠标…事件函数 普通函数的定义位置 放在methods里面
函数作为事件函数使用时 不需要加 ( ) 执行
methods里面直接定义的 函数 不要写 箭头函数 要不然 this会指向window
事件函数传参情况
1.不传参数,不加括号
2.正常传参 加括号写实参 需要定义形参
<button @click="addNum(1)">1</button>
接受event事件对象
<button @click="addNum">1</button>
methods: {
addNum(value) {
console.log(value); //PointerEvent {.....}
this.num += 1
}
}
//定义形参,不传实参
又想传普通参,又想传事件对象
实参需要传 $event 其他一一对应即可
<button @click="addNum(1,$event)">1</button>
methods: {
addNum(value, event) {
console.log(value, event);
this.num += 1
}
}
v-on多事件处理处理器
此时就要带括号执行了!
<button @click="changeNum(),changeFn()">点击 ++ </button>
v-on事件修饰符
.stop阻止事件冒泡(常用)
(不想泡泡冒到父级+祖先身上 就在该dom上 阻止事件冒泡 )
.prevent 阻止默认事件 (常用)
<input type="submit" @click.prevent> (有默认提交效果)
(5)列表循环指令
v-for
数组
对象
字符串
数组
(6)双向数据绑定
v-model
语法糖写法
~ <input type="text" v-model:value="msg"> 默认绑定value 不需要 写也可以
<input type="text" v-model="msg">
new Vue({
el: '#app',
data() {
return {
msg: '123'
}
},
methods: {
changeMsg(event) {
console.log(event);
this.msg = event.target.value
}
}
})
原生的写法
利用事件对象中的target里面的value值来手动赋值设置
<input type="text" v-model:value="msg"> 默认绑定value 不需要 写也可以
<input type="text" v-model="msg">
data() {
return {
msg: '123'
}
},
V-model修饰词
.lazy 只有在input框内 回车 才会触发数据的更新(要不然input框就是实时更新)
<input type="text" v-model.lazy="msg"
.number 尝试将输入的值转换成数值类型
<input type="text" v-model.number="num1">
<input type="text" v-model.number="num2">
.trim 去除首尾空格
<input type="text" v-model.trim="msg">
v-model的几个应用
绑定复选框
<span>爱好: </span>
<label for="checkbox1">睡觉</label>
<input type="checkbox" v-model="checkboxValue" id="checkbox1" value="sleep">
<label for="checkbox2">花钱</label>
<input type="checkbox" v-model="checkboxValue" id="checkbox2" value="money">
<label for="checkbox3">吃东西</label>
<input type="checkbox" v-model="checkboxValue" id="checkbox3" value="eat">
单选按钮
<span>性别: </span>
<label for="checkbox1">女</label>
<input type="radio" v-model="checkboxValue" id="checkbox1" value="woman">
<label for="checkbox2">男</label>
<input type="radio" v-model="checkboxValue" id="checkbox2" value="man">
<label for="checkbox3">其他</label>
<input type="radio" v-model="checkboxValue" id="checkbox3" value="else">
下拉选框
<select v-model="selectValue">
<option disabled>请选择男朋友</option>
<option>吴彦祖</option>
<option>彭于晏</option>
<option>胡歌</option>
</select>
selectValue: ''
多选下拉
<select v-model="selectValue" multiple>
<option disabled>请选择男朋友</option>
<option>吴彦祖</option>
<option>彭于晏</option>
<option>胡歌</option>
</select>
selectValue: [] 数组
3.key属性
使用了v-for就需要给节点带上key值 值必须是独一无二的值 一般是对象内的id
为什么要使用key值?
vue采用的就近原则,当页面重新渲染的时候,它会找就近的位置进行渲染,使用key值就相当于给节点设置了一个身份!
v-for和v-if不要用在同一个节点上
能在父级上用v-if 就在父级上面用
不能的话 外面就包一个父级(template)
<template v-if="true"> <span v-for="item in arr" :key="item.name">{{item.name}}</span> </template>
4.数组的响应式监听
this.arr[4]=obj
类似于上面的赋值操作,不能引起响应式变化
解决方法
(可以修改原数组这个api)
push
shift
unshift
sort
splice
reverse
pop …(不修改原数组的api)
map
reduce
forEach
concat
slice
filter …
5.对象增加属性和删除属性
实例内
添加属性
this. s e t ( t a r g e t O b j , k e y , v a l u e ) 删除属性 t h i s . set(targetObj,key,value) 删除属性 this. set(targetObj,key,value)删除属性this.delete(targetObj,key)<button @click="changeMsg">点我</button> new Vue({ el: '#app', data() { return { msg: { name:'jack', age:183 } } }, methods: { changeMsg(event) { this.$set(this.msg,"gonghui",1232) console.log(this.msg); } } }) 放到外面 vm.$set(vm.obj,key,value) Vue构造函数上也有这个方法 Vue.set(vm.obj, 'hobby', '花钱')
上述代码是可以发生响应式变化的
// this.obj.hobby = '花钱' // delete this.obj.age 无法引起响应式变化 (数据变了 但是页面dom上不发生更新)
6.计算属性computed
是一个加工厂,用来加工data中的数据(当然其他地方的数据也可以加工)
用的时候 不要加括号 因为不是函数
getter和setter
computed: {
// handleName() {
// return this.myname + '~~~~~'
// }
handleName: {
get: function () {
console.log('-----get------');
return this.firstname + ' ' + this.lastname
},
set: function (newValue) {
console.log('-----set------', newValue);
this.firstname = newValue.split(" ")[0]
this.lastname = newValue.split(" ")[1]
}
}
}
getter和setter触发的时机
首次获取(模板内使用了该计算属性 或者你在其他地方使用 )这个计算属性 触发getter
因为有缓存 其他多次获取不会触发当计算属性 依赖的数据(本身是响应式数据==> 比如data中的数据 )发生变化时 会再次触发 getter
当 对计算属性 有赋值操作时 会触发 setter ( 一般情况很少用到 )
7.watch侦听 / 监听
监听 顾名思义就是监督
num: {
handler(newValue, oldValue) {
console.log('新值', newValue);
console.log('旧值', oldValue);
},
// 不管num发没发生变化 先执行回调函数
immediate: true,
// 深度监听 默认是false
deep: true // 视情况使用
}
对对象的监听 非深度
( 一层监听 只有 = 赋值修改时 才会触发监听 ) 对内层属性的修改不能监听
obj: {
handler(newV, oldV) {
console.log(newV, oldV);
},
// 开启深度监听
deep: true
}
只想监听 对象的某个属性 (如果属性是基础值类型 不需要deep true 如果属性 还是对象类型 deep:true加上才能深度监听 )
"obj.name": {
handler(newV, oldV) {
console.log(newV, oldV);
}
}
8.生命周期(钩子)
9.注册全局组件
分成三步
第一步
// 在这个配置项中 vm的配置对象内的配置属性 几乎都可以使用 除了 el 因为子组件不需要挂载
// 先配置子组件配置对象 然后先注册 再生成vm实例对象
const child = Vue.extend({
// 给child定义的数据
data() {
return {
msg: '我是儿子'
}
},
mounted() {
console.log(this);
},
template: `
<h1>{{msg}}</h1>
`
})
第二步
// 全局注册
Vue.component('child', child)
第三步 使用组件
<div id='app'>
<!-- 单文件组件XXX.vue(一个文件对应一个组件) 非单文件组件 .html (可以创建多个组件)-->
<child></child>
</div>
注意点
1.vue2中只允许一个顶节点
2.child 构造函数 解析到有child组件使用时 new 构造函数 生成 vc实例对象
10.vm vc vm 管理 vc
<script src='https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.js'></script>
Vue.prototype.$axios = axios
const child = Vue.extend({
// 给child定义的数据
data() {
return {
msg: '我是儿子'
}
},
mounted() {
// this.fn()
console.log(this.$axios);
},
template: `
<div>
<h1>{{msg}}</h1>
<h1>{{msg}}</h1>
</div>
`
})
11.单文件写法,为脚手架做准备了
<template>
<div>组件的dom<son></son></div>
</template>
<script>
// 导入 son的配置对象
import son from "./son.vue";
export default {
data() {
return {
msg: "我是儿子",
};
},
components: {
son: son,
},
};
</script>
<style></style>
12.下载脚手架工具
全局安装
npm i -g @vue/cli
查看版本
@vue/cli 4.5.14 你们的 最新的应该是 5 开头的
vue -V
搭建基础项目
cd到对应文件夹 在命令行中 初入 vue create 07vue-demo---
Vue CLI v5.0.4
? Please pick a preset:
Default ([Vue 3] babel, eslint)
Default ([Vue 2] babel, eslint)
> Manually select features (!) 自己选择版本和配置 回车确认
要使用哪些功能到你的项目中 (空格操作取消 和 勾选)
? Check the features needed for your project: (Press <space> to select, <a> to toggle all, <i> to
invert selection, and <enter> to proceed)
>(*) Babel // 兼容打包成(ES5以外的)低版本的js
( ) TypeScript // js的超集
( ) Progressive Web App (PWA) Support // 是否要把项目添加到桌面图标
( ) Router // 配置路由
( ) Vuex // 状态管理
( ) CSS Pre-processors // css预处理工具
( ) Linter / Formatter // 代码检查工具
( ) Unit Testing // 测试工具 不需要
( ) E2E Testing // 测试工具 不需要
选择vue的版本
? Choose a version of Vue.js that you want to start the project with
3.x
> 2.x
配置是放到单独的文件 还是 package.json
? Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys)
> In dedicated config files
In package.json
不要保存为 预设项
Save this as a preset for future projects? (y/N) N
等待即可
各部分功能解析
public
index.html 整个项目的入口html文件 在浏览器上看到的页面 div#app 整个项目最大的容器
src资源 项目的99%的内容都在这里
assets 存放静态资源 图片资源 共用的js 公用的css sas less
components router 存放组件 .vue
App.vue 根组件 有一个 配置对象{} 传给 new Vue({}) 生成vm实例对象
main.js 入口js文件
.browserslistrc 浏览器版本控制
.gitignore 上传到远程仓库 省略一些什么文件
babel.config.js babel的配置文件
jsconfig.json 项目的配置项
vue.config.js 可以配置 webpack的一些设置
main.js
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
// 底层 之前 运行 自动模板渲染成dom
// 手动的调用 渲染函数 模板渲染成dom
// 生成的项目中 我们所使用的的vue 不是完整的vue 砍掉了 渲染功能
// 渲染功能 只在 开发阶段可以使用 上线之后 这个功能对于项目而言是无用的
new Vue({
render: h => h(App),
}).$mount('#app')
单页面应用的逻辑
虽然路由在换 但实际上 都是同一个页面---public里面的index.html 切换了展示的组件
<style scoped> scoped 带作用域 限制里面的css只服务于 当前组件
<img src="@/assets/logo.png" alt="" /> 可以不需要考虑文件的相对关系 只需要考虑资源在 src目录中存储的位置
@/ 相当于 src/----方便绝对路径,不用考虑相对路径
export default {
name: "HelloWorld",
data() {
return {
src: require("@/assets/logo.png"), 如果在data中要用@ 就要用require请求一下资源
};
},
};
13.组件ref属性–父组件来修改子组件的值
作用:在vue中获取dom的方法
ref在 普通dom上使用 就是获取的普通dom
在组件上用 获取到的是helloworld组件的vc对象
<template>
<div id="app">
<son ref="son"></son>
<button @click='changeMsg'>修改儿子的值</button>
</div>
</template>
<script>
import son from "./components/son.vue";
export default {
name: "App",
components: {
son,
},
methods: {
changeMsg(){
// console.log( this.$refs);
console.log(this.$refs.son);
this.$refs.son.msg='456'
}
},
};
</script>
<style>
</style>
14.单文件组件需要注意的几个点:
1.组件配置项中 name的名字都是要配置的 因为name就是在开发工具里出现的名字。
2.组件名字的命名一般才用大驼峰命名法。
3.template中只能有一个节点 (vue3中忽略不计,因为vue3中会自动地帮我们去找虚拟的节点)
15.动态组件
根据条件,动态的切换组件。
<component :is="componentName"></component> 组件名
16.缓存动态组件
场景:用户在切换这个组件的时候,希望保存这个组件的值。input输入框。被缓存的组件 不会销毁 而是暂时失去活力
<keep-alive>
<component :is="componentId"></component>
</keep-alive>
缓存动态组件的两个非主流生命周期
activited(){
//激活时
}
deactivited(){
//失活时
}
被缓存的组件 二次激活时 不会触发created mounted
<!-- include 对应的组件的name值 定义可以被缓存的组件 -->
<!-- exclude 对应的组件的name值 定义不缓存的组件 -->
<keep-alive exclude="Singer">
<component :is="componentId"></component>
</keep-alive>
vue3中 (不能定义组件的name 也就是 不能使用 exclude include)
17.在style中控制元素—用于修改css框架的默认样式
vue2深度控制子元素
.singer /deep/ li {
list-style: none;
}
.singer 组件的顶节点 /deep/ 控制到 组件内的 子元素的css
常用于 修改 css框架---element UI的默认样式
/deep/ li 所有子组件内部的li都会修改,遵循css原理
18.父子组件通信
父组件传值给子组件
子组件用props接收,父组件以动态属性的形式传递!
props内的数据 爸爸内发生变化 儿子也会发生变化
一定要注意 不要在 子组件中修改props值!!如果有使用场景需要对props里的值进行加工 请使用computed!
子组件中 props: ["title", "list"],//传递数据的标签属性
父组件中 <SelectList :title="title" :list="singerList"></SelectList>
19.props的接受写法
数组写法:props: [“title”, “list”]
对象写法:主要是进行一些类型的检查
key为属性 value为规则
props: {
// 基础的类型检查
title: String,
msg: String,
// 可以接收多个数据类型
// msg: [String, Number],
// 接收字符串型 并且必传值 Missing required prop: "msg" 缺失必须的prop msg
// msg: {
// type: String,
// required: true,
// },
// 接收字符串型 但如果没有传递 使用default默认值 (用户可配置项 可以用default)
// msg: {
// type: String,
// default: "我是默认值",
// },
20.props未接受的属性
对于未接收的list 将会出现在
this.$attrs
对象内 在 selectList的顶节点上 也有(标签属性)
21.深究js中导入导出
import export
(1)默认(加入了default)导出(完全由使用者去命名—在import的时候由使用者命名)
export default {
name:'东星耀阳'
}
(2)导入的时候–只有默认导出导入不需要加上{}
import obj from './01'
console.log(obj);
(3)命名导出
export const arr = [1, 2]
//主人去设置名字,我引入的时候必须就要解构赋值了
//按需求去导入
导入
import { arr } from './01' arr要和导出文件 对应
console.log(arr);
(4)统一导出
// 统一导出
const arr = [1, 2]
const obj = { name: 1 }
export {
arr,
obj
}
导入
import { arr } from './01'
console.log(arr);
22.父子传值— 自定义事件 子组件改变父组件中的值
思路
在子组件中,不是直接修改爸爸的值,而是自定义事件通知爸爸去修改爸爸的值
在父组件中使用自定义事件,子组件中去触发这个自定义事件
子组件
<template>
<div class='son'>
我是儿子里面的值{{msg}}
<button @click='change'>xiugai</button>
</div>
</template>
<script>
export default {
name: "son",
data () {
return {
msg:'123'
};
},
methods: {
change(){
this.$emit('change')
}
},
}
</script>
<style lang="scss" scoped>
</style>
父组件
<template>
<div class='App'>
msg:{{msg}}
<son @change='changemsg'></son>
</div>
</template>
<script>
import son from '@/components/son'
export default {
name: "App",
data () {
return {
msg:'我是爸爸的值'
};
},
components:{
son
},
methods: {
changemsg(){
this.msg+=1111
}
},
}
</script>
<style lang="scss" scoped>
</style>
另一种注册自定义事件的方法
父组件 // 利用ref属性
mounted() {
// 给 selectList 实例对象注册上 change自定义事件
this.$refs.selectListRef.$on("change", this.changeContent); mounted() {
//一次性的
this.$refs.selectListRef.$once("change", this.changeContent);
},
destroyed() {
// 解绑事件
this.$refs.selectListRef.$off("change", this.changeContent);
},
},
23.插槽
用于 父组件 给 子组件传递 html 内容-----用于element UI
如何将dom放在组件中去渲染,插槽帮我们解决了这个问题。
Vnode是个虚拟节点
默认插槽
第一步
<SelectList :msg="msg">
<span style="background: red">这是父亲传来的dom</span>
</SelectList>
第二步
子组件内
<div class="select-list">
<h2>msg:{{ msg }}</h2>
<slot></slot>
</div>
具名插槽
目的:将不同的slot分开,区分结构
先给 槽位安排 名字
<div class="header"><slot name="header"></slot></div>
<div class="content"><slot name="content"></slot></div>
<div class="footer"><slot name="footer"></slot></div>
父组件中
<SelectList :msg="msg">
<!-- slot值对应到 slot 的name值 才能放到对应的坑位 -->
<template slot="header">
<span style="background: red">头部</span>
</template>
<template slot="content">
<span style="background: blue">内容</span>
</template>
<template slot="footer">
<span style="background: orange">尾部</span>
</template>
</SelectList>
作用域插槽(传数据 子传到父)
第一步: 传数据
<slot name="header" :dataList="arr"></slot>
第二步 接收 并使用 (使用范围仅在 接收的插槽dom内 )
<SelectList :msg="msg">
<template slot="header" scope="{ dataList }">
<span>{{ dataList }}</span>
</template>
或者 abc 随便命名
<template slot="header" scope="abc">
<span>{{ abc.dataList }}</span>
</template>
</SelectList>
scope接收的是一个对象。
24.自定义指令
全局注册指令
directive有两种写法,一种是回调函数,一种是对象写法,对象写法里面是常用的三个生命周期的函数
全局注册
回调函数
Vue.directive(指令名,cb)
使用 <div v-指令名='value'></div>
//指令是等到模板渲染的时候才会执行
main.js中
Vue.directive('abc', () => {
console.log(1111);
})
组件中
使用指令
Singer.vue
<template>
<div class="singer">
// 解析到了这 才会去执行指令
<h1 v-abc="456">{{ content }}</h1>
<SelectList :msg="msg"></SelectList>
<hr />
</div>
</template>
对象写法
为什么要使用对象写法呢?
下面先来看一个例子
小bug
Vue.directive('focus', (el, binding) => {
// 操作dom 确保dom已经渲染完毕
console.log(el);
el.focus() // 这里没有效果时因为 此时 input还没渲染好
})
解决方法
解决方案 写完整写法
Vue.directive('focus', {
// 钩子函数
bind(el, binding) {
// 指令和元素成功绑定时
},
inserted(el, binding) {
// 指令所在元素被插入页面时调用 dom被渲染完毕
el.focus()
},
update(el, binding) {
// 节点更新时调用 绑定值发生变化时调用
console.log(binding.value);
}
})
小demo
实现一个加载指令
<ul style="height: 100px; background-color: red" v-loading="bool">
<li v-for="item in arr" :key="item.num">{{ item.num }}</li>
</ul>
bool: true,
arr: null,
mounted() {
setTimeout(() => {
this.arr = [{ num: 1 }, { num: 2 }, { num: 3 }];
this.bool = false;
}, 1000);
},
Vue.directive('loading', {
inserted(el, binding) {
if (binding.value) {
el.img = document.createElement("img")
el.img.src = require('./assets/images/loading.gif')
el.appendChild(el.img)
}
},
update(el, binding) {
// false的时候
if (!binding.value) {
el.removeChild(el.img)
}else {
el.appendChild(el.img)
}
},
})
局部注册指令
在配置对象中 只能用在 配置对象 对应的组件中
directives: {
// bind时机
//只想在bind时机上用,直接写组件名
// abc(el, binding) {
// console.log(el);
// },
// 完整写法
abc: {
bind(el, binding) {},
inserted() {},
update() {},
},
},
两个单词 使用的时候 v-abc-a
"abc-a":{
}
25.全局事件总线-任意组件通信(利用自定义事件**)**
好处:任意组件之间可以通讯,不用组件!
main.js 把vm在Vue的原型对象上存一下
const vm = new Vue({
render: h => h(App),
beforeCreate() {
Vue.prototype.$bus = this // this就是vm
}
}).$mount('#app')
Song中
<template>
<div class="song">
<h1>{{ content }}</h1>
</div>
</template>
<script>
export default {
name: "Song",
data() {
return {
content: "Song",
};
},
components: {},
mounted() {
// 注册 形参 value
this.$bus.$on("toSong", (value) => {
console.log(value);
this.content += value;
});
},
};
</script>
<style scoped></style>
Singer中
<template>
<div class="singer">
<h1>{{ content }}</h1>
<button @click="fn">点击发送数据给Song</button>
</div>
</template>
<script>
export default {
name: "Singer",
data() {
return {
content: "Singer",
};
},
methods: {
fn() {
// Singer 传值(实参 触发事件) 给 Song (形参接收实参 注册事件)
this.$bus.$emit("toSong", "这是Singer给Song的数据");
},
},
};
</script>
//重点就是搞清楚 哪个注册哪个执行
//传值的那个触发执行,
//被传值的那个是注册事件
this. b u s . bus. bus.on注册事件
this. b u s . bus. bus.emit 触发事件,跟自定义事件的emit一样用法
26.利用自定义指令实现图片的懒加载
在视口范围内的img src正常 url 请求资源
非视口范围内的img src 赋值为默认图片
img进入的视口了 src 替换为正常url 正常请求资源
main.js
let scrollHTML = document.documentElement.scrollTop
let windo3wHeight = window.innerHeight
const refreshScroll = () => {
scrollHTML = document.documentElement.scrollTop
}
const refreshHeight = () => {
windowHeight = window.innerHeight
}
window.addEventListener('scroll', refreshScroll);
window.addEventListener('resize', refreshHeight);
let defaultImg = require('./assets/images/default.png')
Vue.directive('lazy', {
inserted(el, binding) {
const observe = new IntersectionObserver((changes) => {
// console.log(changes[0].boundingClientRect.y);
const ImgTop = changes[0].boundingClientRect.y
// 图片上方到html顶部的距离 > 浏览器高度 + html的滚动距离 视口外
// 图片上方到html顶部的距离 < 浏览器高度 + html的滚动距离 视口内
if (ImgTop > windowHeight + scrollHTML) {
// 视口外
el.src = defaultImg
} else {
//视口内
setTimeout(() => {
el.src = binding.value
// 监听解绑
observe.unobserve(el)
}, 500)
}
}, {})
// 监听的操作
observe.observe(el)
}
})
Singer.vue
<ul>
<li v-for="(item, index) in arr" :key="index">
<img alt="" v-lazy="item.srcImage" />
</li>
</ul>
27.按钮节流操作
// 事件捕获 事件冒泡 定时器
Vue.directive('throttle', {
inserted(el, binding) {
let throttleTime = binding.value
//当用户没有传 默认2s
if (!throttleTime) {
throttleTime = 2000
}
let cbFun
// 捕获 后绑定的事件 先执行
el.addEventListener('click', (event) => {
if (!cbFun) { //第一次执行 上
cbFun = setTimeout(() => {
cbFun = null
}, throttleTime)//
} else { //下
event && event.stopImmediatePropagation()
}
}, true)
}
})
<button @click="sayBye" v-throttle="3000">11111</button>
methods: {
sayBye() {
console.log("bye ~~~");
},
},
28.mixin混入 复用 配置项
目的:抽离公共项,复用
原始情况:
原始情况
----目的是抽离公共项,复用
<template>
<div class="song">
<h1>{{ contentCom }}</h1>
</div>
</template>
<script>
export default {
name: "Song",
data() {
return {
content: "Song",
};
},
computed: {
contentCom() {
return (this.content += "~~~~");
},
},
methods: {
fn() {
console.log(1111);
},
},
mounted() {
console.log(2222);
},
};
</script>
<style scoped></style>
singer
<template>
<div class="singer">
<h1>{{ contentCom }}</h1>
</div>
</template>
<script>
export default {
name: "Singer",
data() {
return {
content: "Singer",
};
},
computed: {
contentCom() {
return (this.content += "~~~~");
},
},
methods: {
fn() {
console.log(1111);
},
},
mounted() {
console.log(2222);
},
};
</script>
抽离公共项
mixin.js //文件的名字是自由定义的,也可以定义多个,最后只要在mixins:[]
//引入即可
export const mixin = {
computed: {
contentCom() {
return (this.content += "~~~~");
},
},
methods: {
fn() {
console.log(1111);
},
},
mounted() {
// console.log(this.$el);
},
}
混入公共项
混入公共项。使用配置项mixins:[]接受 //文件的名字是自由定义的
全局使用:Vue.mixin(mixin)
注意点:
1.如果和自己身上的
生命周期
同时使用(不会覆盖) 先运行mixin中的 再运行组件内部的2.其他配置项中 出现同一个定义项 函数名 数据名 优先使用自己的
29.plugin插件
目的:增加vue的全局功能
plugin/index.js 或者 plugin.js
export default {
install(Vue, options) {
// 在这里 全局功能的添加
Vue.directive('throttle', {
inserted(el, binding) {
let throttleTime = binding.value
//当用户没有传 默认2s
if (!throttleTime) {
throttleTime = 2000
}
let cbFun
// 捕获 后绑定的事件 先执行
el.addEventListener('click', (event) => {
if (!cbFun) { //第一次执行 上
cbFun = setTimeout(() => {
cbFun = null
}, throttleTime)
} else { //下
event && event.stopImmediatePropagation()
}
}, true)
}
})
// Vue.component(...)
// Vue.mixin(....)
// Vue.prototype.$bus = xxxx
}
}
加载插件
在main.js中
import plugins from './plugin'
Vue.use(plugins)
30.生命周期中nextTick
情景问题:
由于定时器不知道多少秒能够渲染完毕
,所以使用nextTick,下次dom更新结束后来执行回调!
<div class="singer">
<h1>{{ content }}</h1>
<div v-if="isShow">
<input type="text" ref="ipt" />
</div>
<button @click="changeIsShow">触发变化</button>
</div>
isShow: false,
changeIsShow() {
this.isShow = !this.isShow;
// setTimeout(() => {
// if (this.isShow) {
// // 得等 input渲染完毕之后才能 获取
// console.log(this.$refs.ipt);
// }
// }, 500);
// 等下一次dom更新结束后 执行回调
this.$nextTick(() => {
if (this.isShow) {
// 得等 input渲染完毕之后才能 获取
console.log(this.$refs.ipt);
this.$refs.ipt.focus();
}
});
// if (this.isShow) {
// // 得等 input渲染完毕之后才能 获取
// console.log(this.$refs.ipt);
// this.$refs.ipt.focus();
// }
},
//应用情景:需求要在一个盒子里面渲染一个数组的内容,我要获取外层盒子的高度
//异步获取的情况
31.过渡
1.过渡中的四点两过
要过渡的dom要transition标签给他包裹起来
四点:
v-enter
v-enter-to
v-leave
v-leave-to
两过:
v-enter-active
v-leave-active
原理:
Vue底层给他们添加类名
<template>
<div class="app">
<transition>
<Singer v-show="bool"></Singer>
</transition>
<button @click="bool = !bool">切换Singer是否出现</button>
</div>
</template>
/*
两个状态 进场(进来) 出场(出去)
进来的起点 translate 100% 0 (v-enter)
进来的终点 translate 0 0 (v-enter-to)
出去的起点 translate 0 0 (v-leave)
出去的终点 translate 100% 0 (v-leave-to)
transition:transform 2s; active (运动的过程中) 进来的active 出去的active
进来的active (v-enter-active)
出去的active (v-leave-active)
*/
默认的 transition 标签
.v-enter {
transform: translate(100%, 0);
}
.v-enter-to {
transform: translate(0, 0);
}
/* 出去 */
.v-leave {
transform: translate(0, 0);
}
.v-leave-to {
transform: translate(100%, 0);
}
/* 进来过程 */
.v-enter-active {
transition: transform 2s;
}
/* 出去过程 */
.v-leave-active {
transition: transform 2s;
}
四个点,两个过程
四点两过
2.指定name名字
相较于默认的变化,
transition上面增加了一个name属性
在样式书写当中,将v改写成name所对应的属性值
<transition name="demo">
<Singer v-show="bool"></Singer>
</transition>
/* 进来的起点和离开的终点 */
.demo-enter,
.demo-leave-to {
opacity: 0;
transform: translate(0, -100%);
}
/* 进来的终点和离开的起点 */
.demo-enter-to,
.demo-leave {
opacity: 1;
transform: translate(0, 0);
}
/* 进来过程和出去过程 */
.demo-enter-active,
.demo-leave-active {
transition: 2s;
}
3.appear属性
目的:想要出场效果就有
<!-- appear 如果dom本身就会渲染 第一次渲染就会具备过渡效果 默认不会 -->
<transition name="demo" appear>
<Singer v-show="bool" class="center"></Singer>
</transition>
4.transition-group
1.想要多个组件都拥有过渡效果
2.小要求:给每个组件中都要用key值
3.里面有个tag属性,会将渲染其属性值渲染成对应的标签
<transition-group>
<Singer v-if="bool" key="1"></Singer>
<Singer v-else class="center" key="2"></Singer>
</transition-group>
tag会把 transition-group 渲染会一个ul标签 transition标签没有
<transition-group tag="ul">
<li v-for="item in arr" :key="item">{{ item }}</li>
</transition-group>
<button @click="spliceArr">把3移除</button>
data() {
return {
bool: true,
arr: [1, 2, 3, 4, 5],
};
},
methods: {
spliceArr() {
this.arr.splice(2, 1);
},
},
5.动画的写法
就不用写四点了
<transition name="move">
<div class="box" v-show="bool"></div>
</transition>
<button @click="bool = !bool">切换Singer是否出现</button>
.box {
position: absolute;
top: 0;
left: 0;
width: 200px;
height: 200px;
border-radius: 50%;
background-color: yellowgreen;
}
.move-enter-active,
.move-leave-active {
animation: move 2s;
}
@keyframes move {
0% {
top: 0;
left: 0;
}
50% {
top: 50%;
left: 50%;
}
100% {
top: 0;
left: 0;
}
}
32.v-model用到组件
上
自定义事件是要处理很复杂的逻辑
而用v-model只是单纯的想在子组件中修改值
<AlertBox ref="alertBoxRef" v-model="data"></AlertBox>
data() {
return {
data: "",
};
},
子组件中
<button @click="fn"></button>
model:{
prop:['data'],
event:'change'
},
props:['data']
methods:{
fn(){
this.$emit('change',123)
}
}
33.vuex
1.vuex的作用:如果一个数据需要被很多数据共享,一个数据需要被很多组件修改,这个时候,我们就要用vuex,因为那个时候父子组件传值,全局总线之间传值已经满足不了我们的需求了
2.context就是一个简化版本的store
3。在actions里定义的函数都是经过了特殊的处理,有一个参数context,是一个简化版本的store
4.mutations里面的函数的参数是state
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
// Store对象 统一管理
export default new Vuex.Store({
state: {
// 数据存这里
},
mutations: {
// 真实的修改数据
},
actions: {
// 修改数据之前你要做的一些逻辑 ..
},
getters: {
},
modules: {
}
})
main.js中引入
import Vue from 'vue'
import App from './App.vue'
import store from './store'
Vue.config.productionTip = false
new Vue({
store,
render: h => h(App)
}).$mount('#app')
app中的组件
<template>
<div id="app">
<PageA> </PageA>
<hr />
<ControlBtn></ControlBtn>
<hr />
<InputAdd></InputAdd>
</div>
</template>
<script>
import PageA from "./components/PageA.vue";
import ControlBtn from "./components/ControlBtn.vue";
import InputAdd from "./components/InputAdd.vue";
export default {
name: "App",
data() {
return {};
},
components: {
PageA,
ControlBtn,
InputAdd,
},
};
</script>
list定义到state中
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
// Store对象 统一管理
export default new Vuex.Store({
state: {
// 数据存这里
list: ['周杰伦', '蔡依林', '陈奕迅']
},
mutations: {
// 真实的修改数据
},
actions: {
// 数据之前你要做的一些逻辑 ..
},
getters: {
},
modules: {
}
})
pageA需要用到数据
<template>
<div class="page-a">
<ul>
<li v-for="item in list">{{ item }}</li>
</ul>
</div>
</template>
<script>
export default {
name: "component_name",
data() {
return {
list: [],
};
},
mounted() {
this.list = this.$store.state.list;
},
};
</script>
//使用computed保持响应式
computed: {
list() {
return this.$store.state.list;
},
},
注意点:
如果要使用到数据,直接使用到computed里面定义一个函数然后返回,因为只有这样具有响应式
vuex基础总结:
需要被很多组件共享的数据放到vuex中
需要触发actions==>dispatch
需要触发mutations==>commit
this.$store.dispatch
context.commit(‘deleteListFn’, arr)
规律:
在vuex以外,一般使用this.$store
在vuex中,一般用context
34.获取state数据,actions函数,mutations函数的其他方式
1.获取state数据
mapState 帮助映射state中的数据为计算属性
所以在计算属性中使用
import { mapState } from "vuex";
在计算属性中使用
computed: {
// list() {
// return this.$store.state.list;
// },
// arr() {
// return this.$store.state.arr;
// },
// 等价于上面的写法
...mapState(["list", "arr"]),
},
第一个: 数组中的数组项 就是state中数据的名称
第二个 必须写在 computed 必须要使用 ....
对象写法
对象写法和数组写法的区别:
// listPage() {
// return this.$store.state.list;
// },
// 等价于上面的写法
// listPage 定义的计算属性 'list' state中的数据
...mapState({ listPage: "list" }),
数组写法 +> 组件内计算属性名 与 state中数据 名一致
对象写法 +> 组件内计算属性名 与 state中数据 名不一致
2.actions函数
import { mapActions } from "vuex";
使用的位置 methods中
数组写法
<button @click="deleteList">删除最后一项</button>
methods: {
// deleteList() {
// this.$store.dispatch("deleteList");
// },
//等价于上面的写法
...mapActions(["deleteList"]),
},
对象写法
methods: {
// deleteOne() {
// // 第一个参数 是actions里面定义的函数名 就可以触发actions内的函数了
// this.$store.dispatch("deleteList");
// },
// 等价于上面的写法
...mapActions({ deleteOne: "deleteList" }),
}
数组写法 +> 组件内函数名 与 actions 中函数名一致
对象写法 +> 组件内函数名 与 actions 中函数名不一致
如果要传参 那就是 deleteOne(参数) deleteList(参数)
3.mutations函数
import { mapMutations } from "vuex";
写在 methods中
数组写法
// addListFn() {
// this.$store.commit("addListFn", this.query);
// },
...mapMutations(["addListFn"]),
对象写法
// fn() {
// this.$store.commit("addListFn", this.query);
// },
// 等价于上面的写法
...mapMutations({ fn: "addListFn" }),
数组写法 +> 组件内函数名 与 Mutations 中函数名一致
对象写法 +> 组件内函数名 与 Mutations 中函数名不一致
如果要传参 那就是 addListFn(参数) fn(参数)
35.getters
拿state中的数据,跟计算属性差不多
getters的其他获取方式
写在computed
import { mapState, mapGetters } from "vuex";
<span>{{ hobby }}</span>
<span>{{ hobby }}</span>
数组写法
computed: {
hobbyFirst() {
return this.$store.getters.hobbyFirst;
},//等价于上面的写法
...mapGetters(["hobbyFirst"]),
}
对象写法
hobby() {
return this.$store.getters.hobbyFirst;
},
...mapGetters({ hobby: "hobbyFirst" }),
36.modules
分了模块之后的问题?
获取state中数据不一样了,先进入模块名,再进入变量的名称。
而且在利用map获取的时候,只能一个一个地去获取
index.js
index.js
import Vue from 'vue'
import Vuex from 'vuex'
import list from './modules/list'
import listOther from './modules/listOther'
import person from './modules/person'
import getters from './getters'
Vue.use(Vuex)
// Store对象 统一管理
export default new Vuex.Store({
getters,
modules: {
list,
listOther,
person
}
})
./modules/list.js
export default {
// 开启命名空间
namespaced: true,
state() {
return {
list: ['周杰伦', '蔡依林', '陈奕迅'],
abs: 1
}
},
actions: {
deleteList(context) {
// 假设很多的逻辑 ........
if (context.state.list.length) {
// 删除的真实操作
let arr = [...context.state.list]
arr.pop()
context.commit('deleteListFn', arr)
}
},
addList(context, value) {
if (!value) return
if (!context.state.list.includes(value)) {
context.commit('addListFn', value)
}
},
},
mutations: {
// 真实的修改数据
deleteListFn(state, value) {
state.list = value
},
// 添加数组项
addListFn(state, value) {
state.list.push(value)
},
}
}
./modules/listOther.js
export default {
// 开启命名空间
namespaced: true,
state() {
return {
listOther: ['稻花香', '爱情七十二变', '好好说话'],
}
},
actions: {},
mutations: {
reverseListOther(state) {
state.listOther = state.listOther.reverse()
},
handleListOther(state, value) {
state.listOther = state.listOther.map(item => item + value)
}
}
}
state的获取
list() {
return this.$store.state.list.list; // 第一个list模块名. 第二个list数据名
},
// 第一个参数 是 模块名 第二个 [模块中的数据名]
...mapState("list", ["list"]),
...mapState("listOther", ["listOther"]),
对象写法
...mapState("listOther", { listOther1: "listOther" }),
变化就是在 mapState函数第一个参数位置 添加模块名
在getters中获取
export default {
hobbyFirst(state) {
// state.模块名.数据名
return state.person.person.info.hobby[0] + '~'
}
}
actions,mutations的触发
deleteOne() {
// 模块名/actions中的函数名
this.$store.dispatch("list/deleteList");
},
// 模块名 , 正常接收
...mapActions("list", { deleteOne: "deleteList" }),
数组写法
...mapActions("list", ["deleteList"]),
总结 普通 就是 模块/函数名
map形式 第一个参数要填 模块名 后面参数正常填写
总结:
获取时,相对于以前,要增加模块名下面去取数据!!!
如果是要获取多个模块,写多个map就行
37.在一个模块中想要触发或访问 其他模块 中数据或函数时
1.获取其他模块中的state数据 context.rootState.数据名
2.获取其他模块中的getter rootGetters 中
3.开启root:true 才能在顶层 去寻找其他模块的actions函数 默认情况是只找当前模块中的actions函数,mutations里面是同理的!!
在listOther.js中
changeListOther(context, value) {
console.log(value);
// 获取其他模块中state中数据 cpmtext.rootState.数据名
// 获取其他模块中的getter rootGetters 中
// console.log(context);
// 开启root:true 才能在 顶层 去寻找其他模块的actions函数 默认情况是只找当前模块中的actions函数
//在context中有root可以传递
// context.dispatch('list/deleteList', null, { root: true })
// 去触发其他模块中的 mutations中的函数
context.commit('list/testFn', null, { root: true })
}
38.router(现在没怎么使用router了)
router 路由器 route路由
4版本的 vue-router和 vue2不兼容 所以一定要下载3版本
"vue-router": "^3.5.1",
"vuex": "^3.6.2"
(1)router和route
// router 路由对象 路由的跳转,回退,前进都是由路由对象进行管理的!!!
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
// 路由规则 / ==> 首页.vue /404 ==> 404.vue
const routes = [
]
// router 路由对象 路由的跳转,回退,前进都是由路由对象进行管理的!!!
const router = new VueRouter({
mode: 'history',
routes
})
export default router
//一定要记住在main.js中导入!!!
main.js
import router from './router'
new Vue({
router,
render: h => h(App)
}).$mount('#app')
(2)hash history 两种模式
hash http://localhost:8081/#/index /index
# 后面的内容 不会发送给服务器 只在前端使用
兼容性比较好
不美观
若地址通过第三方分享(手机app),如果地址校验比较严格 可能会被标记为不合法
history http://localhost:8081/index
会发送给服务器 和 后端的路由发生冲突 (后端可以处理这个误会)
兼容性比hash差一点
美观
// router 路由对象
const router = new VueRouter({
mode: 'hash', / history 直接改值可以切换
routes
})
(3)配置路由规则
1.每一条路由规则都是一个对象
2.根据路由规则匹配的组件叫路由组件 放到views文件中
3.前端的单页面展示不是跳转到某个页面,而是在app.vue中某个位置换一个路由组件展示。
const routes = [
{
path: '/home',// 路由
name: 'Home', // 路由名----可选项,一般首字母大写啊
component: Home // 路由组件
}
]
(4)路由跳转的显示位置
分为一级路由,二级路由,三级路由
显示路由的标签
点我跳转首页路由跳转的标签
a标签在vue中是很少见的
1.1-一级路由展示的位置
<template>
<div id="app">
<!-- 一级路由组件展示的位置 -->
<router-view></router-view>
<router-link to="/home">点我跳转首页</router-link>路由跳转的标签
</div>
</template>
当访问 /home路由时 就会在app中的 router-view的位置展示 对应的路由组件
(5)一般组件 路由组件的区别
一般组件
import导入到父组件中===>注册====>在模板中使用===>一般组件通常存放在components文件夹中
路由组件
在router/index.js中 导入 在路由表中 使用
访问对应路由 展示对应 路由组件
路由组件 通常存放在 views/pages 文件夹中
当访问路由组件的时候 ,生命周期会重新被执行一次
(6)$router $route
每个组件都有自己的 r o u t e 里面存储的是自己的路由信息每个组件也能访问到 route 里面存储的是自己的路由信息 每个组件也能访问到 route里面存储的是自己的路由信息每个组件也能访问到router 指向同一个router对象 可以进行路由的操作
(7)二级路由
注意点:
1.在children里面写path不带/
2.二级路由在一级路由里面写组件展示的位置
import Singer from '@/views/Singer'
{
path: '/about',// 路由
name: 'About', // 路由名
component: About, // 路由组件 /about/singer
children: [
{
// 二级路由 要在 上级路由组件中 开一个 router-view展示二级路由组件
path: 'singer', // 不带 / 不带 / 不带 /---注意这个写法
name: 'Singer',
component: Singer
}
]
}
一级路由组件中的写法
<div class="about">
<h1>关于</h1>
<router-link to="/about/singer">点我跳到singer</router-link> |
<router-link to="/about/song">点我跳到song</router-link>
<!-- about二级路由组件展示位置 -->
<router-view></router-view>
</div>
(8)路由传参
1.1 传递query参数
接受时,在
对应路由
组件中 this.route.query中 获取
http://localhost:8081/about/song/detail?id=001&name=%E6%AD%8C%E6%9B%B21
查询字符串
方式一
<router-link to="/about/song/detail?id=001&name=歌曲1">
{{item.name}}</router-link>
接收时 在对应路由组件中
this.route.query中 获取
detail中
<template>
<div class="detail">
歌曲名: {{ info.name }}
<br />
编号:{{ info.id }}
</div>
</template>
<script>
export default {
name: "Detail",
data() {
return {
info: {},
};
},
mounted() {
this.info = this.$route.query;
},
};
</script>
<style scoped></style>
可以采用对象的写法
<router-link
:to="{
path: '/about/song/detail',
query: {
id: item.id,
name: item.name,
},
}"
>{{ item.name }}</router-link>
detail中 可以完美的接收数据
(9)要写路由命名跳转,只能写在对象里面
children: [
{
path: 'detail',
name: "Detail", //这就是路由命名
component: Detail
}
]
根据路由命名进行跳转 / 只能写在 对象写法里 !!!
<router-link
:to="{
name: 'Detail',
query: {
id: item.id,
name: item.name,
},
}"
>{{ item.name }}</router-link
>
path: '/about/song/detail',
name: 'Detail', 写法相对更加简单
(10)动态路由
传递params参数
在路由表中配置
children: [
{
path: 'detail/:id/:name', // detail/001/歌曲1
name: "Detail",
component: Detail
}
]
换成 活数据
<router-link :to="`/about/song/detail/${item.id}/${item.name}`">{{
item.name
}}</router-link>
换成美观写法(常用)
<router-link
:to="{
name: 'Detail', // 不能使用path 只能用 name
params: {
id: item.id,
name: item.name,
},
}"
>{{ item.name }}</router-link
>
接收
<div class="detail">
歌曲名: {{ $route.params.name }}
<br />
编号:{{ $route.params.id }}
</div>
小tip:当用params对象写法时,只能写name。
(11)路由的props配置
第一个写法–写死了
先配置好
//第一个方法:写死了
children: [
{
path: 'detail/:id/:name', // detail/001/歌曲1
name: "Detail",
component: Detail,
props: {
id: '001',
name: '歌曲1'
}
}
]
//需要接收数据的路由组件中 可以 通过props取到对应的值
export default {
name: "Detail",
props: ["id", "name"],
};
第二个写法–接受布尔值
//第二个写法 ---接收布尔值
props: true 值为布尔值 把路由组件收到的所有的params参数 以props的形式 传给detail组件 detail就可以用props接收了 ( query参数无法识别 )
第三个写法—函数写法
//第三个写法
//函数写法 -----这种写法可以接受query模式的写法
//query的传递
//定义路由时
children: [
{
path: 'detail', // detail/001/歌曲1
name: "Detail",
component: Detail,
props($route) {
return {
id: $route.query.id,
name: $route.query.name,
}
}
}
//传递数据时
<router-link
:to="{
name: 'Detail',
query: {
id: item.id,
name: item.name,
},
}"
>{{ item.name }}</router-link
>
//接收数据时
props: ["id", "name"],
//params的传递
//定义路由时
children: [
{
path: 'detail/:id/:name', // detail/001/歌曲1
name: "Detail",
component: Detail,
props($route) {
return {
id: $route.params.id,
name: $route.params.name,
}
}
}
//传递数据时
<router-link
:to="{
name: 'Detail',
params: {
id: item.id,
name: item.name,
},
}"
>{{ item.name }}</router-link
>
//接收
props: ["id", "name"],
(12)路由懒加载
底层性能的一些优化
替换成 这种写法 component: () => import(/* webpackChunkName: “Detail” */‘@/views/Detail’),
等用到组件的时候再引入,这样性能会更优化一点!!!
替换成 这种写法 component: () => import(/* webpackChunkName: "Detail" */'@/views/Detail'),
import Vue from 'vue'
import VueRouter from 'vue-router'
// 只到文件夹 那就回去找文件夹中 index.vue
import Home from '@/views/Home'
将直接导入的方式换成懒加载的方式,底层性能的一种优化!!!
// import About from '@/views/About'
// import Singer from '@/views/Singer'
// import Song from '@/views/Song'
// import Detail from '@/views/Detail'
Vue.use(VueRouter)
const routes = [
{
path: '/home',// 路由
name: 'Home', // 路由名
component: Home // 路由组件
},
{
path: '/about',
name: 'About',
component: () => import(/* webpackChunkName: "About" */'@/views/About'), // 访问到组件才去加载
等价于()=>{
return import(```)
}
children: [
{
path: 'singer',
name: 'Singer',
component: () => import(/* webpackChunkName: "Singer" */'@/views/Singer')
},
{
path: 'song',
name: 'Song',
component: () => import(/* webpackChunkName: "Song" */'@/views/Song'),
children: [
{
path: 'detail/:id/:name',
name: "Detail",
component: () => import(/* webpackChunkName: "Detail" */'@/views/Detail'),
props($route) {
return {
id: $route.params.id,
name: $route.params.name,
}
}
}
]
}
]
}
]
// router 路由对象
const router = new VueRouter({
mode: 'history',
routes
})
export default router
(13)replace属性 和 push属性
这两种属性都是用于历史记录的!!
默认 我们在地址栏访问地址 历史记录的添加方式 是追加push的
push追加历史记录(默认)
replace 替换当前记录
<router-link to="/Home" replace>首页</router-link>
(14)命名视图–展示多个路由组件
同时(同级)展示多个视图
{
path: '/about',
name: 'About',
components: {
default: () => import(/* webpackChunkName: "About" */'@/views/About'),
PageA: () => import(/* webpackChunkName: "PageA" */'@/views/PageA')
},
展示位置
<router-view></router-view>
<router-view name="PageA"></router-view>
(15)路由的别名
目的:访问到其他的名字,也能匹配到这个路由组件
{
path: '/home',
name: 'Home',
alias: ['/home1', '/home2'], // 别名 访问 数组中的路由 也是访问 /home
component: Home
},
(16)重定向
场景:如果登录不成功,就会重新跳转,一般用于条件判断语句当中。
{
path: '/home',
name: 'Home',
alias: ['/home1', '/home2'], // 别名 访问 数组中的路由 也是访问 /home
redirect: '/', 或者
// redirect: { name: 'About' },
component: Home
},
访问 /home 在地址栏会跳到 / 跟路由
(17)编程式导航
目的:不用router-link发生路由跳转
使用this.$route.push直接跳转
this.$route.replace
this.$route.go(1)前进
this.$route.go(-1)后退
this.$route.forward()前进
this.$route.back()后退
(18)缓存路由组件
<!-- 缓存路由组件 -->
<keep-alive>
<router-view></router-view>
</keep-alive>
缓存有两个生命周期
activited
deactivited
(19)导航守卫
理解为路由跳转前和跳转后提供的钩子
所有路由跳转之前都会执行beforeEach
afterEach作为路由最后一个钩子
全局前置钩子
所有的路由跳转 之前 都会执行 beforeEach
let token = '' // 假设 空是未登录 有值似乎登录
const whitePath = ['/', '/login', '/401', '/404',]
router.beforeEach((to, from, next) => {
// to 跳转去哪个路由
// from 从哪个路由发生跳转
console.log(to);
if (whitePath.includes(to.path)) {
next()
} else {
// 判断 是否登录
// token
// 获取token
if (token) { //登录了
next()
} else {
//假设跳到login
next('/login')
}
}
// next() // 执行下一步 如果没有就会卡死在这 路由不会成功
})
全局后置钩子
router.afterEach((to, from) => {
// 作为 路由最后一个钩子 不需要next
if (to.meta.title) {
document.title = to.meta.title
}
})
独享守卫–路由规则独享的
放到路由规则中来
{
path: '/about',
name: 'About',
meta: {
title: '关于页面',
},
// 独享守卫 只对/about起作用 只有 访问 / about才会触发 独享守卫
beforeEnter(to, from, next) {
console.log(to);
next()
},
components: {
default: () => import(/* webpackChunkName: "About" */'@/views/About'),
PageA: () => import(/* webpackChunkName: "PageA" */'@/views/PageA')
},
children: [..............]
路由组件守卫
beforeRouteEnter 加载之前
beforeRouteLeave 离开路由组件是
beforeRouteUpdate 复用路由组件时
export default {
name: "Detail",
props: ["id", "name"],
// 访问路由组件时 加载之前
beforeRouteEnter(to, from, next) {
console.log("组件中的守卫 进来");
next();
},
// 离开路由组件时
beforeRouteLeave(to, from, next) {
console.log("组件中的守卫 离开");
next();
},
// 复用路由组件时
beforeRouteUpdate(to, from, next) {
console.log("组件中的复用");
next();
},
};
er-link to=“/Home” replace>首页
#### (14)命名视图--展示多个路由组件
> 同时(同级)展示多个视图
```js
{
path: '/about',
name: 'About',
components: {
default: () => import(/* webpackChunkName: "About" */'@/views/About'),
PageA: () => import(/* webpackChunkName: "PageA" */'@/views/PageA')
},
展示位置
<router-view></router-view>
<router-view name="PageA"></router-view>
(15)路由的别名
目的:访问到其他的名字,也能匹配到这个路由组件
{
path: '/home',
name: 'Home',
alias: ['/home1', '/home2'], // 别名 访问 数组中的路由 也是访问 /home
component: Home
},
(16)重定向
场景:如果登录不成功,就会重新跳转,一般用于条件判断语句当中。
{
path: '/home',
name: 'Home',
alias: ['/home1', '/home2'], // 别名 访问 数组中的路由 也是访问 /home
redirect: '/', 或者
// redirect: { name: 'About' },
component: Home
},
访问 /home 在地址栏会跳到 / 跟路由
(17)编程式导航
目的:不用router-link发生路由跳转
使用this.$route.push直接跳转
this.$route.replace
this.$route.go(1)前进
this.$route.go(-1)后退
this.$route.forward()前进
this.$route.back()后退
(18)缓存路由组件
<!-- 缓存路由组件 -->
<keep-alive>
<router-view></router-view>
</keep-alive>
缓存有两个生命周期
activited
deactivited
(19)导航守卫
理解为路由跳转前和跳转后提供的钩子
所有路由跳转之前都会执行beforeEach
afterEach作为路由最后一个钩子
全局前置钩子
所有的路由跳转 之前 都会执行 beforeEach
let token = '' // 假设 空是未登录 有值似乎登录
const whitePath = ['/', '/login', '/401', '/404',]
router.beforeEach((to, from, next) => {
// to 跳转去哪个路由
// from 从哪个路由发生跳转
console.log(to);
if (whitePath.includes(to.path)) {
next()
} else {
// 判断 是否登录
// token
// 获取token
if (token) { //登录了
next()
} else {
//假设跳到login
next('/login')
}
}
// next() // 执行下一步 如果没有就会卡死在这 路由不会成功
})
全局后置钩子
router.afterEach((to, from) => {
// 作为 路由最后一个钩子 不需要next
if (to.meta.title) {
document.title = to.meta.title
}
})
独享守卫–路由规则独享的
放到路由规则中来
{
path: '/about',
name: 'About',
meta: {
title: '关于页面',
},
// 独享守卫 只对/about起作用 只有 访问 / about才会触发 独享守卫
beforeEnter(to, from, next) {
console.log(to);
next()
},
components: {
default: () => import(/* webpackChunkName: "About" */'@/views/About'),
PageA: () => import(/* webpackChunkName: "PageA" */'@/views/PageA')
},
children: [..............]
路由组件守卫
beforeRouteEnter 加载之前
beforeRouteLeave 离开路由组件是
beforeRouteUpdate 复用路由组件时
export default {
name: "Detail",
props: ["id", "name"],
// 访问路由组件时 加载之前
beforeRouteEnter(to, from, next) {
console.log("组件中的守卫 进来");
next();
},
// 离开路由组件时
beforeRouteLeave(to, from, next) {
console.log("组件中的守卫 离开");
next();
},
// 复用路由组件时
beforeRouteUpdate(to, from, next) {
console.log("组件中的复用");
next();
},
};