Vue学习笔记(尚硅谷天禹老师)
Vue学习笔记
文章目录
- Vue学习笔记
- 1. Vue核心
- 1.1. Vue简介
- 1.1.1. 官网
- 1.1.2. 介绍与描述
- 1.1.3. Vue的特点
- 1.1.4.与其他JS框架的关联
- 1.1.5. Vue周边库
- 1.2. 初识Vue
- 1.3. 模板语法
- 1.4. 数据绑定
- 1.5. el与data的两种写法
- 1.6. MVVM模型
- 1.7. Vue中的数据代理
- 1.8. 事件处理
- 1.8.1. 事件的基本用法
- 1.8.2. 事件修饰符
- 1.8.3. 键盘事件
- 1.9. 计算属性
- 1.10. 监视属性
- 1.10.1. 监视属性基本用法
- 1.10.2. 深度监视
- 1.10.3. 监视属性简写
- 1.10.4. 监听属性 VS 计算属性
- 1.11. 绑定样式
- 1.12. 条件渲染
- 1.13. 列表渲染
- 1.13.1. 基本列表
- 1.13.2. key的作用与原理
- 1.13.3. 列表过滤
- 1.13.4. 列表排序
- 1.13.5. Vue数据监视
- 1.14. 收集表单数据
- 1.15. 过滤器
- 1.16. 内置指令
- 1.16.1. v-text指令
- 1.16.2. v-html指令
- 1.16.3. v-cloak指令
- 1.16.4. v-once指令
- 1.16.5. v-pre指令
- 1.17. 自定义指令
- 1.18. Vue生命周期
- 1.18.1. 引出生命周期
- 1.18.2. 分析生命周期
- 1.18.3. 总结生命周期
- 2. Vue组件化编程
- 2.1. 模块与组件、模块化与组件化
- 2.1.1. 模块
- 2.1.2. 组件
- 2.1.3. 模块化
- 2.1.4. 组件化
- 2.2. 非单文件组件
- 2.2.1. 基本使用
- 2.2.2. 组件注意事项
- 2.2.3. 组件的嵌套
- 2.2.4. VueComponent
- 2.2.5. 一个重要的内置关系
- 2.3. 单文件组件
- 3. 使用Vue CLI脚手架
- 3.1. 初始化脚手架
- 3.1.1. 说明
- 3.1.2. 具体步骤
- 3.1.3. 分析脚手架结构
- 3.1.4. render函数
- 3.1.5. 修改默认配置
- 3.2. ref属性
- 3.3. props配置项
- 3.4. mixin混入
- 3.5. plugin插件
- 3.6. scoped样式
- 3.7. Todo-List案例
- 3.8. WebStorage
- 3.9. 自定义事件
- 3.9.1. 绑定
- 3.9.2. 解绑
- 3.10. 全局事件总线
- 3.11. 消息的订阅与发布
- 3.12. $nextTick
- 3.13. 过渡与动画
- 4. Vue中的Ajax
- 4.1. Vue脚手架配置代理
- 4.2. GitHub用户搜索案例
- 4.3. vue-resource
- 4.4. slot插槽
- 4.4.1. 默认插槽
- 4.4.2. 具名插槽
- 4.4.3. 作用域插槽
- 5. Vuex
- 5.1. 理解Vuex
- 5.1.1. Vuex是什么
- 5.1.2. 什么时候使用Vuex
- 5.1.3. Vuex工作原理图
- 5.2. 求和案例
- 5.2.1. 使用纯vue编写
- 5.2.2. 搭建Vuex环境
- 5.2.3. 使用Vuex编写
- 5.3. getters配置项
- 5.4. 四个map方法的使用
- 5.4.1. mapState与mapGetters
- 5.4.2. mapActions与mapMutations
- 5.5. 多组件共享数据
- 5.6. 模块化+命名空间
- 6. Vue Router路由管理器
- 6.1 相关理解
- 6.1.1 vue-router的理解
- 6.1.2 对SPA应用的理解
- 6.1.3 路由的理解
- 6.2 基本路由
- 6.3. 几个注意事项
- 6.4. 多级路由
- 6.5. 路由的query参数
- 6.6. 命名路由
- 6.7. 路由的params参数
- 6.8. 路由的props配置
- 6.9. 路由跳转的replace方法
- 6.10. 编程式路由导航
- 6.11. 缓存路由组件
- 6.12. activated和deactivated
- 6.13. 路由守卫
- 6.13.1. 全局路由守卫
- 6.13.2. 独享路由守卫
- 6.13.3. 组件内路由守卫
- 6.14. 路由器的两种工作模式
- 7. Vue UI组件库
- 7.1 常用UI组件库
- 7.1.1 移动端常用UI组件库
- 7.1.2. PC端常用UI组件库
- 7.2. element-ui基本使用
- 7.3. element-ui按需引入
- 8. Vue 3
1. Vue核心
1.1. Vue简介
1.1.1. 官网
英文官网
中文官网
1.1.2. 介绍与描述
动态构建用户界面的渐进式JavaScript框架
作者:尤雨溪
1.1.3. Vue的特点
遵循MVVM模式
编码简洁,体积小,运行效率高,适合移动/PC端开发
它本身只关注UI,可以引入其它第三方库开发项目
1.1.4.与其他JS框架的关联
- 借鉴 Angular 的模板和数据绑定技术
- 借鉴 React 的组件化和虚拟DOM技术
1.1.5. Vue周边库
- vue-cli:vue脚手架
- vue-resource
- axios
- vue-router:路由
- vuex:状态管理
- element-ui:基于vue的UI组件库(PC端)
1.2. 初识Vue
<!DOCTYPE html>
<html lang="en">
<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"><title>初识vue</title><!-- 引入Vue --><script src="../js/vue.js"></script>
</head>
<body><!-- 准备好一个容器 --><div id="root"><h1>Hello!{{name}}!</h1></div><script>Vue.config.productionTip = false // 阻止vue在启动时生成生产提示new Vue({el:'#root', //el用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符串data:{ //data用于存储数据,数据共el所指定的容器去使用name:'JOJO'}})</script>
</body>
</html>
效果:
注意:
- 想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象
- root容器里的代码依然符合html规范,只不过混入了一些特殊的Vue语法
- root容器里的代码被称为Vue模板
- Vue实例与容器是一一对应的
- 真实开发中只有一个Vue实例,并且会配合着组件一起使用
{{xxx}}
中的xxx要写js表达式,且xxx可以自动读取到data中的所有属性- 一旦data中的数据发生变化,那么模板中用到该数据的地方也会自动更新
1.3. 模板语法
<!DOCTYPE html>
<html lang="en">
<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"><title>vue模板语法</title><script src="../js/vue.js"></script>
</head>
<body><div id="root"><h1>插值语法</h1><h3>你好,{{name}}!</h3><hr><h1>指令语法</h1><a v-bind:href="url">快去看新番!</a><br><a :href="url">快去看新番!</a></div><script>Vue.config.productionTip = false new Vue({el:'#root', data:{ name:'JOJO',url:'https://www.bilibili.com/'}})</script>
</body>
</html>
效果:
总结:
Vue模板语法包括两大类:
插值语法:
- 功能:用于解析标签体内容
- 写法:
{{xxx}}
,xxx是js表达式,且可以直接读取到data中的所有区域
指令语法:
- 功能:用于解析标签(包括:标签属性、标签体内容、绑定事件…)
- 举例:
<a v-bind:href="xxx">
或简写为<a :href="xxx">
,xxx同样要写js表达式,且可以直接读取到data中的所有属性 - 备注:Vue中有很多的指令,且形式都是
v-???
,此处我们只是拿v-bind
举个例子
1.4. 数据绑定
<!DOCTYPE html>
<html lang="en">
<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"><title>数据绑定</title><script src="../js/vue.js"></script>
</head>
<body><div id="root">单向数据绑定:<input type="text" v-bind:value="name"><br>双向数据绑定:<input type="text" v-model:value="name"></div><script>Vue.config.productionTip = false new Vue({el:'#root', data:{name:'JOJO'}})</script>
</body>
</html>
效果:
总结:
Vue中有2种数据绑定的方式:
- 单向绑定(
v-bind
):数据只能从data流向页面 - 双向绑定(
v-model
):数据不仅能从data流向页面,还可以从页面流向data
- 单向绑定(
备注:
- 双向绑定一般都应用在表单类元素上(如:
<input>
、<select>
、<textarea>
等) v-model:value
可以简写为v-model
,因为v-model
默认收集的就是value值
- 双向绑定一般都应用在表单类元素上(如:
1.5. el与data的两种写法
<!DOCTYPE html>
<html lang="en">
<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"><title>el与data的两种写法</title><script src="../js/vue.js"></script>
</head>
<body><div id="root"><h1>Hello,{{name}}!</h1></div><script>Vue.config.productionTip = false //el的两种写法:// const vm = new Vue({// // el:'#root', //第一种写法// data:{// name:'JOJO'// }// })// vm.$mount('#root')//第二种写法//data的两种写法:new Vue({el:'#root', //data的第一种写法:对象式// data:{// name:'JOJO'// }//data的第二种写法:函数式data(){return{name:'JOJO'}}})</script>
</body>
</html>
总结:
el有2种写法:
- 创建Vue实例对象的时候配置el属性
- 先创建Vue实例,随后再通过
vm.$mount('#root')
指定el的值
data有2种写法:
- 对象式
- 函数式
- 如何选择:目前哪种写法都可以,以后学到组件时,data必须使用函数,否则会报错
由Vue管理的函数,一定不要写箭头函数,否则this就不再是Vue实例了
1.6. MVVM模型
- MVVM模型:
- M:模型(Model),data中的数据
- V:视图(View),模板代码
- VM:视图模型(ViewModel),Vue实例
<!DOCTYPE html>
<html lang="en">
<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"><title>mvvm</title><script src="../js/vue.js"></script>
</head>
<body><div id="root"><h2>名称:{{name}}</h2><h2>战队:{{rank}}</h2><h2>测试:{{$options}}</h2></div><script>Vue.config.productionTip = falsenew Vue({el:'#root',data:{ name:'uzi',rank:'RNG'}})</script>
</body>
</html>
效果:
总结:
- data中所有的属性,最后都出现在了vm身上
- vm身上所有的属性 及 Vue原型身上所有的属性,在Vue模板中都可以直接使用
1.7. Vue中的数据代理
总结:
- Vue中的数据代理通过vm对象来代理data对象中属性的操作(读/写)
- Vue中数据代理的好处:更加方便的操作data中的数据
- 基本原理:
- 通过
object.defineProperty()
把data对象中所有属性添加到vm上。 - 为每一个添加到vm上的属性,都指定一个getter/setter。
- 在getter/setter内部去操作(读/写)data中对应的属性。
- 通过
1.8. 事件处理
1.8.1. 事件的基本用法
<!DOCTYPE html>
<html lang="en">
<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"><title>事件的基本用法</title><script src="../js/vue.js"></script>
</head>
<body><div id="root"><h2>hello,{{name}}</h2><button v-on:click="showInfo1">点我提示信息1</button><button @click="showInfo2($event,66)">点我提示信息2</button></div><script>Vue.config.productionTip = false new Vue({el:'#root', data:{name:'JOJO'},methods:{showInfo1(event){console.log(event)},showInfo2(evnet,num){console.log(event,num)}}})</script>
</body>
</html>
效果:
总结:
- 使用
v-on:xxx
或@xxx
绑定事件,其中xxx是事件名 - 事件的回调需要配置在methods对象中,最终会在vm上
- methods中配置的函数,==不要用箭头函数!==否则this就不是vm了
- methods中配置的函数,都是被Vue所管理的函数,this的指向是vm或组件实例对象
@click="demo
和@click="demo($event)"
效果一致,但后者可以传参
1.8.2. 事件修饰符
<!DOCTYPE html>
<html><head><meta charset="UTF-8" /><title>事件修饰符</title><script type="text/javascript" src="../js/vue.js"></script><style>*{margin-top: 20px;}.demo1{height: 50px;background-color: skyblue;}.box1{padding: 5px;background-color: skyblue;}.box2{padding: 5px;background-color: orange;}.list{width: 200px;height: 200px;background-color: peru;overflow: auto;}li{height: 100px;}</style></head><body><div id="root"><h2>欢迎来到{{name}}学习</h2><!-- 阻止默认事件 --><a href="http://www.atguigu.com" @click.prevent="showInfo">点我提示信息</a><!-- 阻止事件冒泡 --><div class="demo1" @click="showInfo"><button @click.stop="showInfo">点我提示信息</button></div><!-- 事件只触发一次 --><button @click.once="showInfo">点我提示信息</button><!-- 使用事件的捕获模式 --><div class="box1" @click.capture="showMsg(1)">div1<div class="box2" @click="showMsg(2)">div2</div></div><!-- 只有event.target是当前操作的元素时才触发事件 --><div class="demo1" @click.self="showInfo"><button @click="showInfo">点我提示信息</button></div><!-- 事件的默认行为立即执行,无需等待事件回调执行完毕 --><ul @wheel.passive="demo" class="list"><li>1</li><li>2</li><li>3</li><li>4</li></ul></div></body><script type="text/javascript">Vue.config.productionTip = falsenew Vue({el:'#root',data:{name:'尚硅谷'},methods:{showInfo(e){alert('同学你好!')},showMsg(msg){console.log(msg)},demo(){for (let i = 0; i < 100000; i++) {console.log('#')}console.log('累坏了')}}})</script>
</html>
效果:
总结:
Vue中的事件修饰符:
- prevent:阻止默认事件(常用)
- stop:阻止事件冒泡(常用)
- once:事件只触发一次(常用)
- capture:使用事件的捕获模式
- self:只有
event.target
是当前操作的元素时才触发事件 - passive:事件的默认行为立即执行,无需等待事件回调执行完毕
修饰符可以连续写,比如可以这么用:
@click.prevent.stop="showInfo"
1.8.3. 键盘事件
<!DOCTYPE html>
<html><head><meta charset="UTF-8" /><title>键盘事件</title><script type="text/javascript" src="../js/vue.js"></script></head><body><div id="root"><h2>欢迎来到{{name}}学习</h2><input type="text" placeholder="按下回车提示输入" @keydown.enter="showInfo"></div></body><script type="text/javascript">Vue.config.productionTip = falsenew Vue({el:'#root',data:{name:'尚硅谷'},methods: {showInfo(e){console.log(e.target.value)}},})</script>
</html>
效果:
总结:
键盘上的每个按键都有自己的名称和编码,例如:Enter(13)。而Vue还对一些常用按键起了别名方便使用
Vue中常用的按键别名:
- 回车:enter
- 删除:delete (捕获“删除”和“退格”键)
- 退出:esc
- 空格:space
- 换行:tab (特殊,必须配合keydown去使用)
- 上:up
- 下:down
- 左:left
- 右:right
注意:
系统修饰键(用法特殊):ctrl、alt、shift、meta
- 配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发
- 配合keydown使用:正常触发事件
可以使用keyCode去指定具体的按键,比如:
@keydown.13="showInfo"
,但不推荐这样使用Vue.config.keyCodes.自定义键名 = 键码
,可以自定义按键别名
1.9. 计算属性
<!DOCTYPE html>
<html lang="en">
<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"><title>计算属性</title><script src="../js/vue.js"></script>
</head>
<body><div id="root">姓:<input type="text" v-model="firstName"><br><br>名:<input type="text" v-model="lastName"><br><br>姓名:<span>{{fullName}}</span></div><script>Vue.config.productionTip = false new Vue({el:'#root', data:{ firstName:'张',lastName:'三'},computed:{fullName:{get(){return this.firstName + '-' + this.lastName},set(value){const arr = value.split('-')this.firstName = arr[0]this.lastName = arr[1]}}}})</script>
</body>
</html>
效果:
总结:
计算属性:
定义:要用的属性不存在,需要通过已有属性计算得来。
原理:底层借助了
Objcet.defineproperty()
方法提供的getter和setter。get函数什么时候执行?
- 初次读取时会执行一次
- 当依赖的数据发生改变时会被再次调用
优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便
备注:
- 计算属性最终会出现在vm上,直接读取使用即可
- 如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生改变
- 如果计算属性确定不考虑修改,可以使用计算属性的简写形式
new Vue({el:'#root', data:{ firstName:'张',lastName:'三'},computed:{fullName(){return this.firstName + '-' + this.lastName}}
})
1.10. 监视属性
1.10.1. 监视属性基本用法
<!DOCTYPE html>
<html lang="en">
<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"><title>监视属性</title><script src="../js/vue.js"></script>
</head>
<body><div id="root"><h2>今天天气好{{info}}!</h2><button @click="changeWeather">点击切换天气</button></div><script>Vue.config.productionTip = false new Vue({el:'#root', data:{ isHot:true,},computed:{info(){return this.isHot ? '炎热' : '凉爽' }},methods:{changeWeather(){this.isHot = !this.isHot}},watch:{isHot:{immediate:true, //初始化时让handler调用一下//handler什么时候调用?当isHot发生改变时handler(newValue,oldValue){console.log('isHot被修改了',newValue,oldValue)}}}})</script>
</body>
</html>
效果:
总结:
监视属性watch:
- 当被监视的属性变化时,回调函数自动调用,进行相关操作
- 监视的属性必须存在,才能进行监视
- 监视有两种写法:
- 创建Vue时传入watch配置
- 通过
vm.$watch
监视
vm.$watch('isHot',{immediate:true,handler(newValue,oldValue){console.log('isHot被修改了',newValue,oldValue)}
})
1.10.2. 深度监视
<!DOCTYPE html>
<html lang="en">
<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"><title>深度监视</title><script src="../js/vue.js"></script>
</head>
<body><div id="root"><h3>a的值是:{{numbers.a}}</h3><button @click="numbers.a++">点我让a+1</button><h3>b的值是:{{numbers.b}}</h3><button @click="numbers.b++">点我让b+1</button></div><script>Vue.config.productionTip = false new Vue({el:'#root', data:{ isHot:true,numbers:{a:1,b:1,}},watch:{//监视多级结构中所有属性的变化numbers:{deep:true,handler(){console.log('numbers改变了')}}//监视多级结构中某个属性的变化/* 'numbers.a':{handler(){console.log('a被改变了')}} */}})</script>
</body>
</html>
效果:
总结:
深度监视:
- Vue中的watch默认不监测对象内部值的改变(一层)
- 在watch中配置
deep:true
可以监测对象内部值的改变(多层)
备注:
- Vue自身可以监测对象内部值的改变,但Vue提供的watch默认不可以
- 使用watch时根据监视数据的具体结构,决定是否采用深度监视
1.10.3. 监视属性简写
如果监视属性除了handler没有其他配置项的话,可以进行简写。
<script type="text/javascript">Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。const vm = new Vue({el:'#root',data:{isHot:true,},computed:{info(){return this.isHot ? '炎热' : '凉爽'}},methods: {changeWeather(){this.isHot = !this.isHot}},watch:{//正常写法isHot:{handler(newValue,oldValue){console.log('isHot被修改了',newValue,oldValue)}}, //简写isHot(newValue,oldValue){console.log('isHot被修改了',newValue,oldValue,this)}}})//正常写法vm.$watch('isHot',{handler(newValue,oldValue){console.log('isHot被修改了',newValue,oldValue)}})//简写vm.$watch('isHot',function(newValue,oldValue){console.log('isHot被修改了',newValue,oldValue,this)})
</script>
1.10.4. 监听属性 VS 计算属性
使用计算属性:
new Vue({el:'#root', data:{ firstName:'张',lastName:'三'},computed:{fullName(){return this.firstName + '-' + this.lastName}}
})
使用监听属性:
new Vue({el:'#root',data:{firstName:'张',lastName:'三',fullName:'张-三'},watch:{firstName(val){setTimeout(()=>{this.fullName = val + '-' + this.lastName},1000);},lastName(val){this.fullName = this.firstName + '-' + val}}
})
总结:
computed和watch之间的区别:
- computed能完成的功能,watch都可以完成
- watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作
两个重要的小原则:
- 所有被Vue管理的函数,最好写成普通函数,这样this的指向才是vm 或 组件实例对象
- 所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数),最好写成箭头函数,这样this的指向才是vm 或 组件实例对象。
1.11. 绑定样式
<style>.basic{width: 400px;height: 100px;border: 1px solid black;}.happy{border: 4px solid red;;background-color: rgba(255, 255, 0, 0.644);background: linear-gradient(30deg,yellow,pink,orange,yellow);}.sad{border: 4px dashed rgb(2, 197, 2);background-color: gray;}.normal{background-color: skyblue;}.atguigu1{background-color: yellowgreen;}.atguigu2{font-size: 30px;text-shadow:2px 2px 10px red;}.atguigu3{border-radius: 20px;}
</style>
<div id="root"><!-- 绑定class样式--字符串写法,适用于:样式的类名不确定,需要动态指定 --><div class="basic" :class="mood" @click="changeMood">{{name}}</div> <br/><br/><!-- 绑定class样式--数组写法,适用于:要绑定的样式个数不确定、名字也不确定 --><div class="basic" :class="classArr">{{name}}</div> <br/><br/><!-- 绑定class样式--对象写法,适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用 --><div class="basic" :class="classObj">{{name}}</div> <br/><br/><!-- 绑定style样式--对象写法 --><div class="basic" :style="styleObj">{{name}}</div> <br/><br/><!-- 绑定style样式--数组写法 --><div class="basic" :style="styleArr">{{name}}</div>
</div>
<script type="text/javascript">Vue.config.productionTip = falseconst vm = new Vue({el:'#root',data:{name:'尚硅谷',mood:'normal',classArr:['atguigu1','atguigu2','atguigu3'],classObj:{atguigu1:false,atguigu2:false,},styleObj:{fontSize: '40px',color:'red',},styleObj2:{backgroundColor:'orange'},styleArr:[{fontSize: '40px',color:'blue',},{backgroundColor:'gray'}]},methods: {changeMood(){const arr = ['happy','sad','normal']const index = Math.floor(Math.random()*3)this.mood = arr[index]}},})
</script>
效果:
总结:
class样式:
写法:
class="xxx"
,xxx可以是字符串、对象、数组字符串写法适用于:类名不确定,要动态获取
对象写法适用于:要绑定多个样式,个数不确定,名字也不确定
数组写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用
style样式:
:style="{fontSize: xxx}"
其中xxx是动态值:style="[a,b]"
其中a、b是样式对象
1.12. 条件渲染
<!DOCTYPE html>
<html><head><meta charset="UTF-8" /><title>条件渲染</title><script type="text/javascript" src="../js/vue.js"></script></head><body><div id="root"><h2>当前的n值是:{{n}}</h2><button @click="n++">点我n+1</button><h2 v-show="true">Hello,{{name}}!</h2><div v-if="n === 1">Angular</div><div v-else-if="n === 2">React</div><div v-else>Vue</div></div></body><script type="text/javascript">Vue.config.productionTip = falseconst vm = new Vue({el:'#root',data:{name:'jojo',n:0}})</script>
</html>
效果:
总结:
v-if:
写法:
v-if="表达式"
v-else-if="表达式"
v-else
适用于:切换频率较低的场景
特点:不展示的DOM元素直接被移除
注意:
v-if
可以和v-else-if
、v-else
一起使用,但要求结构不能被打断
v-show:
- 写法:
v-show="表达式"
- 适用于:切换频率较高的场景
- 特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉
- 写法:
使用
v-if
的时,元素可能无法获取到,而使用v-show
一定可以获取到
1.13. 列表渲染
1.13.1. 基本列表
<!DOCTYPE html>
<html><head><meta charset="UTF-8" /><title>基本列表</title><script type="text/javascript" src="../js/vue.js"></script></head><body><div id="root"><h2>人员列表(遍历数组)</h2><ul><li v-for="(p,index) in persons" :key="index">{{p.name}}-{{p.age}}</li></ul><h2>汽车信息(遍历对象)</h2><ul><li v-for="(value,k) in car" :key="k">{{k}}-{{value}}</li></ul><h2>遍历字符串</h2><ul><li v-for="(char,index) in str" :key="index">{{char}}-{{index}}</li></ul><h2>遍历指定次数</h2><ul><li v-for="(number,index) in 5" :key="index">{{index}}-{{number}}</li></ul></div><script type="text/javascript">Vue.config.productionTip = falsenew Vue({el:'#root',data:{persons:[{id:'001',name:'张三',age:18},{id:'002',name:'李四',age:19},{id:'003',name:'王五',age:20}],car:{name:'奥迪A8',price:'70万',color:'黑色'},str:'hello'}})</script></body>
</html>
效果:
总结:
v-for
指令:
- 用于展示列表数据
- 语法:
<li v-for="(item, index) in xxx" :key="yyy">
,其中key可以是index,也可以是遍历对象的唯一标识 - 可遍历:数组、对象、字符串(用的少)、指定次数(用的少)
1.13.2. key的作用与原理
<!DOCTYPE html>
<html><head><meta charset="UTF-8" /><title>key的原理</title><script type="text/javascript" src="../js/vue.js"></script></head><body><div id="root"><h2>人员列表</h2><button @click.once="add">添加老刘</button><ul><li v-for="(p,index) in persons" :key="index">{{p.name}} - {{p.age}}<input type="text"></li></ul></div><script type="text/javascript">Vue.config.productionTip = falsenew Vue({el:'#root',data:{persons:[{id:'001',name:'张三',age:18},{id:'002',name:'李四',age:19},{id:'003',name:'王五',age:20}]},methods: {add(){const p = {id:'004',name:'老刘',age:40}this.persons.unshift(p)}},})</script>
</html>
效果:
原理:
面试题:react、vue中的key有什么作用?(key的内部原理)
虚拟DOM中key的作用:key是虚拟DOM中对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】,随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:
对比规则:
旧虚拟DOM中找到了与新虚拟DOM相同的key:
- 若虚拟DOM中内容没变, 直接使用之前的真实DOM
- 若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM
旧虚拟DOM中未找到与新虚拟DOM相同的key:创建新的真实DOM,随后渲染到到页面
用index作为key可能会引发的问题:
- 若对数据进行逆序添加、逆序删除等破坏顺序操作:会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低
- 若结构中还包含输入类的DOM:会产生错误DOM更新 ==> 界面有问题
开发中如何选择key?
- 最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等唯一值
- 如果不存在对数据的逆序添加、逆序删除等破坏顺序的操作,仅用于渲染列表,使用index作为key是没有问题的
1.13.3. 列表过滤
<!DOCTYPE html>
<html><head><meta charset="UTF-8" /><title>列表过滤</title><script type="text/javascript" src="../js/vue.js"></script></head><body><div id="root"><h2>人员列表</h2><input type="text" placeholder="请输入名字" v-model="keyWord"><ul><li v-for="(p,index) of filPersons" :key="index">{{p.name}}-{{p.age}}-{{p.sex}}</li></ul></div><script type="text/javascript">Vue.config.productionTip = falsenew Vue({el:'#root',data:{keyWord:'',persons:[{id:'001',name:'马冬梅',age:19,sex:'女'},{id:'002',name:'周冬雨',age:20,sex:'女'},{id:'003',name:'周杰伦',age:21,sex:'男'},{id:'004',name:'温兆伦',age:22,sex:'男'}]},computed:{filPersons(){return this.persons.filter((p)=>{return p.name.indexOf(this.keyWord) !== -1})}}})</script></body>
</html>
效果:
1.13.4. 列表排序
<body><div id="root"><h2>人员列表</h2><input type="text" placeholder="请输入名字" v-model="keyWord"><button @click="sortType = 2">年龄升序</button><button @click="sortType = 1">年龄降序</button><button @click="sortType = 0">原顺序</button><ul><li v-for="(p,index) of filPersons" :key="p.id">{{p.name}}-{{p.age}}-{{p.sex}}</li></ul></div><script>new Vue({el:'#root',data:{persons:[{id:'001',name:'马冬梅',age:30,sex:'女'},{id:'002',name:'周冬雨',age:45,sex:'女'},{id:'003',name:'周杰伦',age:21,sex:'男'},{id:'004',name:'温兆伦',age:22,sex:'男'}],keyWord:'',sortType:0,//0代表原顺序,1代表升序,3代表降序},computed:{filPersons(){const arr = this.persons.filter((p)=>{return p.name.indexOf(this.keyWord) !== -1})if(this.sortType){arr.sort((p1, p2)=>{return this.sortType ===1 ? p2.age-p1.age : p1.age-p2.age})}return arr}}})</script>
</body>
效果:
1.13.5. Vue数据监视
<!DOCTYPE html>
<html><head><meta charset="UTF-8" /><title>Vue数据监视</title><style>button{margin-top: 10px;}</style><script type="text/javascript" src="../js/vue.js"></script></head><body><div id="root"><h1>学生信息</h1><button @click="student.age++">年龄+1岁</button><br/><button @click="addSex">添加性别属性,默认值:男</button> <br/><button @click="addFriend">在列表首位添加一个朋友</button> <br/><button @click="updateFirstFriendName">修改第一个朋友的名字为:张三</button><br/><button @click="addHobby">添加一个爱好</button> <br/><button @click="updateHobby">修改第一个爱好为:开车</button><br/><button @click="removeSmoke">过滤掉爱好中的抽烟</button> <br/><h3>姓名:{{student.name}}</h3><h3>年龄:{{student.age}}</h3><h3 v-if="student.sex">性别:{{student.sex}}</h3><h3>爱好:</h3><ul><li v-for="(h,index) in student.hobby" :key="index">{{h}}</li></ul><h3>朋友们:</h3><ul><li v-for="(f,index) in student.friends" :key="index">{{f.name}}--{{f.age}}</li></ul></div></body><script type="text/javascript">Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。const vm = new Vue({el:'#root',data:{student:{name:'tom',age:18,hobby:['抽烟','喝酒','烫头'],friends:[{name:'jerry',age:35},{name:'tony',age:36}]}},methods: {addSex(){//Vue.set(this.student,'sex','男')this.$set(this.student,'sex','男')},addFriend(){this.student.friends.unshift({name:'jack',age:70})},updateFirstFriendName(){this.student.friends[0].name = '张三'},addHobby(){this.student.hobby.push('学习')},updateHobby(){this.student.hobby.splice(0,1,'开车')},removeSmoke(){this.student.hobby = this.student.hobby.filter((h)=>{return h !== '抽烟'})}}})</script>
</html>
效果:
总结:
Vue监视数据的原理:
vue会监视data中所有层次的数据
如何监测对象中的数据?
通过setter实现监视,且要在
new Vue
时就传入要监测的数据- 对象中后追加的属性,Vue默认不做响应式处理
- 如需给后添加的属性做响应式,请使用如下API:
Vue.set(target,propertyName/index,value)
vm.$set(target,propertyName/index,value)
如何监测数组中的数据?
通过包裹数组更新元素的方法实现,本质就是做了两件事:
- 调用原生对应的方法对数组进行更新
- 重新解析模板,进而更新页面
在Vue修改数组中的某个元素一定要用如下方法:
- 使用这些API:
push()
、pop()
、shift()
、unshift()
、splice()
、sort()
、reverse()
Vue.set()
或vm.$set()
- 使用这些API:
特别注意:Vue.set()
和 vm.$set()
不能给vm 或 vm的根数据对象(data等) 添加属性
1.14. 收集表单数据
<!DOCTYPE html>
<html><head><meta charset="UTF-8" /><title>收集表单数据</title><script type="text/javascript" src="../js/vue.js"></script></head><body><div id="root"><form @submit.prevent="demo">账号:<input type="text" v-model.trim="userInfo.account"> <br/><br/>密码:<input type="password" v-model="userInfo.password"> <br/><br/>年龄:<input type="number" v-model.number="userInfo.age"> <br/><br/>性别:男<input type="radio" name="sex" v-model="userInfo.sex" value="male">女<input type="radio" name="sex" v-model="userInfo.sex" value="female"> <br/><br/>爱好:学习<input type="checkbox" v-model="userInfo.hobby" value="study">打游戏<input type="checkbox" v-model="userInfo.hobby" value="game">吃饭<input type="checkbox" v-model="userInfo.hobby" value="eat"><br/><br/>所属校区:<select v-model="userInfo.city"><option value="">请选择校区</option><option value="beijing">北京</option><option value="shanghai">上海</option><option value="shenzhen">深圳</option><option value="wuhan">武汉</option></select><br/><br/>其他信息:<textarea v-model.lazy="userInfo.other"></textarea> <br/><br/><input type="checkbox" v-model="userInfo.agree">阅读并接受<a href="http://www.atguigu.com">《用户协议》</a><button>提交</button></form></div></body><script type="text/javascript">Vue.config.productionTip = falsenew Vue({el:'#root',data:{userInfo:{account:'',password:'',age:0,sex:'female',hobby:[],city:'beijing',other:'',agree:''}},methods: {demo(){console.log(JSON.stringify(this.userInfo))}}})</script>
</html>
效果:
总结:
收集表单数据:
- 若:
<input type="text"/>
,则v-model
收集的是value值,用户输入的内容就是value值 - 若:
<input type="radio"/>
,则v-model
收集的是value值,且要给标签配置value属性 - 若:
<input type="checkbox"/>
- 没有配置value属性,那么收集的是checked属性(勾选 or 未勾选,是布尔值)
- 配置了value属性:
v-model
的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值)v-model
的初始值是数组,那么收集的就是value组成的数组
v-model
的三个修饰符:
- lazy:失去焦点后再收集数据
- number:输入字符串转为有效的数字
- trim:输入首尾空格过滤
1.15. 过滤器
<!DOCTYPE html>
<html><head><meta charset="UTF-8" /><title>过滤器</title><script type="text/javascript" src="../js/vue.js"></script><script src="https://cdn.bootcdn.net/ajax/libs/dayjs/1.10.6/dayjs.min.js"></script></head><body><div id="root"><h2>时间</h2><h3>当前时间戳:{{time}}</h3><h3>转换后时间:{{time | timeFormater()}}</h3><h3>转换后时间:{{time | timeFormater('YYYY-MM-DD HH:mm:ss')}}</h3><h3>截取年月日:{{time | timeFormater() | mySlice}}</h3></div></body><script type="text/javascript">Vue.config.productionTip = false//全局过滤器Vue.filter('mySlice',function(value){return value.slice(0,11)})new Vue({el:'#root',data:{time:1626750147900,},//局部过滤器filters:{timeFormater(value, str="YYYY年MM月DD日 HH:mm:ss"){return dayjs(value).format(str)}}})</script>
</html>
效果:
总结:
过滤器:
定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)。
语法:
注册过滤器:
Vue.filter(name,callback)
或new Vue{filters:{}}
使用过滤器:
{{ xxx | 过滤器名}}
或v-bind:属性 = "xxx | 过滤器名"
备注:
- 过滤器可以接收额外参数,多个过滤器也可以串联
- 并没有改变原本的数据,而是产生新的对应的数据
1.16. 内置指令
1.16.1. v-text指令
<!DOCTYPE html>
<html><head><meta charset="UTF-8" /><title>v-text指令</title><script type="text/javascript" src="../js/vue.js"></script></head><body><div id="root"><div>你好,{{name}}</div><div v-text="name"></div><div v-text="str"></div></div></body><script type="text/javascript">Vue.config.productionTip = false new Vue({el:'#root',data:{name:'JOJO',str:'<h3>你好啊!</h3>'}})</script>
</html>
效果:
总结:
之前学过的指令:
v-bind
:单向绑定解析表达式,可简写为:
v-model
:双向数据绑定v-for
:遍历数组 / 对象 / 字符串v-on
:绑定事件监听,可简写为@
v-if
:条件渲染(动态控制节点是否存存在)v-else
:条件渲染(动态控制节点是否存存在)v-show
:条件渲染 (动态控制节点是否展示)
v-text
指令:作用:向其所在的节点中渲染文本内容
与插值语法的区别:
v-text
会替换掉节点中的内容,{{xx}}
则不会。
1.16.2. v-html指令
<!DOCTYPE html>
<html><head><meta charset="UTF-8" /><title>v-html指令</title><script type="text/javascript" src="../js/vue.js"></script></head><body><div id="root"><div>Hello,{{name}}</div><div v-html="str"></div><div v-html="str2"></div></div></body><script type="text/javascript">Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。new Vue({el:'#root',data:{name:'JOJO',str:'<h3>你好啊!</h3>',str2:'<a href=javascript:location.href="http://www.baidu.com?"+document.cookie>兄弟我找到你想要的资源了,快来!</a>',}})</script>
</html>
效果:
总结:
v-html
指令:
作用:向指定节点中渲染包含html结构的内容
与插值语法的区别:
v-html
会替换掉节点中所有的内容,{{xx}}
则不会v-html
可以识别html结构
严重注意:
v-html
有安全性问题!!!- 在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击
- 一定要在可信的内容上使用
v-html
,永远不要用在用户提交的内容上!!!
1.16.3. v-cloak指令
<!DOCTYPE html>
<html><head><meta charset="UTF-8" /><title>v-cloak指令</title><style>[v-cloak]{display:none;}</style></head><body><div id="root"><h2 v-cloak>{{name}}</h2></div><script type="text/javascript" src="../js/vue.js"></script></body><script type="text/javascript">Vue.config.productionTip = falsenew Vue({el:'#root',data:{name:'尚硅谷'}})</script>
</html>
效果:
总结:
v-cloak
指令(没有值):
- 本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉
v-cloak
属性 - 使用css配合
v-cloak
可以解决网速慢时页面展示出{{xxx}}
的问题
1.16.4. v-once指令
<!DOCTYPE html>
<html><head><meta charset="UTF-8" /><title>v-once指令</title><script type="text/javascript" src="../js/vue.js"></script></head><body><div id="root"><h2 v-once>n初始化的值是:{{n}}</h2><h2>n现在的值是:{{n}}</h2><button @click="n++">点我n+1</button></div></body><script type="text/javascript">Vue.config.productionTip = false new Vue({el:'#root',data:{n:1}})</script>
</html>
效果:
总结:
v-once
指令:
v-once
所在节点在初次动态渲染后,就视为静态内容了以后数据的改变不会引起
v-once
所在结构的更新,可以用于优化性能
1.16.5. v-pre指令
<!DOCTYPE html>
<html><head><meta charset="UTF-8" /><title>v-pre指令</title><script type="text/javascript" src="../js/vue.js"></script></head><body><div id="root"><h2 v-pre>Vue其实很简单</h2><h2>当前的n值是:{{n}}</h2><button @click="n++">点我n+1</button></div></body><script type="text/javascript">Vue.config.productionTip = falsenew Vue({el:'#root',data:{n:1}})</script>
</html>
效果:
总结:
v-pre
指令:
- 跳过其所在节点的编译过程。
- 可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译
1.17. 自定义指令
<!DOCTYPE html>
<html><head><meta charset="UTF-8" /><title>自定义指令</title><script type="text/javascript" src="../js/vue.js"></script></head><!-- 需求1:定义一个v-big指令,和v-text功能类似,但会把绑定的数值放大10倍。需求2:定义一个v-fbind指令,和v-bind功能类似,但可以让其所绑定的input元素默认获取焦点。--><body><div id="root"><h2>当前的n值是:<span v-text="n"></span> </h2><h2>放大10倍后的n值是:<span v-big="n"></span> </h2><button @click="n++">点我n+1</button><hr/><input type="text" v-fbind:value="n"></div></body><script type="text/javascript">Vue.config.productionTip = falsenew Vue({el:'#root',data:{n:1},directives:{//big函数何时会被调用?1.指令与元素成功绑定时(一上来) 2.指令所在的模板被重新解析时big(element,binding){console.log('big',this) //注意此处的this是windowelement.innerText = binding.value * 10},fbind:{//指令与元素成功绑定时(一上来)bind(element,binding){element.value = binding.value},//指令所在元素被插入页面时inserted(element,binding){element.focus()},//指令所在的模板被重新解析时update(element,binding){element.value = binding.value}}}})</script>
</html>
效果:
总结:
自定义指令定义语法:
局部指令:
new Vue({ directives:{指令名:配置对象} })
new Vue({ directives:{指令名:回调函数} })
全局指令:
Vue.directive(指令名,配置对象)
Vue.directive(指令名,回调函数)
例如:
Vue.directive('fbind',{//指令与元素成功绑定时(一上来)bind(element,binding){element.value = binding.value},//指令所在元素被插入页面时inserted(element,binding){element.focus()},//指令所在的模板被重新解析时update(element,binding){element.value = binding.value} })
配置对象中常用的3个回调函数:
bind(element,binding)
:指令与元素成功绑定时调用inserted(element,binding)
:指令所在元素被插入页面时调用update(element,binding)
:指令所在模板结构被重新解析时调用
备注:
指令定义时不加“v-”,但使用时要加“v-”
指令名如果是多个单词,要使用kebab-case命名方式,不要用camelCase命名
new Vue({el:'#root',data:{n:1},directives:{'big-number'(element,binding){element.innerText = binding.value * 10}} })
1.18. Vue生命周期
1.18.1. 引出生命周期
<!DOCTYPE html>
<html><head><meta charset="UTF-8" /><title>引出生命周期</title><script type="text/javascript" src="../js/vue.js"></script></head><body><div id="root"><h2 v-if="a">你好啊</h2><h2 :style="{opacity}">欢迎学习Vue</h2></div></body><script type="text/javascript">Vue.config.productionTip = false new Vue({el:'#root',data:{a:false,opacity:1},mounted(){console.log('mounted',this)setInterval(() => {this.opacity -= 0.01if(this.opacity <= 0) this.opacity = 1},16)},})</script>
</html>
效果:
总结:
生命周期:
- 又名:生命周期回调函数、生命周期函数、生命周期钩子
- 是什么:Vue在关键时刻帮我们调用的一些特殊名称的函数
- 生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的
- 生命周期函数中的this指向是vm 或 组件实例对象
1.18.2. 分析生命周期
<!DOCTYPE html>
<html><head><meta charset="UTF-8" /><title>分析生命周期</title><script type="text/javascript" src="../js/vue.js"></script></head><body><div id="root"><h2 v-text="n"></h2><h2>当前的n值是:{{n}}</h2><button @click="add">点我n+1</button><button @click="bye">点我销毁vm</button></div></body><script type="text/javascript">Vue.config.productionTip = falsenew Vue({el:'#root',// template:`// <div>// <h2>当前的n值是:{{n}}</h2>// <button @click="add">点我n+1</button>// </div>// `,data:{n:1},methods: {add(){console.log('add')this.n++},bye(){console.log('bye')this.$destroy()}},watch:{n(){console.log('n变了')}},beforeCreate() {console.log('beforeCreate')},created() {console.log('created')},beforeMount() {console.log('beforeMount')},mounted() {console.log('mounted')},beforeUpdate() {console.log('beforeUpdate')},updated() {console.log('updated')},beforeDestroy() {console.log('beforeDestroy')},destroyed() {console.log('destroyed')},})</script>
</html>
效果:
1.18.3. 总结生命周期
<!DOCTYPE html>
<html><head><meta charset="UTF-8" /><title>引出生命周期</title><script type="text/javascript" src="../js/vue.js"></script></head><body><div id="root"><h2 :style="{opacity}">欢迎学习Vue</h2><button @click="opacity = 1">透明度设置为1</button><button @click="stop">点我停止变换</button></div></body><script type="text/javascript">Vue.config.productionTip = false new Vue({el:'#root',data:{opacity:1},methods: {stop(){this.$destroy()}},mounted(){console.log('mounted',this)this.timer = setInterval(() => {console.log('setInterval')this.opacity -= 0.01if(this.opacity <= 0) this.opacity = 1},16)},beforeDestroy() {clearInterval(this.timer)console.log('vm即将驾鹤西游了')},})</script>
</html>
效果:
总结:
常用的生命周期钩子:
mounted
:发送ajax请求、启动定时器、绑定自定义事件、订阅消息等初始化操作beforeDestroy
:清除定时器、解绑自定义事件、取消订阅消息等收尾工作
关于销毁Vue实例:
销毁后借助Vue开发者工具看不到任何信息
销毁后自定义事件会失效,但原生DOM事件依然有效
一般不会在
beforeDestroy
操作数据,因为即便操作数据,也不会再触发更新流程了
2. Vue组件化编程
2.1. 模块与组件、模块化与组件化
2.1.1. 模块
- 理解:向外提供特定功能的 js 程序,一般就是一个 js 文件
- 为什么:js 文件很多很复杂
- 作用:复用 js,简化 js 的编写,提高 js 运行效率
2.1.2. 组件
- 定义:用来实现局部功能的代码和资源的集合(html/css/js/image…)
- 为什么:一个界面的功能很复杂
- 作用:复用编码,简化项目编码,提高运行效率
2.1.3. 模块化
当应用中的 js 都以模块来编写的,那这个应用就是一个模块化的应用
2.1.4. 组件化
当应用中的功能都是多组件的方式来编写的,那这个应用就是一个组件化的应用
2.2. 非单文件组件
2.2.1. 基本使用
<!DOCTYPE html>
<html><head><meta charset="UTF-8" /><title>基本使用</title><script type="text/javascript" src="../js/vue.js"></script></head><body><div id="root"><h1>{{msg}}</h1><hr><!-- 第三步:编写组件标签 --><school></school><hr><!-- 第三步:编写组件标签 --><student></student></div></body><script type="text/javascript">Vue.config.productionTip = false//第一步:创建school组件const school = Vue.extend({//组件定义时,一定不要写el配置项,因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器。template:`<div class="demo"><h2>学校名称:{{schoolName}}</h2><h2>学校地址:{{address}}</h2> </div>`,data(){return {schoolName:'尚硅谷',address:'北京昌平'}}})//第一步:创建student组件const student = Vue.extend({template:`<div><h2>学生姓名:{{studentName}}</h2><h2>学生年龄:{{age}}</h2></div>`,data(){return {studentName:'JOJO',age:20}}})//创建vmnew Vue({el:'#root',data:{msg:'你好,JOJO!'},//第二步:注册组件(局部注册)components:{school,student}})</script>
</html>
效果:
总结:
Vue中使用组件的三大步骤:
- 定义组件(创建组件)
- 注册组件
- 使用组件(写组件标签)
如何定义一个组件?
使用
Vue.extend(options)
创建,其中options
和new Vue(options)
时传入的options
几乎一样,但也有点区别:el不要写,为什么?
最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器
data必须写成函数,为什么?
避免组件被复用时,数据存在引用关系
如何注册组件?
- 局部注册:
new Vue
的时候传入components
选项 - 全局注册:
Vue.component('组件名',组件)
- 局部注册:
编写组件标签:
<school></school>
2.2.2. 组件注意事项
<!DOCTYPE html>
<html><head><meta charset="UTF-8" /><title>组件注意事项</title><script type="text/javascript" src="../js/vue.js"></script></head><body><div id="root"><h1>{{msg}}</h1><school></school></div></body><script type="text/javascript">Vue.config.productionTip = falseconst school = Vue.extend({name:'atguigu',template:`<div><h2>学校名称:{{name}}</h2> <h2>学校地址:{{address}}</h2> </div>`,data(){return {name:'尚硅谷',address:'北京'}}})new Vue({el:'#root',data:{msg:'欢迎学习Vue!'},components:{school}})</script>
</html>
效果:
总结:
关于组件名:
一个单词组成:
- 第一种写法(首字母小写):school
- 第二种写法(首字母大写):School
多个单词组成:
- 第一种写法(kebab-case命名):my-school
- 第二种写法(CamelCase命名):MySchool (需要Vue脚手架支持)
备注:
- 组件名尽可能回避HTML中已有的元素名称,例如:h2、H2都不行
- 可以使用name配置项指定组件在开发者工具中呈现的名字
关于组件标签:
- 第一种写法:
<school></school>
- 第二种写法:
<school/>
- 备注:不使用脚手架时,
<school/>
会导致后续组件不能渲染
- 第一种写法:
一个简写方式:
const school = Vue.extend(options)
可简写为:const school = options
2.2.3. 组件的嵌套
<!DOCTYPE html>
<html><head><meta charset="UTF-8" /><title>组件的嵌套</title><script type="text/javascript" src="../js/vue.js"></script></head><body><div id="root"></div></body><script type="text/javascript">Vue.config.productionTip = false//定义student组件const student = Vue.extend({template:`<div><h2>学生名称:{{name}}</h2> <h2>学生年龄:{{age}}</h2> </div>`,data(){return {name:'JOJO',age:20}}})//定义school组件const school = Vue.extend({template:`<div><h2>学校名称:{{name}}</h2> <h2>学校地址:{{address}}</h2> <student></student></div>`,components:{student},data(){return {name:'尚硅谷',address:'北京'}}})//定义hello组件const hello = Vue.extend({template:`<h1>{{msg}}</h1>`,data(){return {msg:"欢迎学习尚硅谷Vue教程!"}}})//定义app组件const app = Vue.extend({template:`<div><hello></hello><school></school></div>`,components:{school,hello}})//创建vmnew Vue({template:`<app></app>`,el:'#root',components:{app}})</script>
</html>
效果:
2.2.4. VueComponent
关于VueComponent:
school组件本质是一个名为
VueComponent
的构造函数,且不是程序员定义的,是Vue.extend
生成的我们只需要写
<school/>
或<school></school>
,Vue解析时会帮我们创建school组件的实例对象,即Vue帮我们执行的:new VueComponent(options)
特别注意:每次调用
Vue.extend
,返回的都是一个全新的VueComponent!关于this指向:
- 组件配置中:
data
函数、methods
中的函数、watch
中的函数、computed
中的函数 它们的this均是VueComponent实例对象 new Vue(options)
配置中:data
函数、methods
中的函数、watch
中的函数、computed
中的函数 它们的this均是Vue实例对象
- 组件配置中:
VueComponent
的实例对象,以后简称vc(也可称之为:组件实例对象)Vue
的实例对象,以后简称vm只有在本笔记中
VueComponent
的实例对象才简称为vc
2.2.5. 一个重要的内置关系
<!DOCTYPE html>
<html><head><meta charset="UTF-8" /><title>一个重要的内置关系</title><script type="text/javascript" src="../js/vue.js"></script></head><body><div id="root"><school></school></div></body><script type="text/javascript">Vue.config.productionTip = falseVue.prototype.x = 99const school = Vue.extend({name:'school',template:`<div><h2>学校名称:{{name}}</h2> <h2>学校地址:{{address}}</h2> <button @click="showX">点我输出x</button></div>`,data(){return {name:'尚硅谷',address:'北京'}},methods: {showX(){console.log(this.x)}},})const vm = new Vue({el:'#root',data:{msg:'你好'},components:{school}})</script>
</html>
效果:
总结:
- 一个重要的内置关系:
VueComponent.prototype.__proto__ === Vue.prototype
- 为什么要有这个关系:让组件实例对象(vc)可以访问到 Vue 原型上的属性、方法
2.3. 单文件组件
School.vue:
<template><div id='Demo'><h2>学校名称:{{name}}</h2><h2>学校地址:{{address}}</h2><button @click="showName">点我提示学校名</button></div> </template><script>export default {name:'School',data() {return {name:'尚硅谷',address:'北京'}},methods: {showName(){alert(this.name)}},} </script><style>#Demo{background: orange;} </style>
Student.vue:
<template><div><h2>学生姓名:{{name}}</h2><h2>学生年龄:{{age}}</h2></div> </template><script>export default {name:'Student',data() {return {name:'JOJO',age:20}},} </script>
App.vue:
<template><div><School></School><Student></Student></div> </template><script>import School from './School.vue'import Student from './Student.vue'export default {name:'App',components:{School,Student}} </script>
main.js:
import App from './App.vue'new Vue({template:`<App></App>`,el:'#root',components:{App} })
index.html
<!DOCTYPE html> <html lang="en"> <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"><title>单文件组件练习</title> </head> <body><div id="root"></div><script src="../../js/vue.js"></script><script src="./main.js"></script> </body> </html>
3. 使用Vue CLI脚手架
3.1. 初始化脚手架
3.1.1. 说明
- Vue 脚手架是 Vue 官方提供的标准化开发工具(开发平台)
- 最新的版本是 4.x
- 文档:Vue CLI
3.1.2. 具体步骤
- 如果下载缓慢请配置 npm 淘宝镜像:
npm config set registry http://registry.npm.taobao.org
- 全局安装@vue/cli:
npm install -g @vue/cli
- 切换到你要创建项目的目录,然后使用命令创建项目:
vue create xxxx
- 选择使用vue的版本
- 启动项目:
npm run serve
- 暂停项目:Ctrl+C
Vue 脚手架隐藏了所有 webpack 相关的配置,若想查看具体的 webpakc 配置,请执行:
vue inspect > output.js
3.1.3. 分析脚手架结构
脚手架文件结构:
.文件目录
├── node_modules
├── public
│ ├── favicon.ico: 页签图标
│ └── index.html: 主页面
├── src
│ ├── assets: 存放静态资源
│ │ └── logo.png
│ │── component: 存放组件
│ │ └── HelloWorld.vue
│ │── App.vue: 汇总所有组件
│ └── main.js: 入口文件
├── .gitignore: git版本管制忽略的配置
├── babel.config.js: babel的配置文件
├── package.json: 应用包配置文件
├── README.md: 应用描述文件
└── package-lock.json: 包版本控制文件
src/components/School.vue
:
<template><div id='Demo'><h2>学校名称:{{name}}</h2><h2>学校地址:{{address}}</h2><button @click="showName">点我提示学校名</button></div>
</template><script>export default {name:'School',data() {return {name:'尚硅谷',address:'北京'}},methods: {showName() {alert(this.name)}},}
</script><style>#Demo{background: orange;}
</style>
src/components/Student.vue
:
<template><div><h2>学生姓名:{{name}}</h2><h2>学生年龄:{{age}}</h2></div>
</template><script>export default {name:'Student',data() {return {name:'JOJO',age:20}},}
</script>
src/App.vue
:
<template><div><School></School><Student></Student></div>
</template><script>import School from './components/School.vue'import Student from './components/Student.vue'export default {name:'App',components:{School,Student}}
</script>
src/main.js
:
import Vue from 'vue'
import App from './App.vue'Vue.config.productionTip = falsenew Vue({el:'#app',render: h => h(App),
})
public/index.html
:
<!DOCTYPE html>
<html lang=""><head><meta charset="UTF-8"><!-- 针对IE浏览器的特殊配置,含义是让IE浏览器以最高渲染级别渲染页面 --><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><!-- 容器 --><div id="app"></div></body>
</html>
效果:
3.1.4. render函数
import Vue from 'vue'
import App from './App.vue'Vue.config.productionTip = falsenew Vue({el:'#app',// 简写形式render: h => h(App),// 完整形式// render(createElement){// return createElement(App)// }
})
总结:
关于不同版本的函数:
vue.js
与vue.runtime.xxx.js
的区别:vue.js
是完整版的 Vue,包含:核心功能+模板解析器vue.runtime.xxx.js
是运行版的 Vue,只包含核心功能,没有模板解析器
因为
vue.runtime.xxx.js
没有模板解析器,所以不能使用template
配置项,需要使用render
函数接收到的createElement
函数去指定具体内容
3.1.5. 修改默认配置
vue.config.js
是一个可选的配置文件,如果项目的(和package.json
同级的)根目录中存在这个文件,那么它会被@vue/cli-service
自动加载- 使用
vue.config.js
可以对脚手架进行个性化定制,详见配置参考 | Vue CLI
module.exports = {pages: {index: {// 入口entry: 'src/index/main.js'}},// 关闭语法检查lineOnSave:false
}
3.2. ref属性
<template><div><h1 ref="title">{{msg}}</h1><School ref="sch"/><button @click="show" ref="btn">点我输出ref</button></div>
</template><script>import School from './components/School.vue'export default {name:'App',components: { School },data() {return {msg:'欢迎学习Vue!'}},methods:{show(){console.log(this.$refs.title)console.log(this.$refs.sch)console.log(this.$refs.btn)}}}
</script>
效果:
总结:
ref
属性:
- 被用来给元素或子组件注册引用信息(id的替代者)
- 应用在
html
标签上获取的是真实DOM元素,应用在组件标签上获取的是组件实例对象(vc) - 使用方式:
- 打标识:
<h1 ref="xxx"></h1>
或<School ref="xxx"></School>
- 获取:
this.$refs.xxx
- 打标识:
3.3. props配置项
src/App.vue
:
<template><div><Student name="JOJO" sex="男酮" :age="20" /></div>
</template><script>import Student from './components/Student.vue'export default {name:'App',components: { Student },}
</script>
src/components/Student.vue
:
<template><div><h1>{{msg}}</h1><h2>学生姓名:{{name}}</h2><h2>学生性别:{{sex}}</h2><h2>学生年龄:{{age}}</h2> </div>
</template><script>export default {name:'Student',data() {return {msg:"我是一名来自枝江大学的男酮,嘿嘿,我的金轮~~",}},// 简单声明接收// props:['name','age','sex']// 接收的同时对数据进行类型限制/* props:{name:String,age:Number,sex:String} */// 接收的同时对数据进行类型限制 + 指定默认值 + 限制必要性props:{name:{type:String,required:true,},age:{type:Number,default:99},sex:{type:String,required:true}}}
</script>
效果:
总结:
props
配置项:
功能:让组件接收外部传过来的数据
传递数据:
<Demo name="xxx"/>
接收数据:
第一种方式(只接收):
props:['name']
第二种方式(限制数据类型):
props:{name:String}
第三种方式(限制类型、限制必要性、指定默认值):
props:{name:{type:String, //类型required:true, //必要性default:'JOJO' //默认值} }
props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据
3.4. mixin混入
局部混入:
src/mixin.js
:
export const mixin = {methods: {showName() {alert(this.name)}},mounted() {console.log("你好呀~")}
}
src/components/School.vue
<template><div><h2 @click="showName">学校姓名:{{name}}</h2><h2>学校地址:{{address}}</h2> </div>
</template><script>//引入混入import {mixin} from '../mixin'export default {name:'School',data() {return {name:'尚硅谷',address:'北京'}},mixins:[mixin]}
</script>
src/components/Student.vue
:
<template><div><h2 @click="showName">学生姓名:{{name}}</h2><h2>学生性别:{{sex}}</h2> </div>
</template><script>//引入混入import {mixin} from '../mixin'export default {name:'Student',data() {return {name:'JOJO',sex:'男'}},mixins:[mixin]}
</script>
src/App.vue
:
<template><div><School/><hr/><Student/></div>
</template><script>import Student from './components/Student.vue'import School from './components/School.vue'export default {name:'App',components: { Student,School },}
</script>
效果:
全局混入:
src/main.js
:
import Vue from 'vue'
import App from './App.vue'
import {mixin} from './mixin'Vue.config.productionTip = false
Vue.mixin(mixin)new Vue({el:"#app",render: h => h(App)
})
效果:
总结:
mixin
(混入):
功能:可以把多个组件共用的配置提取成一个混入对象
使用方式:
定义混入:
const mixin = {data(){....},methods:{....}.... }
使用混入:
- 全局混入:
Vue.mixin(xxx)
- 局部混入:
mixins:['xxx']
- 全局混入:
备注:
组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”,在发生冲突时以组件优先。
var mixin = {data: function () {return {message: 'hello',foo: 'abc'}} }new Vue({mixins: [mixin],data () {return {message: 'goodbye',bar: 'def'}},created () {console.log(this.$data)// => { message: "goodbye", foo: "abc", bar: "def" }} })
同名生命周期钩子将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。
var mixin = {created () {console.log('混入对象的钩子被调用')} }new Vue({mixins: [mixin],created () {console.log('组件钩子被调用')} })// => "混入对象的钩子被调用" // => "组件钩子被调用"
3.5. plugin插件
src/plugin.js
:
export default {install(Vue,x,y,z){console.log(x,y,z)//全局过滤器Vue.filter('mySlice',function(value){return value.slice(0,4)})//定义混入Vue.mixin({data() {return {x:100,y:200}},})//给Vue原型上添加一个方法(vm和vc就都能用了)Vue.prototype.hello = ()=>{alert('你好啊')}}
}
src/main.js
:
import Vue from 'vue'
import App from './App.vue'
import plugin from './plugin'Vue.config.productionTip = false
Vue.use(plugin,1,2,3)new Vue({el:"#app",render: h => h(App)
})
src/components/School.vue
:
<template><div><h2>学校姓名:{{name | mySlice}}</h2><h2>学校地址:{{address}}</h2> </div>
</template><script>export default {name:'School',data() {return {name:'尚硅谷atguigu',address:'北京'}}}
</script>
src/components/Student.vue
:
<template><div><h2>学生姓名:{{name}}</h2><h2>学生性别:{{sex}}</h2> <button @click="test">点我测试hello方法</button> </div>
</template><script>export default {name:'Student',data() {return {name:'JOJO',sex:'男'}},methods:{test() {this.hello()}}}
</script>
效果:
总结:
插件:
功能:用于增强Vue
本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据
定义插件:
plugin.install = function (Vue, options) {// 1. 添加全局过滤器Vue.filter(....)// 2. 添加全局指令Vue.directive(....)// 3. 配置全局混入Vue.mixin(....)// 4. 添加实例方法Vue.prototype.$myMethod = function () {...}Vue.prototype.$myProperty = xxxx}
使用插件:
Vue.use(plugin)
3.6. scoped样式
src/components/School.vue
:
<template><div class="demo"><h2>学校姓名:{{name}}</h2><h2>学校地址:{{address}}</h2> </div>
</template><script>export default {name:'School',data() {return {name:'尚硅谷',address:'北京'}}}
</script><style scoped>.demo{background-color: blueviolet;}
</style>
src/components/Student.vue
<template><div class="demo"><h2>学生姓名:{{name}}</h2><h2>学生性别:{{sex}}</h2> </div>
</template><script>export default {name:'Student',data() {return {name:'JOJO',sex:'男'}}}
</script><style scoped>.demo{background-color: chartreuse;}
</style>
src/App.vue
:
<template><div><School/><Student/></div>
</template><script>import Student from './components/Student.vue'import School from './components/School.vue'export default {name:'App',components: { Student,School },}
</script>
效果:
总结:
scoped
样式:
- 作用:让样式在局部生效,防止冲突
- 写法:
<style scoped>
scoped
样式一般不会在App.vue
中使用
3.7. Todo-List案例
src/components/MyHeader.vue
:
<template><div class="todo-header"><input type="text" placeholder="请输入你的任务名称,按回车键确认" @keydown.enter="add" v-model="title"/></div>
</template><script>import {nanoid} from 'nanoid'export default {name:'MyHeader',data() {return {title:''}},methods:{add(){if(!this.title.trim()) returnconst todoObj = {id:nanoid(),title:this.title,done:false}this.addTodo(todoObj)this.title = ''}},props:['addTodo']}
</script><style scoped>.todo-header input {width: 560px;height: 28px;font-size: 14px;border: 1px solid #ccc;border-radius: 4px;padding: 4px 7px;}.todo-header input:focus {outline: none;border-color: rgba(82, 168, 236, 0.8);box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);}
</style>
src/components/MyItem.vue
:
<template><li><label><input type="checkbox" :checked="todo.done" @click="handleCheck(todo.id)"/><span>{{todo.title}}</span></label><button class="btn btn-danger" @click="handleDelete(todo.id,todo.title)">删除</button></li>
</template><script>export default {name:'MyItem',props:['todo','checkTodo','deleteTodo'],methods:{handleCheck(id){this.checkTodo(id)},handleDelete(id,title){if(confirm("确定删除任务:"+title+"吗?")){this.deleteTodo(id)}}}}
</script><style scoped>li {list-style: none;height: 36px;line-height: 36px;padding: 0 5px;border-bottom: 1px solid #ddd;}li label {float: left;cursor: pointer;}li label li input {vertical-align: middle;margin-right: 6px;position: relative;top: -1px;}li button {float: right;display: none;margin-top: 3px;}li:before {content: initial;}li:last-child {border-bottom: none;}li:hover {background-color: #eee;}li:hover button{display: block;}
</style>
src/components/MyList.vue
:
<template><ul class="todo-main"><MyItem v-for="todo in todos" :key="todo.id" :todo="todo" :checkTodo="checkTodo":deleteTodo="deleteTodo"/></ul>
</template><script>import MyItem from './MyItem.vue'export default {name:'MyList',components:{MyItem},props:['todos','checkTodo','deleteTodo']}
</script><style scoped>.todo-main {margin-left: 0px;border: 1px solid #ddd;border-radius: 2px;padding: 0px;}.todo-empty {height: 40px;line-height: 40px;border: 1px solid #ddd;border-radius: 2px;padding-left: 5px;margin-top: 10px;}
</style>
src/components/MyFooter.vue
:
<template><div class="todo-footer" v-show="total"><label><input type="checkbox" v-model="isAll"/></label><span><span>已完成{{doneTotal}}</span> / 全部{{total}}</span><button class="btn btn-danger" @click="clearAll">清除已完成任务</button></div>
</template><script>export default {name:'MyFooter',props:['todos','checkAllTodo','clearAllTodo'],computed:{doneTotal(){return this.todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0) ,0)},total(){return this.todos.length},isAll:{get(){return this.total === this.doneTotal && this.total > 0},set(value){this.checkAllTodo(value)}}},methods:{clearAll(){this.clearAllTodo()}}}
</script><style scoped>.todo-footer {height: 40px;line-height: 40px;padding-left: 6px;margin-top: 5px;}.todo-footer label {display: inline-block;margin-right: 20px;cursor: pointer;}.todo-footer label input {position: relative;top: -1px;vertical-align: middle;margin-right: 5px;}.todo-footer button {float: right;margin-top: 5px;}
</style>
src/App.vue
:
<template><div id="root"><div class="todo-container"><div class="todo-wrap"><MyHeader :addTodo="addTodo"/><MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/><MyFooter :todos="todos" :checkAllTodo="checkAllTodo" :clearAllTodo="clearAllTodo"/></div></div></div>
</template><script>import MyHeader from './components/MyHeader.vue'import MyList from './components/MyList.vue'import MyFooter from './components/MyFooter.vue'export default {name:'App',components: { MyHeader,MyList,MyFooter },data() {return {todos:[{id:'001',title:'抽烟',done:false},{id:'002',title:'喝酒',done:false},{id:'003',title:'烫头',done:false},]}},methods:{//添加一个todoaddTodo(todoObj){this.todos.unshift(todoObj)},//勾选or取消勾选一个todocheckTodo(id){this.todos.forEach((todo)=>{if(todo.id === id) todo.done = !todo.done})},//删除一个tododeleteTodo(id){this.todos = this.todos.filter(todo => todo.id !== id)},//全选or取消勾选checkAllTodo(done){this.todos.forEach(todo => todo.done = done)},//删除已完成的todoclearAllTodo(){this.todos = this.todos.filter(todo => !todo.done)}}}
</script><style>body {background: #fff;}.btn {display: inline-block;padding: 4px 12px;margin-bottom: 0;font-size: 14px;line-height: 20px;text-align: center;vertical-align: middle;cursor: pointer;box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);border-radius: 4px;}.btn-danger {color: #fff;background-color: #da4f49;border: 1px solid #bd362f;}.btn-danger:hover {color: #fff;background-color: #bd362f;}.btn:focus {outline: none;}.todo-container {width: 600px;margin: 0 auto;}.todo-container .todo-wrap {padding: 10px;border: 1px solid #ddd;border-radius: 5px;}
</style>
效果:
总结:
组件化编码流程:
- 拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突
- 实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:
- 一个组件在用:放在组件自身即可
- 一些组件在用:放在他们共同的父组件上(状态提升)
- 实现交互:从绑定事件开始
props
适用于:- 父组件 ==> 子组件 通信
- 子组件 ==> 父组件 通信(要求父组件先给子组件一个函数)
使用
v-model
时要切记:v-model
绑定的值不能是props
传过来的值,因为props
是不可以修改的props
传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做
3.8. WebStorage
<!DOCTYPE html>
<html lang="en">
<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"><title>localStorage</title>
</head>
<body><h2>localStorage</h2><button onclick="saveDate()">点我保存数据</button><br/><button onclick="readDate()">点我读数据</button><br/><button onclick="deleteDate()">点我删除数据</button><br/><button onclick="deleteAllDate()">点我清空数据</button><br/><script>let person = {name:"JOJO",age:20}function saveDate(){localStorage.setItem('msg','localStorage')localStorage.setItem('person',JSON.stringify(person))}function readDate(){console.log(localStorage.getItem('msg'))const person = localStorage.getItem('person')console.log(JSON.parse(person))}function deleteDate(){localStorage.removeItem('msg')localStorage.removeItem('person')}function deleteAllDate(){localStorage.clear()}</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<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"><title>sessionStorage</title>
</head>
<body><h2>sessionStorage</h2><button onclick="saveDate()">点我保存数据</button><br/><button onclick="readDate()">点我读数据</button><br/><button onclick="deleteDate()">点我删除数据</button><br/><button onclick="deleteAllDate()">点我清空数据</button><br/><script>let person = {name:"JOJO",age:20}function saveDate(){sessionStorage.setItem('msg','sessionStorage')sessionStorage.setItem('person',JSON.stringify(person))}function readDate(){console.log(sessionStorage.getItem('msg'))const person = sessionStorage.getItem('person')console.log(JSON.parse(person))}function deleteDate(){sessionStorage.removeItem('msg')sessionStorage.removeItem('person')}function deleteAllDate(){sessionStorage.clear()}</script>
</body>
</html>
总结:
存储内容大小一般支持5MB左右(不同浏览器可能还不一样)
浏览器端通过
Window.sessionStorage
和Window.localStorage
属性来实现本地存储机制相关API:
xxxStorage.setItem('key', 'value')
:该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值xxxStorage.getItem('key')
:该方法接受一个键名作为参数,返回键名对应的值xxxStorage.removeItem('key')
:该方法接受一个键名作为参数,并把该键名从存储中删除xxxStorage.clear()
:该方法会清空存储中的所有数据
备注:
SessionStorage
存储的内容会随着浏览器窗口关闭而消失LocalStorage
存储的内容,需要手动清除才会消失xxxStorage.getItem(xxx)
如果 xxx 对应的 value 获取不到,那么getItem()
的返回值是nullJSON.parse(null)
的结果依然是null
使用本地存储优化Todo-List:
src/App.vue
:
<template><div id="root"><div class="todo-container"><div class="todo-wrap"><MyHeader :addTodo="addTodo"/><MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/><MyFooter :todos="todos" :checkAllTodo="checkAllTodo" :clearAllTodo="clearAllTodo"/></div></div></div>
</template><script>import MyHeader from './components/MyHeader.vue'import MyList from './components/MyList.vue'import MyFooter from './components/MyFooter.vue'export default {name:'App',components: { MyHeader,MyList,MyFooter },data() {return {//若localStorage中存有'todos'则从localStorage中取出,否则初始为空数组todos:JSON.parse(localStorage.getItem('todos')) || []}},methods:{//添加一个todoaddTodo(todoObj){this.todos.unshift(todoObj)},//勾选or取消勾选一个todocheckTodo(id){this.todos.forEach((todo)=>{if(todo.id === id) todo.done = !todo.done})},//删除一个tododeleteTodo(id){this.todos = this.todos.filter(todo => todo.id !== id)},//全选or取消勾选checkAllTodo(done){this.todos.forEach(todo => todo.done = done)},//删除已完成的todoclearAllTodo(){this.todos = this.todos.filter(todo => !todo.done)}},watch:{todos:{//由于todos是对象数组,所以必须开启深度监视才能发现数组中对象的变化deep:true,handler(value){localStorage.setItem('todos',JSON.stringify(value))}}}}
</script><style>body {background: #fff;}.btn {display: inline-block;padding: 4px 12px;margin-bottom: 0;font-size: 14px;line-height: 20px;text-align: center;vertical-align: middle;cursor: pointer;box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);border-radius: 4px;}.btn-danger {color: #fff;background-color: #da4f49;border: 1px solid #bd362f;}.btn-danger:hover {color: #fff;background-color: #bd362f;}.btn:focus {outline: none;}.todo-container {width: 600px;margin: 0 auto;}.todo-container .todo-wrap {padding: 10px;border: 1px solid #ddd;border-radius: 5px;}
</style>
3.9. 自定义事件
3.9.1. 绑定
src/App.vue
:
<template><div class="app"><!-- 通过父组件给子组件传递函数类型的props实现子给父传递数据 --><School :getSchoolName="getSchoolName"/><!-- 通过父组件给子组件绑定一个自定义事件实现子给父传递数据(第一种写法,使用@或v-on) --><!-- <Student @jojo="getStudentName"/> --><!-- 通过父组件给子组件绑定一个自定义事件实现子给父传递数据(第二种写法,使用ref) --><Student ref="student"/></div>
</template><script>import Student from './components/Student.vue'import School from './components/School.vue'export default {name:'App',components: { Student,School },methods:{getSchoolName(name){console.log("已收到学校的名称:"+name)},getStudentName(name){console.log("已收到学生的姓名:"+name) }},mounted(){this.$refs.student.$on('jojo',this.getStudentName)}}
</script><style scoped>.app{background-color: gray;padding: 5px;}
</style>
src/components/Student.vue
:
<template><div class="student"><h2>学生姓名:{{name}}</h2><h2>学生性别:{{sex}}</h2><button @click="sendStudentName">点我传递学生姓名</button> </div>
</template><script>export default {name:'Student',data() {return {name:'JOJO',sex:'男'}},methods:{sendStudentName(){this.$emit('jojo',this.name)}}}
</script><style scoped>.student{background-color: chartreuse;padding: 5px;margin-top: 30px;}
</style>
效果:
3.9.2. 解绑
src/App.vue
:
<template><div class="app"><Student @jojo="getStudentName"/></div>
</template><script>import Student from './components/Student.vue'export default {name:'App',components: { Student },methods:{getStudentName(name){console.log("已收到学生的姓名:"+name) }}}
</script><style scoped>.app{background-color: gray;padding: 5px;}
</style>
src/components/Student.vue
:
<template><div class="student"><h2>学生姓名:{{name}}</h2><h2>学生性别:{{sex}}</h2><button @click="sendStudentName">点我传递学生姓名</button> <button @click="unbind">解绑自定义事件</button> </div>
</template><script>export default {name:'Student',data() {return {name:'JOJO',sex:'男'}},methods:{sendStudentName(){this.$emit('jojo',this.name)},unbind(){// 解绑一个自定义事件// this.$off('jojo')// 解绑多个自定义事件// this.$off(['jojo'])// 解绑所有自定义事件this.$off()}}}
</script><style scoped>.student{background-color: chartreuse;padding: 5px;margin-top: 30px;}
</style>
效果:
总结:
组件的自定义事件:
一种组件间通信的方式,适用于:==子组件 > 父组件
使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)
绑定自定义事件:
第一种方式,在父组件中:
<Demo @atguigu="test"/>
或<Demo v-on:atguigu="test"/>
第二种方式,在父组件中:
<Demo ref="demo"/> ... mounted(){this.$refs.demo.$on('atguigu',data) }
若想让自定义事件只能触发一次,可以使用
once
修饰符,或$once
方法
触发自定义事件:
this.$emit('atguigu',数据)
解绑自定义事件:
this.$off('atguigu')
组件上也可以绑定原生DOM事件,需要使用
native
修饰符注意:通过
this.$refs.xxx.$on('atguigu',回调)
绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!
使用自定义事件优化Todo-List:
src/App.vue
:
<template><div id="root"><div class="todo-container"><div class="todo-wrap"><MyHeader @addTodo="addTodo"/><MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/><MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"/></div></div></div>
</template><script>import MyHeader from './components/MyHeader.vue'import MyList from './components/MyList.vue'import MyFooter from './components/MyFooter.vue'export default {name:'App',components: { MyHeader,MyList,MyFooter },data() {return {todos:JSON.parse(localStorage.getItem('todos')) || []}},methods:{//添加一个todoaddTodo(todoObj){this.todos.unshift(todoObj)},//勾选or取消勾选一个todocheckTodo(id){this.todos.forEach((todo)=>{if(todo.id === id) todo.done = !todo.done})},//删除一个tododeleteTodo(id){this.todos = this.todos.filter(todo => todo.id !== id)},//全选or取消勾选checkAllTodo(done){this.todos.forEach(todo => todo.done = done)},//删除已完成的todoclearAllTodo(){this.todos = this.todos.filter(todo => !todo.done)}},watch:{todos:{deep:true,handler(value){localStorage.setItem('todos',JSON.stringify(value))}}}}
</script><style>body {background: #fff;}.btn {display: inline-block;padding: 4px 12px;margin-bottom: 0;font-size: 14px;line-height: 20px;text-align: center;vertical-align: middle;cursor: pointer;box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);border-radius: 4px;}.btn-danger {color: #fff;background-color: #da4f49;border: 1px solid #bd362f;}.btn-danger:hover {color: #fff;background-color: #bd362f;}.btn:focus {outline: none;}.todo-container {width: 600px;margin: 0 auto;}.todo-container .todo-wrap {padding: 10px;border: 1px solid #ddd;border-radius: 5px;}
</style>
src/components/MyHeader.vue
:
<template><div class="todo-header"><input type="text" placeholder="请输入你的任务名称,按回车键确认" @keydown.enter="add" v-model="title"/></div>
</template><script>import {nanoid} from 'nanoid'export default {name:'MyHeader',data() {return {title:''}},methods:{add(){if(!this.title.trim()) returnconst todoObj = {id:nanoid(),title:this.title,done:false}this.$emit('addTodo',todoObj)this.title = ''}}}
</script><style scoped>/*header*/.todo-header input {width: 560px;height: 28px;font-size: 14px;border: 1px solid #ccc;border-radius: 4px;padding: 4px 7px;}.todo-header input:focus {outline: none;border-color: rgba(82, 168, 236, 0.8);box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);}
</style>
src/components/MyFooter
:
<template><div class="todo-footer" v-show="total"><label><input type="checkbox" v-model="isAll"/></label><span><span>已完成{{doneTotal}}</span> / 全部{{total}}</span><button class="btn btn-danger" @click="clearAll">清除已完成任务</button></div>
</template><script>export default {name:'MyFooter',props:['todos'],computed:{doneTotal(){return this.todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0) ,0)},total(){return this.todos.length},isAll:{get(){return this.total === this.doneTotal && this.total > 0},set(value){this.$emit('checkAllTodo',value)}}},methods:{clearAll(){this.$emit('clearAllTodo')}}}
</script><style scoped>.todo-footer {height: 40px;line-height: 40px;padding-left: 6px;margin-top: 5px;}.todo-footer label {display: inline-block;margin-right: 20px;cursor: pointer;}.todo-footer label input {position: relative;top: -1px;vertical-align: middle;margin-right: 5px;}.todo-footer button {float: right;margin-top: 5px;}
</style>
3.10. 全局事件总线
全局事件总线是一种可以在任意组件间通信的方式,本质上就是一个对象。它必须满足以下条件:1. 所有的组件对象都必须能看见他 2. 这个对象必须能够使用
$on
、$emit
和$off
方法去绑定、触发和解绑事件
src/main.js
:
import Vue from 'vue'
import App from './App.vue'Vue.config.productionTip = falsenew Vue({el:'#app',render: h => h(App),beforeCreate() {Vue.prototype.$bus = this //安装全局事件总线}
})
src/App.vue
:
<template><div class="app"><School/><Student/></div>
</template><script>import Student from './components/Student'import School from './components/School'export default {name:'App',components:{School,Student}}
</script><style scoped>.app{background-color: gray;padding: 5px;}
</style>
src/components/School.vue
:
<template><div class="school"><h2>学校名称:{{name}}</h2><h2>学校地址:{{address}}</h2></div>
</template><script>export default {name:'School',data() {return {name:'尚硅谷',address:'北京',}},methods:{demo(data) {console.log('我是School组件,收到了数据:',data)}},mounted() {this.$bus.$on('demo',this.demo)},beforeDestroy() {this.$bus.$off('demo')},}
</script><style scoped>.school{background-color: skyblue;padding: 5px;}
</style>
src/components/Student.vue
:
<template><div class="student"><h2>学生姓名:{{name}}</h2><h2>学生性别:{{sex}}</h2><button @click="sendStudentName">把学生名给School组件</button></div>
</template><script>export default {name:'Student',data() {return {name:'张三',sex:'男'}},methods: {sendStudentName(){this.$bus.$emit('demo',this.name)}}}
</script><style scoped>.student{background-color: pink;padding: 5px;margin-top: 30px;}
</style>
效果:
总结:
全局事件总线(GlobalEventBus):
一种组件间通信的方式,适用于任意组件间通信
安装全局事件总线:
new Vue({...beforeCreate() {Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm},... })
使用事件总线:
接收数据:A组件想接收数据,则在A组件中给
$bus
绑定自定义事件,事件的回调留在A组件自身export default {methods(){demo(data){...}}...mounted() {this.$bus.$on('xxx',this.demo)} }
提供数据:
this.$bus.$emit('xxx',data)
最好在
beforeDestroy
钩子中,用$off
去解绑当前组件所用到的事件
使用自定义事件优化Todo-List:
src/mian.js
:
import Vue from 'vue'
import App from './App.vue'Vue.config.productionTip = falsenew Vue({el:"#app",render: h => h(App),beforeCreate() {Vue.prototype.$bus = this}
})
src/components/App.vue
<template><div id="root"><div class="todo-container"><div class="todo-wrap"><MyHeader @addTodo="addTodo"/><MyList :todos="todos"/><MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"/></div></div></div>
</template><script>import MyHeader from './components/MyHeader.vue'import MyList from './components/MyList.vue'import MyFooter from './components/MyFooter.vue'export default {name:'App',components: { MyHeader,MyList,MyFooter },data() {return {todos:JSON.parse(localStorage.getItem('todos')) || []}},methods:{//添加一个todoaddTodo(todoObj){this.todos.unshift(todoObj)},//勾选or取消勾选一个todocheckTodo(id){this.todos.forEach((todo)=>{if(todo.id === id) todo.done = !todo.done})},//删除一个tododeleteTodo(id){this.todos = this.todos.filter(todo => todo.id !== id)},//全选or取消勾选checkAllTodo(done){this.todos.forEach(todo => todo.done = done)},//删除已完成的todoclearAllTodo(){this.todos = this.todos.filter(todo => !todo.done)}},watch:{todos:{deep:true,handler(value){localStorage.setItem('todos',JSON.stringify(value))}}},mounted(){this.$bus.$on('checkTodo',this.checkTodo)this.$bus.$on('deleteTodo',this.deleteTodo)},beforeDestroy(){this.$bus.$off(['checkTodo','deleteTodo'])}}
</script><style>body {background: #fff;}.btn {display: inline-block;padding: 4px 12px;margin-bottom: 0;font-size: 14px;line-height: 20px;text-align: center;vertical-align: middle;cursor: pointer;box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);border-radius: 4px;}.btn-danger {color: #fff;background-color: #da4f49;border: 1px solid #bd362f;}.btn-danger:hover {color: #fff;background-color: #bd362f;}.btn:focus {outline: none;}.todo-container {width: 600px;margin: 0 auto;}.todo-container .todo-wrap {padding: 10px;border: 1px solid #ddd;border-radius: 5px;}
</style>
src/components/MyItem.vue
:
<template><li><label><input type="checkbox" :checked="todo.done" @click="handleCheck(todo.id)"/><span>{{todo.title}}</span></label><button class="btn btn-danger" @click="handleDelete(todo.id,todo.title)">删除</button></li>
</template><script>export default {name:'MyItem',props:['todo'],methods:{handleCheck(id){this.$bus.$emit('checkTodo',id)},handleDelete(id,title){if(confirm("确定删除任务:"+title+"吗?")){this.$bus.$emit('deleteTodo',id)}}}}
</script><style scoped>li {list-style: none;height: 36px;line-height: 36px;padding: 0 5px;border-bottom: 1px solid #ddd;}li label {float: left;cursor: pointer;}li label li input {vertical-align: middle;margin-right: 6px;position: relative;top: -1px;}li button {float: right;display: none;margin-top: 3px;}li:before {content: initial;}li:last-child {border-bottom: none;}li:hover {background-color: #eee;}li:hover button{display: block;}
</style>
3.11. 消息的订阅与发布
src/components/School.vue
:
<template><div class="school"><h2>学校名称:{{name}}</h2><h2>学校地址:{{address}}</h2></div>
</template><script>import pubsub from 'pubsub-js'export default {name:'School',data() {return {name:'尚硅谷',address:'北京',}},methods:{demo(msgName,data) {console.log('我是School组件,收到了数据:',data)}},mounted() {this.pubId = pubsub.subscribe('demo',this.demo) //订阅消息},beforeDestroy() {pubsub.unsubscribe(this.pubId) //取消订阅}}
</script><style scoped>.school{background-color: skyblue;padding: 5px;}
</style>
src/components/Student.vue
:
<template><div class="student"><h2>学生姓名:{{name}}</h2><h2>学生性别:{{sex}}</h2><button @click="sendStudentName">把学生名给School组件</button></div>
</template><script>import pubsub from 'pubsub-js'export default {name:'Student',data() {return {name:'JOJO',sex:'男',}},methods: {sendStudentName(){pubsub.publish('demo',this.name) //发布消息}}}
</script><style scoped>.student{background-color: pink;padding: 5px;margin-top: 30px;}
</style>
效果:
总结:
消息订阅与发布(pubsub):
消息订阅与发布是一种组件间通信的方式,适用于任意组件间通信
使用步骤:
安装pubsub:
npm i pubsub-js
引入:
import pubsub from 'pubsub-js'
接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身
export default {methods(){demo(data){...}}...mounted() {this.pid = pubsub.subscribe('xxx',this.demo)} }
提供数据:
pubsub.publish('xxx',data)
最好在
beforeDestroy
钩子中,使用pubsub.unsubscribe(pid)
取消订阅
使用消息的订阅与发布优化Todo-List:
src/App.vue
:
<template><div id="root"><div class="todo-container"><div class="todo-wrap"><MyHeader @addTodo="addTodo"/><MyList :todos="todos"/><MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"/></div></div></div>
</template><script>import pubsub from 'pubsub-js'import MyHeader from './components/MyHeader.vue'import MyList from './components/MyList.vue'import MyFooter from './components/MyFooter.vue'export default {name:'App',components: { MyHeader,MyList,MyFooter },data() {return {todos:JSON.parse(localStorage.getItem('todos')) || []}},methods:{//添加一个todoaddTodo(todoObj){this.todos.unshift(todoObj)},//勾选or取消勾选一个todocheckTodo(_,id){this.todos.forEach((todo)=>{if(todo.id === id) todo.done = !todo.done})},//删除一个tododeleteTodo(id){this.todos = this.todos.filter(todo => todo.id !== id)},//全选or取消勾选checkAllTodo(done){this.todos.forEach(todo => todo.done = done)},//删除已完成的todoclearAllTodo(){this.todos = this.todos.filter(todo => !todo.done)}},watch:{todos:{deep:true,handler(value){localStorage.setItem('todos',JSON.stringify(value))}}},mounted(){this.pubId = pubsub.subscribe('checkTodo',this.checkTodo)this.$bus.$on('deleteTodo',this.deleteTodo)},beforeDestroy(){pubsub.unsubscribe(this.pubId)this.$bus.$off('deleteTodo')}}
</script><style>body {background: #fff;}.btn {display: inline-block;padding: 4px 12px;margin-bottom: 0;font-size: 14px;line-height: 20px;text-align: center;vertical-align: middle;cursor: pointer;box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);border-radius: 4px;}.btn-danger {color: #fff;background-color: #da4f49;border: 1px solid #bd362f;}.btn-danger:hover {color: #fff;background-color: #bd362f;}.btn:focus {outline: none;}.todo-container {width: 600px;margin: 0 auto;}.todo-container .todo-wrap {padding: 10px;border: 1px solid #ddd;border-radius: 5px;}
</style>
src/components/myItem.vue
:
<template><li><label><input type="checkbox" :checked="todo.done" @click="handleCheck(todo.id)"/><span>{{todo.title}}</span></label><button class="btn btn-danger" @click="handleDelete(todo.id,todo.title)">删除</button></li>
</template><script>import pubsub from 'pubsub-js'export default {name:'MyItem',props:['todo'],methods:{handleCheck(id){ pubsub.publish('checkTodo',id)},handleDelete(id,title){if(confirm("确定删除任务:"+title+"吗?")){this.$bus.$emit('deleteTodo',id)}}}}
</script><style scoped>li {list-style: none;height: 36px;line-height: 36px;padding: 0 5px;border-bottom: 1px solid #ddd;}li label {float: left;cursor: pointer;}li label li input {vertical-align: middle;margin-right: 6px;position: relative;top: -1px;}li button {float: right;display: none;margin-top: 3px;}li:before {content: initial;}li:last-child {border-bottom: none;}li:hover {background-color: #eee;}li:hover button{display: block;}
</style>
3.12. $nextTick
$nextTick(回调函数)
可以将回调延迟到下次 DOM 更新循环之后执行
使用$nextTick优化Todo-List:
src/App.vue
:
<template><div id="root"><div class="todo-container"><div class="todo-wrap"><MyHeader @addTodo="addTodo"/><MyList :todos="todos"/><MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"/></div></div></div>
</template><script>import pubsub from 'pubsub-js'import MyHeader from './components/MyHeader.vue'import MyList from './components/MyList.vue'import MyFooter from './components/MyFooter.vue'export default {name:'App',components: { MyHeader,MyList,MyFooter },data() {return {todos:JSON.parse(localStorage.getItem('todos')) || []}},methods:{//添加一个todoaddTodo(todoObj){this.todos.unshift(todoObj)},//勾选or取消勾选一个todocheckTodo(_,id){this.todos.forEach((todo)=>{if(todo.id === id) todo.done = !todo.done})},//删除一个tododeleteTodo(id){this.todos = this.todos.filter(todo => todo.id !== id)},//更新一个todoupdateTodo(id,title){this.todos.forEach((todo)=>{if(todo.id === id) todo.title = title})},//全选or取消勾选checkAllTodo(done){this.todos.forEach(todo => todo.done = done)},//删除已完成的todoclearAllTodo(){this.todos = this.todos.filter(todo => !todo.done)}},watch:{todos:{deep:true,handler(value){localStorage.setItem('todos',JSON.stringify(value))}}},mounted(){this.pubId = pubsub.subscribe('checkTodo',this.checkTodo)this.$bus.$on('deleteTodo',this.deleteTodo)this.$bus.$on('updateTodo',this.updateTodo)},beforeDestroy(){pubsub.unsubscribe(this.pubId)this.$bus.$off('deleteTodo')this.$bus.$off('updateTodo')}}
</script><style>body {background: #fff;}.btn {display: inline-block;padding: 4px 12px;margin-bottom: 0;font-size: 14px;line-height: 20px;text-align: center;vertical-align: middle;cursor: pointer;box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);border-radius: 4px;}.btn-danger {color: #fff;background-color: #e04e49;border: 1px solid #bd362f;}.btn-danger:hover {color: #fff;background-color: #bd362f;}.btn-info {color: #fff;background-color: rgb(50, 129, 233);border: 1px solid rgb(1, 47, 212);margin-right: 5px;}.btn-info:hover {color: #fff;background-color: rgb(1, 47, 212);}.btn:focus {outline: none;}.todo-container {width: 600px;margin: 0 auto;}.todo-container .todo-wrap {padding: 10px;border: 1px solid #ddd;border-radius: 5px;}
</style>
src/components/MyItem.vue
:
<template><li><label><input type="checkbox" :checked="todo.done" @click="handleCheck(todo.id)"/><span v-show="!todo.isEdit">{{todo.title}}</span><input type="text" v-show="todo.isEdit" :value="todo.title" @blur="handleBlur(todo,$event)" ref="inputTitle"></label><button class="btn btn-danger" @click="handleDelete(todo.id,todo.title)">删除</button><button class="btn btn-info" v-show="!todo.isEdit" @click="handleEdit(todo)">编辑</button></li>
</template><script>import pubsub from 'pubsub-js'export default {name:'MyItem',props:['todo'],methods:{handleCheck(id){ pubsub.publish('checkTodo',id)},handleDelete(id,title){if(confirm("确定删除任务:"+title+"吗?")){this.$bus.$emit('deleteTodo',id)}},handleEdit(todo){// 如果todo自身有isEdit属性就将isEdit改成trueif(Object.prototype.hasOwnProperty.call(todo,'isEdit')){todo.isEdit = true}else{// 如果没有就向todo中添加一个响应式的isEdit属性并设为truethis.$set(todo,'isEdit',true)}// 当Vue重新编译模板之后执行$nextTick()中的回调函数this.$nextTick(function(){// 使input框获取焦点this.$refs.inputTitle.focus()})},// 当input框失去焦点时更新handleBlur(todo,event){todo.isEdit = falseif(!event.target.value.trim()) return alert('输入不能为空!')this.$bus.$emit('updateTodo',todo.id,event.target.value)}}}
</script><style scoped>li {list-style: none;height: 36px;line-height: 36px;padding: 0 5px;border-bottom: 1px solid #ddd;}li label {float: left;cursor: pointer;}li label li input {vertical-align: middle;margin-right: 6px;position: relative;top: -1px;}li button {float: right;display: none;margin-top: 3px;}li:before {content: initial;}li:last-child {border-bottom: none;}li:hover {background-color: #eee;}li:hover button{display: block;}
</style>
Todo-List最终效果:
总结:
$nextTick:
- 语法:
this.$nextTick(回调函数)
- 作用:在下一次 DOM 更新结束后执行其指定的回调
- 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行
3.13. 过渡与动画
src/App.vue
:
<template><div id="root"><MyAnimation/><MyTransition/><MyTransitionGroup/><ThirdPartAnimation/></div>
</template><script>import MyAnimation from './components/MyAnimation.vue'import MyTransition from './components/MyTransition.vue'import MyTransitionGroup from './components/MyTransitionGroup.vue'import ThirdPartAnimation from './components/ThirdPartAnimation.vue'export default {name:'App',components: { MyAnimation,MyTransition,MyTransitionGroup,ThirdPartAnimation },}
</script>
src/components/MyAnimation
:
<template><div><button @click="isShow = !isShow">显示/隐藏</button><transition name="jojo" appear><h1 v-show="isShow">你好啊!</h1></transition></div>
</template><script>
export default {name:'MyTitle',data() {return {isShow:true}}
}
</script><style scoped>h1{background-color: orange;}.jojo-enter-active{animation: jojo 0.5s linear;}.jojo-leave-active{animation: jojo 0.5s linear reverse;}@keyframes jojo {from{transform: translateX(-100%);}to{transform: translateX(0px);}}
</style>
src/components/MyTransition.vue
:
<template><div><button @click="isShow = !isShow">显示/隐藏</button><transition name="jojo" appear><h1 v-show="isShow">你好啊!</h1></transition></div>
</template><script>
export default {name:'MyTitle',data() {return {isShow:true}}
}
</script><style scoped>h1{background-color: orange;}.jojo-enter,.jojo-leave-to{transform: translateX(-100%);}.jojo-enter-to,.jojo-leave{transform: translateX(0);}.jojo-enter-active,.jojo-leave-active{transition: 0.5s linear;}
</style>
src/components/MyTransitionGroup.vue
:
<template><div><button @click="isShow = !isShow">显示/隐藏</button><transition-group name="jojo" appear><h1 v-show="isShow" key="1">你好啊!</h1><h1 v-show="!isShow" key="2">大笨蛋</h1></transition-group></div>
</template><script>
export default {name:'MyTitle',data() {return {isShow:true}}
}
</script>
·
<style scoped>h1{background-color: orange;}.jojo-enter,.jojo-leave-to{transform: translateX(-100%);}.jojo-enter-to,.jojo-leave{transform: translateX(0);}.jojo-enter-active,.jojo-leave-active{transition: 0.5s linear;}
</style>
src/components/ThirdPartAnimation
:
<template><div><button @click="isShow = !isShow">显示/隐藏</button><transition-group appearname="animate__animated animate__bounce"enter-active-class="animate__backInUp" leave-active-class="animate__backOutUp"><h1 v-show="isShow" key="1">你好啊!</h1><h1 v-show="!isShow" key="2">大笨蛋</h1></transition-group></div>
</template><script>import 'animate.css'export default {name:'MyTitle',data() {return {isShow:true}}}
</script><style scoped>h1{background-color: orange;}</style>
效果:
总结:
Vue封装的过度与动画:
- 作用:在插入、更新或移除 DOM元素时,在合适的时候给元素添加样式类名
- 图示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F7tX8Utw-1648820283412)(https://cn.vuejs.org/images/transition.png)]
写法:
准备好样式:
元素进入的样式:
v-enter
:进入的起点v-enter-active
:进入过程中v-enter-to
:进入的终点
元素离开的样式:
v-leave
:离开的起点v-leave-active
:离开过程中v-leave-to
:离开的终点
使用
<transition>
包裹要过度的元素,并配置name属性:<transition name="hello"><h1 v-show="isShow">你好啊!</h1> </transition>
备注:若有多个元素需要过度,则需要使用:
<transition-group>
,且每个元素都要指定key
值
使用动画优化Todo-List:
src/components/MyList.vue
:
<template><ul class="todo-main"><transition-group name="todo" appear><MyItem v-for="todo in todos" :key="todo.id" :todo="todo"/></transition-group></ul>
</template><script>import MyItem from './MyItem.vue'export default {name:'MyList',components:{MyItem},props:['todos']}
</script><style scoped>.todo-main {margin-left: 0px;border: 1px solid #ddd;border-radius: 2px;padding: 0px;}.todo-empty {height: 40px;line-height: 40px;border: 1px solid #ddd;border-radius: 2px;padding-left: 5px;margin-top: 10px;}.todo-enter-active{animation: todo 0.5s linear;}.todo-leave-active{animation: todo 0.5s linear reverse;}@keyframes todo {from{transform: translateX(-100%);}to{transform: translateX(0px);}}
</style>
4. Vue中的Ajax
4.1. Vue脚手架配置代理
本案例需要下载axios库:
npm install axios
vue.config.js
:
module.exports = {pages: {index: {entry: 'src/main.js',},},lintOnSave:false,// 开启代理服务器(方式一)// devServer: {// proxy:'http://localhost:5000'// }//开启代理服务器(方式二)devServer: {proxy: {'/jojo': {target: 'http://localhost:5000',pathRewrite:{'^/jojo':''},// ws: true, //用于支持websocket,默认值为true// changeOrigin: true //用于控制请求头中的host值,默认值为true},'/atguigu': {target: 'http://localhost:5001',pathRewrite:{'^/atguigu':''},// ws: true, //用于支持websocket,默认值为true// changeOrigin: true //用于控制请求头中的host值,默认值为true}}}
}
src/App.vue
:
<template><div id="root"><button @click="getStudents">获取学生信息</button><br/><button @click="getCars">获取汽车信息</button></div>
</template><script>import axios from 'axios'export default {name:'App',methods: {getStudents(){axios.get('http://localhost:8080/jojo/students').then(response => {console.log('请求成功了',response.data)},error => {console.log('请求失败了',error.message)})},getCars(){axios.get('http://localhost:8080/atguigu/cars').then(response => {console.log('请求成功了',response.data)},error => {console.log('请求失败了',error.message)})}}}
</script>
效果:
总结:
vue脚手架配置代理服务器:
方法一:在
vue.config.js
中添加如下配置:devServer:{proxy:"http://localhost:5000" }
说明:
- 优点:配置简单,请求资源时直接发给前端即可
- 缺点:不能配置多个代理,不能灵活的控制请求是否走代理
- 工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器 (优先匹配前端资源)
方法二:
devServer: {proxy: {'/api1': { // 匹配所有以 '/api1'开头的请求路径target: 'http://localhost:5000',// 代理目标的基础路径changeOrigin: true,pathRewrite: {'^/api1': ''}},'/api2': { // 匹配所有以 '/api2'开头的请求路径target: 'http://localhost:5001',// 代理目标的基础路径changeOrigin: true,pathRewrite: {'^/api2': ''}}} }// changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000 // changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080
说明:
- 优点:可以配置多个代理,且可以灵活的控制请求是否走代理
- 缺点:配置略微繁琐,请求资源时必须加前缀
4.2. GitHub用户搜索案例
public/index.html
:
<!DOCTYPE html>
<html lang=""><head><meta charset="UTF-8"><!-- 针对IE浏览器的特殊配置,含义是让IE浏览器以最高渲染级别渲染页面 --><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"><!-- 引入bootstrap样式 --><link rel="stylesheet" href="<%= BASE_URL %>css/bootstrap.css"><!-- 配置网页标题 --><title><%= htmlWebpackPlugin.options.title %></title></head><body><!-- 容器 --><div id="app"></div></body>
</html>
src/main.js
:
import Vue from 'vue'
import App from './App.vue'Vue.config.productionTip = falsenew Vue({el:"#app",render: h => h(App),beforeCreate(){Vue.prototype.$bus = this}
})
src/App.vue
:
<template><div class="container"><Search/><List/></div>
</template><script>import Search from './components/Search.vue'import List from './components/List.vue'export default {name:'App',components:{Search,List},}
</script>
src/components/Search.vue
:
<template><section class="jumbotron"><h3 class="jumbotron-heading">Search Github Users</h3><div><input type="text" placeholder="enter the name you search" v-model="keyWord"/> <button @click="getUsers">Search</button></div></section>
</template><script>import axios from 'axios'export default {name:'Search',data() {return {keyWord:''}},methods: {getUsers(){//请求前更新List的数据this.$bus.$emit('updateListData',{isLoading:true,errMsg:'',users:[],isFirst:false})axios.get(`https://api.github.com/search/users?q=${this.keyWord}`).then(response => {console.log('请求成功了')//请求成功后更新List的数据this.$bus.$emit('updateListData',{isLoading:false,errMsg:'',users:response.data.items})},error => {//请求后更新List的数据this.$bus.$emit('updateListData',{isLoading:false,errMsg:error.message,users:[]})})}}}
</script>
src/components/List.vue
:
<template><div class="row"><!-- 展示用户列表 --><div class="card" v-show="info.users.length" v-for="user in info.users" :key="user.id"><a :href="user.html_url" target="_blank"><img :src="user.avatar_url" style='width: 100px'/></a><h4 class="card-title">{{user.login}}</h4></div><!-- 展示欢迎词 --><h1 v-show="info.isFirst">欢迎使用!</h1><!-- 展示加载中 --><h1 v-show="info.isLoading">加载中...</h1><!-- 展示错误信息 --><h1 v-show="info.errMsg">{{errMsg}}</h1></div>
</template><script>export default {name:'List',data() {return {info:{isFirst:true,isLoading:false,errMsg:'',users:[]}}},mounted(){this.$bus.$on('updateListData',(dataObj)=>{//动态合并两个对象的属性this.info = {...this.info,...dataObj}})},beforeDestroy(){this.$bus.$off('updateListData')}}
</script><style scoped>.album {min-height: 50rem; /* Can be removed; just added for demo purposes */padding-top: 3rem;padding-bottom: 3rem;background-color: #f7f7f7;}.card {float: left;width: 33.333%;padding: .75rem;margin-bottom: 2rem;border: 1px solid #efefef;text-align: center;}.card > img {margin-bottom: .75rem;border-radius: 100px;}.card-text {font-size: 85%;}
</style>
效果:
4.3. vue-resource
下载 vue-resource库:
npm i vue-resource
src/main.js
:
import Vue from 'vue'
import App from './App.vue'
import vueResource from 'vue-resource'Vue.config.productionTip = false
Vue.use(vueResource)new Vue({el:"#app",render: h => h(App),beforeCreate(){Vue.prototype.$bus = this}
})
src/App.vue
:
<template><div class="container"><Search/><List/></div>
</template><script>import Search from './components/Search.vue'import List from './components/List.vue'export default {name:'App',components:{Search,List},}
</script>
src/components/Search.vue
:
<template><section class="jumbotron"><h3 class="jumbotron-heading">Search Github Users</h3><div><input type="text" placeholder="enter the name you search" v-model="keyWord"/> <button @click="getUsers">Search</button></div></section>
</template><script>export default {name:'Search',data() {return {keyWord:''}},methods: {getUsers(){//请求前更新List的数据this.$bus.$emit('updateListData',{isLoading:true,errMsg:'',users:[],isFirst:false})this.$http.get(`https://api.github.com/search/users?q=${this.keyWord}`).then(response => {console.log('请求成功了')//请求成功后更新List的数据this.$bus.$emit('updateListData',{isLoading:false,errMsg:'',users:response.data.items})},error => {//请求后更新List的数据this.$bus.$emit('updateListData',{isLoading:false,errMsg:error.message,users:[]})})}}}
</script>
src/components/List.vue
:
<template><div class="row"><!-- 展示用户列表 --><div class="card" v-show="info.users.length" v-for="user in info.users" :key="user.id"><a :href="user.html_url" target="_blank"><img :src="user.avatar_url" style='width: 100px'/></a><h4 class="card-title">{{user.login}}</h4></div><!-- 展示欢迎词 --><h1 v-show="info.isFirst">欢迎使用!</h1><!-- 展示加载中 --><h1 v-show="info.isLoading">加载中...</h1><!-- 展示错误信息 --><h1 v-show="info.errMsg">{{errMsg}}</h1></div>
</template><script>export default {name:'List',data() {return {info:{isFirst:true,isLoading:false,errMsg:'',users:[]}}},mounted(){this.$bus.$on('updateListData',(dataObj)=>{this.info = {...this.info,...dataObj}})},beforeDestroy(){this.$bus.$off('updateListData')}}
</script><style scoped>.album {min-height: 50rem; /* Can be removed; just added for demo purposes */padding-top: 3rem;padding-bottom: 3rem;background-color: #f7f7f7;}.card {float: left;width: 33.333%;padding: .75rem;margin-bottom: 2rem;border: 1px solid #efefef;text-align: center;}.card > img {margin-bottom: .75rem;border-radius: 100px;}.card-text {font-size: 85%;}
</style>
总结:
vue项目常用的两个Ajax库:
- axios:通用的Ajax请求库,官方推荐,效率高
- vue-resource:vue插件库,vue 1.x使用广泛,官方已不维护
4.4. slot插槽
4.4.1. 默认插槽
src/App.vue
:
<template><div class="container"><Category title="美食" ><img src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt=""></Category><Category title="游戏" ><ul><li v-for="(g,index) in games" :key="index">{{g}}</li></ul></Category><Category title="电影"><video controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video></Category></div>
</template><script>import Category from './components/Category'export default {name:'App',components:{Category},data() {return {games:['植物大战僵尸','红色警戒','空洞骑士','王国']}},}
</script><style scoped>.container{display: flex;justify-content: space-around;}
</style>
src/components/Category.vue
:
<template><div class="category"><h3>{{title}}分类</h3><!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) --><slot>我是一些默认值,当使用者没有传递具体结构时,我会出现</slot></div>
</template><script>export default {name:'Category',props:['title']}
</script><style scoped>.category{background-color: skyblue;width: 200px;height: 300px;}h3{text-align: center;background-color: orange;}video{width: 100%;}img{width: 100%;}
</style>
效果:
4.4.2. 具名插槽
src/App.vue
:
<template><div class="container"><Category title="美食" ><img slot="center" src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt=""><a slot="footer" href="http://www.atguigu.com">更多美食</a></Category><Category title="游戏" ><ul slot="center"><li v-for="(g,index) in games" :key="index">{{g}}</li></ul><div class="foot" slot="footer"><a href="http://www.atguigu.com">单机游戏</a><a href="http://www.atguigu.com">网络游戏</a></div></Category><Category title="电影"><video slot="center" controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video><template v-slot:footer><div class="foot"><a href="http://www.atguigu.com">经典</a><a href="http://www.atguigu.com">热门</a><a href="http://www.atguigu.com">推荐</a></div><h4>欢迎前来观影</h4></template></Category></div>
</template><script>import Category from './components/Category'export default {name:'App',components:{Category},data() {return {games:['植物大战僵尸','红色警戒','空洞骑士','王国']}},}
</script><style>.container,.foot{display: flex;justify-content: space-around;}h4{text-align: center;}
</style>
src/components/Category.vue
:
<template><div class="category"><h3>{{title}}分类</h3><!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) --><slot name="center">我是一些默认值,当使用者没有传递具体结构时,我会出现1</slot><slot name="footer">我是一些默认值,当使用者没有传递具体结构时,我会出现2</slot></div>
</template><script>export default {name:'Category',props:['title']}
</script><style scoped>.category{background-color: skyblue;width: 200px;height: 300px;}h3{text-align: center;background-color: orange;}video{width: 100%;}img{width: 100%;}
</style>
效果:
4.4.3. 作用域插槽
src/App.vue
:
<template><div class="container"><Category title="游戏" ><template scope="jojo"><ul><li v-for="(g,index) in jojo.games" :key="index">{{g}}</li></ul></template></Category><Category title="游戏" ><template scope="jojo"><ol><li v-for="(g,index) in jojo.games" :key="index">{{g}}</li></ol></template></Category><Category title="游戏" ><template scope="jojo"><h4 v-for="(g,index) in jojo.games" :key="index">{{g}}</h4></template></Category></div>
</template><script>import Category from './components/Category'export default {name:'App',components:{Category}}
</script><style>.container,.foot{display: flex;justify-content: space-around;}h4{text-align: center;}
</style>
src/components/Category.vue
:
<template><div class="category"><h3>{{title}}分类</h3><!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) --><slot :games="games">我是一些默认值,当使用者没有传递具体结构时,我会出现1</slot></div>
</template><script>export default {name:'Category',props:['title'],data() {return {games:['植物大战僵尸','红色警戒','空洞骑士','王国']}},}
</script><style scoped>.category{background-color: skyblue;width: 200px;height: 300px;}h3{text-align: center;background-color: orange;}video{width: 100%;}img{width: 100%;}
</style>
效果:
总结:
插槽:
作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于==父组件 > 子组件
分类:默认插槽、具名插槽、作用域插槽
使用方式:
默认插槽:
父组件中:<Category><div>html结构1</div></Category> 子组件中:<template><div><slot>插槽默认内容...</slot></div></template>
具名插槽:
父组件中:<Category><template slot="center"><div>html结构1</div></template><template v-slot:footer><div>html结构2</div></template></Category> 子组件中:<template><div><slot name="center">插槽默认内容...</slot><slot name="footer">插槽默认内容...</slot></div></template>
作用域插槽:
理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(games数据在Category组件中,但使用数据所遍历出来的结构由App组件决定)
具体编码:
父组件中:<Category><template scope="scopeData"><!-- 生成的是ul列表 --><ul><li v-for="g in scopeData.games" :key="g">{{g}}</li></ul></template></Category><Category><template slot-scope="scopeData"><!-- 生成的是h4标题 --><h4 v-for="g in scopeData.games" :key="g">{{g}}</h4></template></Category> 子组件中:<template><div><slot :games="games"></slot></div></template><script>export default {name:'Category',props:['title'],//数据在子组件自身data() {return {games:['红色警戒','穿越火线','劲舞团','超级玛丽']}},}</script>
5. Vuex
5.1. 理解Vuex
5.1.1. Vuex是什么
- 概念:专门在 Vue 中实现集中式状态(数据)管理的一个 Vue 插件,对 vue 应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信
- Vuex Github地址
5.1.2. 什么时候使用Vuex
- 多个组件依赖于同一状态
- 来自不同组件的行为需要变更同一状态
5.1.3. Vuex工作原理图
5.2. 求和案例
下载
vuex
:npm i vuex
5.2.1. 使用纯vue编写
src/App.vue
:
<template><div class="container"><Count/></div>
</template><script>import Count from './components/Count'export default {name:'App',components:{Count}}
</script>
src/components/Count.vue
:
<template><div><h1>当前求和为:{{sum}}</h1><select v-model.number="n"><option value="1">1</option><option value="2">2</option><option value="3">3</option></select><button @click="increment">+</button><button @click="decrement">-</button><button @click="incrementOdd">当前求和为奇数再加</button><button @click="incrementWait">等一等再加</button></div>
</template><script>export default {name:'Count',data() {return {n:1, //用户选择的数字sum:0 //当前的和}},methods: {increment(){this.sum += this.n},decrement(){this.sum -= this.n},incrementOdd(){if(this.sum % 2){this.sum += this.n}},incrementWait(){setTimeout(()=>{this.sum += this.n},500)},},}
</script><style>button{margin-left: 5px;}
</style>
效果:
5.2.2. 搭建Vuex环境
下载 Vuex:
npm i vuex
创建
src/store/index.js
://引入Vue核心库 import Vue from 'vue' //引入Vuex import Vuex from 'vuex' //应用Vuex插件 Vue.use(Vuex)//准备actions对象——响应组件中用户的动作、处理业务逻辑 const actions = {} //准备mutations对象——修改state中的数据 const mutations = {} //准备state对象——保存具体的数据 const state = {}//创建并暴露store export default new Vuex.Store({actions,mutations,state })
在
src/main.js
中创建 vm 时传入store
配置项:
import Vue from 'vue'
import App from './App.vue'
import Vuex from 'vuex'
import store from './store'Vue.config.productionTip = falseVue.use(Vuex)new Vue({el:"#app",render: h => h(App),store
})
5.2.3. 使用Vuex编写
src/components/Count.vue
:
<template><div><h1>当前求和为:{{$store.state.sum}}</h1><select v-model.number="n"><option value="1">1</option><option value="2">2</option><option value="3">3</option></select><button @click="increment">+</button><button @click="decrement">-</button><button @click="incrementOdd">当前求和为奇数再加</button><button @click="incrementWait">等一等再加</button></div>
</template><script>export default {name:'Count',data() {return {n:1, //用户选择的数字}},methods: {increment(){this.$store.commit('ADD',this.n)},decrement(){this.$store.commit('SUBTRACT',this.n)},incrementOdd(){this.$store.dispatch('addOdd',this.n)},incrementWait(){this.$store.dispatch('addWait',this.n)},},}
</script><style>button{margin-left: 5px;}
</style>
src/store/index.js
:
//引入Vue核心库
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//应用Vuex插件
Vue.use(Vuex)//准备actions对象——响应组件中用户的动作
const actions = {addOdd(context,value){console.log("actions中的addOdd被调用了")if(context.state.sum % 2){context.commit('ADD',value)}},addWait(context,value){console.log("actions中的addWait被调用了")setTimeout(()=>{context.commit('ADD',value)},500)},
}
//准备mutations对象——修改state中的数据
const mutations = {ADD(state,value){state.sum += value},SUBTRACT(state,value){state.sum -= value}
}
//准备state对象——保存具体的数据
const state = {sum:0 //当前的和
}//创建并暴露store
export default new Vuex.Store({actions,mutations,state
})
总结:
Vuex的基本使用:
初始化数据
state
,配置actions
、mutations
,操作文件store.js
//引入Vue核心库 import Vue from 'vue' //引入Vuex import Vuex from 'vuex' //引用Vuex Vue.use(Vuex)const actions = {//响应组件中加的动作jia(context,value){// console.log('actions中的jia被调用了',miniStore,value)context.commit('JIA',value)}, }const mutations = {//执行加JIA(state,value){// console.log('mutations中的JIA被调用了',state,value)state.sum += value} }//初始化数据 const state = {sum:0 }//创建并暴露store export default new Vuex.Store({actions,mutations,state, })
组件中读取vuex中的数据:
$store.state.sum
组件中修改vuex中的数据:
$store.dispatch('action中的方法名',数据)
或$store.commit('mutations中的方法名',数据)
备注:若没有网络请求或其他业务逻辑,组件中也可以越过
actions
,即不写dispatch
,直接编写commit
5.3. getters配置项
src/Count.vue
:
<template><div><h1>当前求和为:{{$store.state.sum}}</h1><h3>当前求和的10倍为:{{$store.getters.bigSum}}</h3><select v-model.number="n"><option value="1">1</option><option value="2">2</option><option value="3">3</option></select><button @click="increment">+</button><button @click="decrement">-</button><button @click="incrementOdd">当前求和为奇数再加</button><button @click="incrementWait">等一等再加</button></div>
</template><script>export default {name:'Count',data() {return {n:1, //用户选择的数字}},methods: {increment(){this.$store.commit('ADD',this.n)},decrement(){this.$store.commit('SUBTRACT',this.n)},incrementOdd(){this.$store.dispatch('addOdd',this.n)},incrementWait(){this.$store.dispatch('addWait',this.n)},},}
</script><style>button{margin-left: 5px;}
</style>
src/store/index.js
:
//引入Vue核心库
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//应用Vuex插件
Vue.use(Vuex)//准备actions对象——响应组件中用户的动作
const actions = {addOdd(context,value){console.log("actions中的addOdd被调用了")if(context.state.sum % 2){context.commit('ADD',value)}},addWait(context,value){console.log("actions中的addWait被调用了")setTimeout(()=>{context.commit('ADD',value)},500)},
}
//准备mutations对象——修改state中的数据
const mutations = {ADD(state,value){state.sum += value},SUBTRACT(state,value){state.sum -= value}
}
//准备state对象——保存具体的数据
const state = {sum:0 //当前的和
}
//准备getters对象——用于将state中的数据进行加工
const getters = {bigSum(){return state.sum * 10}
}//创建并暴露store
export default new Vuex.Store({actions,mutations,state,getters
})
效果:
总结:
getters
配置项的使用:
概念:当
state
中的数据需要经过加工后再使用时,可以使用getters
加工在
store.js
中追加getters
配置... const getters = {bigSum(state){return state.sum * 10} }//创建并暴露store export default new Vuex.Store({...getters })
组件中读取数据:
$store.getters.bigSum
5.4. 四个map方法的使用
5.4.1. mapState与mapGetters
src/store/index.js
:
//引入Vue核心库
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//应用Vuex插件
Vue.use(Vuex)//准备actions对象——响应组件中用户的动作
const actions = {addOdd(context,value){console.log("actions中的addOdd被调用了")if(context.state.sum % 2){context.commit('ADD',value)}},addWait(context,value){console.log("actions中的addWait被调用了")setTimeout(()=>{context.commit('ADD',value)},500)},
}
//准备mutations对象——修改state中的数据
const mutations = {ADD(state,value){state.sum += value},SUBTRACT(state,value){state.sum -= value}
}
//准备state对象——保存具体的数据
const state = {sum:0, //当前的和name:'JOJO',school:'尚硅谷',
}
//准备getters对象——用于将state中的数据进行加工
const getters = {bigSum(){return state.sum * 10}
}//创建并暴露store
export default new Vuex.Store({actions,mutations,state,getters
})
src/components/Count.vue
:
<template><div><h1>当前求和为:{{sum}}</h1><h3>当前求和的10倍为:{{bigSum}}</h3><h3>我是{{name}},我在{{school}}学习</h3><select v-model.number="n"><option value="1">1</option><option value="2">2</option><option value="3">3</option></select><button @click="increment">+</button><button @click="decrement">-</button><button @click="incrementOdd">当前求和为奇数再加</button><button @click="incrementWait">等一等再加</button></div>
</template><script>import {mapState,mapGetters} from 'vuex'export default {name:'Count',data() {return {n:1, //用户选择的数字}},methods: {increment(){this.$store.commit('ADD',this.n)},decrement(){this.$store.commit('SUBTRACT',this.n)},incrementOdd(){this.$store.dispatch('addOdd',this.n)},incrementWait(){this.$store.dispatch('addWait',this.n)},},computed:{ // 借助mapState生成计算属性(数组写法)// ...mapState(['sum','school','name']),// 借助mapState生成计算属性(对象写法)...mapState({sum:'sum',school:'school',name:'name'}),...mapGetters(['bigSum'])}}
</script><style>button{margin-left: 5px;}
</style>
效果:
总结:
mapState方法:用于帮助我们映射
state
中的数据computed: {//借助mapState生成计算属性:sum、school、subject(对象写法)...mapState({sum:'sum',school:'school',subject:'subject'}),//借助mapState生成计算属性:sum、school、subject(数组写法)...mapState(['sum','school','subject']), },
mapGetters方法:用于帮助我们映射
getters
中的数据computed: {//借助mapGetters生成计算属性:bigSum(对象写法)...mapGetters({bigSum:'bigSum'}),//借助mapGetters生成计算属性:bigSum(数组写法)...mapGetters(['bigSum']) },
5.4.2. mapActions与mapMutations
src/components/Count.vue
:
<template><div><h1>当前求和为:{{sum}}</h1><h3>当前求和的10倍为:{{bigSum}}</h3><h3>我是{{name}},我在{{school}}学习</h3><select v-model.number="n"><option value="1">1</option><option value="2">2</option><option value="3">3</option></select><button @click="increment(n)">+</button><button @click="decrement(n)">-</button><button @click="incrementOdd(n)">当前求和为奇数再加</button><button @click="incrementWait(n)">等一等再加</button></div>
</template><script>import {mapState,mapGetters,mapMutations,mapActions} from 'vuex'export default {name:'Count',data() {return {n:1, //用户选择的数字}},methods: {// 借助mapActions生成:increment、decrement(对象形式)...mapMutations({increment:'ADD',decrement:'SUBTRACT'}),// 借助mapActions生成:incrementOdd、incrementWait(对象形式)...mapActions({incrementOdd:'addOdd',incrementWait:'addWait'})},computed:{ // 借助mapState生成计算属性(数组写法)// ...mapState(['sum','school','name']),// 借助mapState生成计算属性(对象写法)...mapState({sum:'sum',school:'school',name:'name'}),...mapGetters(['bigSum'])}}
</script><style>button{margin-left: 5px;}
</style>
总结:
mapActions方法:用于帮助我们生成与
actions
对话的方法,即:包含$store.dispatch(xxx)
的函数methods:{//靠mapActions生成:incrementOdd、incrementWait(对象形式)...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})//靠mapActions生成:incrementOdd、incrementWait(数组形式)...mapActions(['jiaOdd','jiaWait']) }
mapMutations方法:用于帮助我们生成与
mutations
对话的方法,即:包含$store.commit(xxx)
的函数methods:{//靠mapActions生成:increment、decrement(对象形式)...mapMutations({increment:'JIA',decrement:'JIAN'}),//靠mapMutations生成:JIA、JIAN(对象形式)...mapMutations(['JIA','JIAN']), }
备注:
mapActions
与mapMutations
使用时,若需要传递参数,则需要在模板中绑定事件时传递好参数,否则参数是事件对象
5.5. 多组件共享数据
src/App.vue
:
<template><div class="container"><Count/><hr/><Person/></div>
</template><script>import Count from './components/Count'import Person from './components/Person'export default {name:'App',components:{Count,Person}}
</script>
src/store/index.js
:
//引入Vue核心库
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//应用Vuex插件
Vue.use(Vuex)//准备actions对象——响应组件中用户的动作
const actions = {addOdd(context,value){console.log("actions中的addOdd被调用了")if(context.state.sum % 2){context.commit('ADD',value)}},addWait(context,value){console.log("actions中的addWait被调用了")setTimeout(()=>{context.commit('ADD',value)},500)},
}
//准备mutations对象——修改state中的数据
const mutations = {ADD(state,value){state.sum += value},SUBTRACT(state,value){state.sum -= value},ADD_PERSON(state,value){console.log('mutations中的ADD_PERSON被调用了')state.personList.unshift(value)}
}
//准备state对象——保存具体的数据
const state = {sum:0, //当前的和name:'JOJO',school:'尚硅谷',personList:[{id:'001',name:'JOJO'}]
}
//准备getters对象——用于将state中的数据进行加工
const getters = {bigSum(){return state.sum * 10}
}//创建并暴露store
export default new Vuex.Store({actions,mutations,state,getters
})
src/components/Count.vue
:
<template><div><h1>当前求和为:{{sum}}</h1><h3>当前求和的10倍为:{{bigSum}}</h3><h3>我是{{name}},我在{{school}}学习</h3><h3 style="color:red">Person组件的总人数是:{{personList.length}}</h3><select v-model.number="n"><option value="1">1</option><option value="2">2</option><option value="3">3</option></select><button @click="increment(n)">+</button><button @click="decrement(n)">-</button><button @click="incrementOdd(n)">当前求和为奇数再加</button><button @click="incrementWait(n)">等一等再加</button></div>
</template><script>import {mapState,mapGetters,mapMutations,mapActions} from 'vuex'export default {name:'Count',data() {return {n:1, //用户选择的数字}},methods: {...mapMutations({increment:'ADD',decrement:'SUBTRACT'}),...mapActions({incrementOdd:'addOdd',incrementWait:'addWait'})},computed:{...mapState(['sum','school','name','personList']),,...mapGetters(['bigSum'])}}
</script><style>button{margin-left: 5px;}
</style>
src/components/Person.vue
:
<template><div><h1>人员列表</h1><h3 style="color:red">Count组件求和为:{{sum}}</h3><input type="text" placeholder="请输入名字" v-model="name"><button @click="add">添加</button><ul><li v-for="p in personList" :key="p.id">{{p.name}}</li></ul></div>
</template><script>import {nanoid} from 'nanoid'export default {name:'Person',data() {return {name:''}},computed:{personList(){return this.$store.state.personList},sum(){return this.$store.state.sum}},methods: {add(){const personObj = {id:nanoid(),name:this.name}this.$store.commit('ADD_PERSON',personObj)this.name = ''}}}
</script>
效果:
5.6. 模块化+命名空间
src/store/index.js
:
//引入Vue核心库
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//引入count
import countOptions from './count'
//引入person
import personOptions from './person'
//应用Vuex插件
Vue.use(Vuex)//创建并暴露store
export default new Vuex.Store({modules:{countAbout:countOptions,personAbout:personOptions,}
})
src/store/count.js
:
export default{namespaced:true,actions:{addOdd(context,value){console.log("actions中的addOdd被调用了")if(context.state.sum % 2){context.commit('ADD',value)}},addWait(context,value){console.log("actions中的addWait被调用了")setTimeout(()=>{context.commit('ADD',value)},500)}},mutations:{ADD(state,value){state.sum += value},SUBTRACT(state,value){state.sum -= value}},state:{sum:0, //当前的和name:'JOJO',school:'尚硅谷',},getters:{bigSum(state){return state.sum * 10}}
}
src/store/person.js
:
import axios from "axios"
import { nanoid } from "nanoid"export default{namespaced:true,actions:{addPersonWang(context,value){if(value.name.indexOf('王') === 0){context.commit('ADD_PERSON',value)}else{alert('添加的人必须姓王!')}},addPersonServer(context){axios.get('http://api.uixsj.cn/hitokoto/get?type=social').then(response => {context.commit('ADD_PERSON',{id:nanoid(),name:response.data})},error => {alert(error.message)})}},mutations:{ADD_PERSON(state,value){console.log('mutations中的ADD_PERSON被调用了')state.personList.unshift(value)}},state:{personList:[{id:'001',name:'JOJO'}]},getters:{firstPersonName(state){return state.personList[0].name}}
}
src/components/Count.vue
:
<template><div><h1>当前求和为:{{sum}}</h1><h3>当前求和的10倍为:{{bigSum}}</h3><h3>我是{{name}},我在{{school}}学习</h3><h3 style="color:red">Person组件的总人数是:{{personList.length}}</h3><select v-model.number="n"><option value="1">1</option><option value="2">2</option><option value="3">3</option></select><button @click="increment(n)">+</button><button @click="decrement(n)">-</button><button @click="incrementOdd(n)">当前求和为奇数再加</button><button @click="incrementWait(n)">等一等再加</button></div>
</template><script>import {mapState,mapGetters,mapMutations,mapActions} from 'vuex'export default {name:'Count',data() {return {n:1, //用户选择的数字}},methods: {...mapMutations('countAbout',{increment:'ADD',decrement:'SUBTRACT'}),...mapActions('countAbout',{incrementOdd:'addOdd',incrementWait:'addWait'})},computed:{...mapState('countAbout',['sum','school','name']),...mapGetters('countAbout',['bigSum']),...mapState('personAbout',['personList'])}}
</script><style>button{margin-left: 5px;}
</style>
src/components/Person.vue
:
<template><div><h1>人员列表</h1><h3 style="color:red">Count组件求和为:{{sum}}</h3><h3>列表中第一个人的名字是:{{firstPersonName}}</h3><input type="text" placeholder="请输入名字" v-model="name"><button @click="add">添加</button><button @click="addWang">添加一个姓王的人</button><button @click="addPerson">随机添加一个人</button><ul><li v-for="p in personList" :key="p.id">{{p.name}}</li></ul></div>
</template><script>import {nanoid} from 'nanoid'export default {name:'Person',data() {return {name:''}},computed:{personList(){return this.$store.state.personAbout.personList},sum(){return this.$store.state.countAbout.sum},firstPersonName(){return this.$store.getters['personAbout/firstPersonName']}},methods: {add(){const personObj = {id:nanoid(),name:this.name}this.$store.commit('personAbout/ADD_PERSON',personObj)this.name = ''},addWang(){const personObj = {id:nanoid(),name:this.name}this.$store.dispatch('personAbout/addPersonWang',personObj)this.name = '' },addPerson(){this.$store.dispatch('personAbout/addPersonServer')}},}
</script>
效果:
总结:
模块化+命名空间:
目的:让代码更好维护,让多种数据分类更加明确
修改
store.js
:const countAbout = {namespaced:true,//开启命名空间state:{x:1},mutations: { ... },actions: { ... },getters: {bigSum(state){return state.sum * 10}} }const personAbout = {namespaced:true,//开启命名空间state:{ ... },mutations: { ... },actions: { ... } }const store = new Vuex.Store({modules: {countAbout,personAbout} })
开启命名空间后,组件中读取
state
数据://方式一:自己直接读取 this.$store.state.personAbout.list //方式二:借助mapState读取: ...mapState('countAbout',['sum','school','subject']),
开启命名空间后,组件中读取
getters
数据://方式一:自己直接读取 this.$store.getters['personAbout/firstPersonName'] //方式二:借助mapGetters读取: ...mapGetters('countAbout',['bigSum'])
开启命名空间后,组件中调用
dispatch
://方式一:自己直接dispatch this.$store.dispatch('personAbout/addPersonWang',person) //方式二:借助mapActions: ...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
开启命名空间后,组件中调用
commit
://方式一:自己直接commit this.$store.commit('personAbout/ADD_PERSON',person) //方式二:借助mapMutations: ...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),
6. Vue Router路由管理器
6.1 相关理解
6.1.1 vue-router的理解
- vue 的一个插件库,专门用来实现SPA 应用
6.1.2 对SPA应用的理解
- 单页 Web 应用(single page web application,SPA)
- 整个应用只有一个完整的页面
- 点击页面中的导航链接不会刷新页面,只会做页面的局部更新
- 数据需要通过ajax请求获取
6.1.3 路由的理解
什么是路由?
- 一个路由就是一组映射关系(key - value)
- key 为路径,value 可能是 function 或 componen
路由分类
后端路由:
- 理解:value 是 function,用于处理客户端提交的请求
- 工作过程:服务器接收到一个请求时,根据请求路径找到匹配的函数来处理请求,返回响应数据
前端路由:
- 理解:value 是 component,用于展示页面内容
- 工作过程:当浏览器的路径改变时,对应的组件就会显示
6.2 基本路由
下载
vue-router
:npm i vue-router
src/router/index.js
:
//该文件专门用于创建整个应用的路由器
import VueRouter from "vue-router";
//引入组件
import Home from '../components/Home'
import About from '../components/About'//创建并暴露一个路由器
export default new VueRouter({routes:[{path:'/about',component:About},{path:'/home',component:Home}]
})
src/main.js
:
import Vue from 'vue'
import App from './App.vue'
import VueRouter from 'vue-router'
import router from './router'Vue.config.productionTip = false
Vue.use(VueRouter)new Vue({el:"#app",render: h => h(App),router
})
src/App.vue
:
<template><div><div class="row"><div class="col-xs-offset-2 col-xs-8"><div class="page-header"><h2>Vue Router Demo</h2></div></div></div><div class="row"><div class="col-xs-2 col-xs-offset-2"><div class="list-group"><!-- 原始html中我们使用a标签实现页面跳转 --><!-- <a class="list-group-item active" href="./about.html">About</a><a class="list-group-item" href="./home.html">Home</a> --><!-- Vue中借助router-link标签实现路由的切换 --><router-link class="list-group-item" active-class="active" to="/about"> About</router-link><router-link class="list-group-item" active-class="active" to="/home">Home</router-link></div></div><div class="col-xs-6"><div class="panel"><div class="panel-body"><!-- 指定组件的呈现位置 --><router-view></router-view></div></div></div></div></div>
</template><script>export default {name:'App',}
</script>
src/components/Home.vue
:
<template><h2>我是Home组件的内容</h2>
</template><script>export default {name:'Home'}
</script>
src/components/About.vue
:
<template><h2>我是About组件的内容</h2>
</template><script>export default {name:'About'}
</script>
总结:
安装
vue-router
,命令:npm i vue-router
应用插件:
Vue.use(VueRouter)
编写router配置项:
//引入VueRouter import VueRouter from 'vue-router' //引入Luyou 组件 import About from '../components/About' import Home from '../components/Home'//创建router实例对象,去管理一组一组的路由规则 const router = new VueRouter({routes:[{path:'/about',component:About},{path:'/home',component:Home}] })//暴露router export default router
实现切换(
active-class
可配置高亮样式):<router-link active-class="active" to="/about">About</router-link>
指定展示位:
<router-view></router-view>
6.3. 几个注意事项
路由组件通常存放在
pages
文件夹,一般组件通常存放在components
文件夹比如上一节的案例就可以修改为:
src/pages/Home.vue
:<template><h2>我是Home组件的内容</h2> </template><script>export default {name:'Home'} </script>
src/pages/About.vue
:<template><h2>我是About组件的内容</h2> </template><script>export default {name:'About'} </script>
src/router/index.js
://该文件专门用于创建整个应用的路由器 import VueRouter from "vue-router"; //引入组件 import Home from '../pages/Home' import About from '../pages/About'//创建并暴露一个路由器 export default new VueRouter({routes:[{path:'/about',component:About},{path:'/home',component:Home}] })
src/components/Banner.vue
:<template><div class="col-xs-offset-2 col-xs-8"><div class="page-header"><h2>Vue Router Demo</h2></div></div> </template><script>export default {name:'Banner'} </script>
src/App.vue
:<template><div><div class="row"><Banner/></div><div class="row"><div class="col-xs-2 col-xs-offset-2"><div class="list-group"><!-- 原始html中我们使用a标签实现页面跳转 --><!-- <a class="list-group-item active" href="./about.html">About</a><a class="list-group-item" href="./home.html">Home</a> --><!-- Vue中借助router-link标签实现路由的切换 --><router-link class="list-group-item" active-class="active" to="/about">About</router-link><router-link class="list-group-item" active-class="active" to="/home">Home</router-link></div></div><div class="col-xs-6"><div class="panel"><div class="panel-body"><!-- 指定组件的呈现位置 --><router-view></router-view></div></div></div></div></div> </template><script>import Banner from './components/Banner.vue'export default {name:'App',components:{Banner}} </script>
通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载
每个组件都有自己的
$route
属性,里面存储着自己的路由信息整个应用只有一个router,可以通过组件的
$router
属性获取到
6.4. 多级路由
src/pages/Home.vue
:
<template><div><h2>Home组件内容</h2><div><ul class="nav nav-tabs"><li><router-link class="list-group-item" active-class="active" to="/home/news"> News</router-link></li><li><router-link class="list-group-item" active-class="active" to="/home/message">Message</router-link></li></ul><router-view></router-view></div></div>
</template><script>export default {name:'Home'}
</script>
src/pages/News.vue
:
<template><ul><li>news001</li><li>news002</li><li>news003</li></ul>
</template><script>export default {name:'News'}
</script>
src/pages/Message.vue
:
<template><ul><li><a href="/message1">message001</a> </li><li><a href="/message2">message002</a> </li><li><a href="/message/3">message003</a> </li></ul>
</template><script>export default {name:'News'}
</script>
src/router/index.js
:
//该文件专门用于创建整个应用的路由器
import VueRouter from "vue-router";
//引入组件
import Home from '../pages/Home'
import About from '../pages/About'
import News from '../pages/News'
import Message from '../pages/Message'//创建并暴露一个路由器
export default new VueRouter({routes:[{path:'/about',component:About},{path:'/home',component:Home,children:[{path:'news',component:News},{path:'message',component:Message}]}]
})
效果:
总结:
配置路由规则,使用children配置项:
routes:[{path:'/about',component:About,},{path:'/home',component:Home,children:[ //通过children配置子级路由{path:'news', //此处一定不要写:/newscomponent:News},{path:'message', //此处一定不要写:/messagecomponent:Message}]} ]
跳转(要写完整路径):
<router-link to="/home/news">News</router-link>
6.5. 路由的query参数
src/router.index.js
:
//该文件专门用于创建整个应用的路由器
import VueRouter from "vue-router";
//引入组件
import Home from '../pages/Home'
import About from '../pages/About'
import News from '../pages/News'
import Message from '../pages/Message'
import Detail from '../pages/Detail'//创建并暴露一个路由器
export default new VueRouter({routes:[{path:'/about',component:About},{path:'/home',component:Home,children:[{path:'news',component:News},{path:'message',component:Message,children:[{path:'detail',component:Detail}]}]}]
})
src/pages/Detail.vue
:
<template><ul><li>消息编号:{{$route.query.id}}</li><li>消息标题:{{$route.query.title}}</li></ul>
</template><script>export default {name:'Detail'}
</script>
src/pages/Message.vue
:
<template><div><ul><li v-for="m in messageList" :key="m.id"><!-- 跳转路由并携带query参数,to的字符串写法 --><!-- <router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">{{m.title}}</router-link> --><!-- 跳转路由并携带query参数,to的对象写法 --><router-link :to="{path:'/home/message/detail',query:{id:m.id,title:m.title}}">{{m.title}}</router-link> </li></ul><hr/><router-view></router-view></div>
</template><script>export default {name:'News',data(){return{messageList:[{id:'001',title:'消息001'},{id:'002',title:'消息002'},{id:'003',title:'消息003'}]}}}
</script>
效果:
总结:
传递参数:
<!-- 跳转并携带query参数,to的字符串写法 --> <router-link :to="/home/message/detail?id=666&title=你好">跳转</router-link><!-- 跳转并携带query参数,to的对象写法 --> <router-link :to="{path:'/home/message/detail',query:{id:666,title:'你好'} }">跳转</router-link>
接收参数:
$route.query.id $route.query.title
6.6. 命名路由
src/router/index.js
:
//该文件专门用于创建整个应用的路由器
import VueRouter from "vue-router";
//引入组件
import Home from '../pages/Home'
import About from '../pages/About'
import News from '../pages/News'
import Message from '../pages/Message'
import Detail from '../pages/Detail'//创建并暴露一个路由器
export default new VueRouter({routes:[{path:'/about',component:About},{path:'/home',component:Home,children:[{path:'news',component:News},{path:'message',component:Message,children:[{//name配置项为路由命名name:'xiangqing',path:'detail',component:Detail}]}]}]
})
src/pages/Message.vue
:
<template><div><ul><li v-for="m in messageList" :key="m.id"><!-- 跳转路由并携带query参数,to的字符串写法 --><!-- <router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">{{m.title}}</router-link> --><!-- 跳转路由并携带query参数,to的对象写法 --><router-link :to="{//使用name进行跳转name:'xiangqing',query:{id:m.id,title:m.title}}">{{m.title}}</router-link> </li></ul><hr/><router-view></router-view></div>
</template><script>export default {name:'News',data(){return{messageList:[{id:'001',title:'消息001'},{id:'002',title:'消息002'},{id:'003',title:'消息003'}]}}}
</script>
总结:
命名路由:
作用:可以简化路由的跳转
如何使用:
给路由命名:
{path:'/demo',component:Demo,children:[{path:'test',component:Test,children:[{name:'hello' //给路由命名path:'welcome',component:Hello,}]}] }
简化跳转:
<!--简化前,需要写完整的路径 --> <router-link to="/demo/test/welcome">跳转</router-link><!--简化后,直接通过名字跳转 --> <router-link :to="{name:'hello'}">跳转</router-link><!--简化写法配合传递参数 --> <router-link :to="{name:'hello',query:{id:666,title:'你好'}}" >跳转</router-link>
6.7. 路由的params参数
src/router/index.js
:
//该文件专门用于创建整个应用的路由器
import VueRouter from "vue-router";
//引入组件
import Home from '../pages/Home'
import About from '../pages/About'
import News from '../pages/News'
import Message from '../pages/Message'
import Detail from '../pages/Detail'//创建并暴露一个路由器
export default new VueRouter({routes:[{path:'/about',component:About},{path:'/home',component:Home,children:[{path:'news',component:News},{path:'message',component:Message,children:[{name:'xiangqing',path:'detail/:id/:title',//使用占位符声明接收params参数component:Detail}]}]}]
})
src/pages/Message.vue
:
<template><div><ul><li v-for="m in messageList" :key="m.id"><!-- 跳转路由并携带params参数,to的字符串写法 --><!-- <router-link :to="`/home/message/detail/${m.id}/${m.title}`">{{m.title}}</router-link> --><!-- 跳转路由并携带params参数,to的对象写法 --><router-link :to="{name:'xiangqing',params:{id:m.id,title:m.title}}">{{m.title}}</router-link> </li></ul><hr/><router-view></router-view></div>
</template><script>export default {name:'News',data(){return{messageList:[{id:'001',title:'消息001'},{id:'002',title:'消息002'},{id:'003',title:'消息003'}]}}}
</script>
src/pages/Detail.vue
:
<template><ul><li>消息编号:{{$route.params.id}}</li><li>消息标题:{{$route.params.title}}</li></ul>
</template><script>export default {name:'Detail'}
</script>
总结:
配置路由,声明接收
params
参数:{path:'/home',component:Home,children:[{path:'news',component:News},{component:Message,children:[{name:'xiangqing',path:'detail/:id/:title', //使用占位符声明接收params参数component:Detail}]}] }
传递参数:
<!-- 跳转并携带params参数,to的字符串写法 --> <router-link :to="/home/message/detail/666/你好">跳转</router-link><!-- 跳转并携带params参数,to的对象写法 --> <router-link :to="{name:'xiangqing',params:{id:666,title:'你好'}}" >跳转</router-link>
特别注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!
接收参数:
$route.params.id $route.params.title
6.8. 路由的props配置
src/pages/Message.vue
:
<template><div><ul><li v-for="m in messageList" :key="m.id"><router-link :to="{name:'xiangqing',params:{id:m.id,title:m.title}}">{{m.title}}</router-link> </li></ul><hr/><router-view></router-view></div>
</template><script>export default {name:'News',data(){return{messageList:[{id:'001',title:'消息001'},{id:'002',title:'消息002'},{id:'003',title:'消息003'}]}}}
</script>
src/router/index.js
:
//该文件专门用于创建整个应用的路由器
import VueRouter from "vue-router";
//引入组件
import Home from '../pages/Home'
import About from '../pages/About'
import News from '../pages/News'
import Message from '../pages/Message'
import Detail from '../pages/Detail'//创建并暴露一个路由器
export default new VueRouter({routes:[{path:'/about',component:About},{path:'/home',component:Home,children:[{path:'news',component:News},{path:'message',component:Message,children:[{name:'xiangqing',path:'detail/:id/:title',component:Detail,//props的第一种写法,值为对象,该对象中的所有key-value都会以props的形式传给Detail组件。// props:{a:1,b:'hello'}//props的第二种写法,值为布尔值,若布尔值为真,就会把该路由组件收到的所有params参数,以props的形式传给Detail组件。// props:true//props的第三种写法,值为函数props($route){return {id:$route.params.id,title:$route.params.title,}}}]}]}]
})
src/pages/Detail.vue
:
<template><ul><li>消息编号:{{id}}</li><li>消息标题:{{title}}</li></ul>
</template><script>export default {name:'Detail',props:['id','title']}
</script>
总结:
作用:让路由组件更方便的收到参数
{name:'xiangqing',path:'detail/:id',component:Detail,//第一种写法:props值为对象,该对象中所有的key-value的组合最终都会通过props传给Detail组件// props:{a:900}//第二种写法:props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给Detail组件// props:true//第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件props(route){return {id:route.query.id,title:route.query.title}} }
6.9. 路由跳转的replace方法
src/pages/Home.vue
:
<template><div><h2>Home组件内容</h2><div><ul class="nav nav-tabs"><li><router-link replace class="list-group-item" active-class="active" to="/home/news">News</router-link></li><li><router-link replace class="list-group-item" active-class="active" to="/home/message">Message</router-link></li></ul><router-view></router-view></div></div>
</template><script>export default {name:'Home'}
</script>
总结:
- 作用:控制路由跳转时操作浏览器历史记录的模式
- 浏览器的历史记录有两种写入方式:
push
和replace
,其中push
是追加历史记录,replace
是替换当前记录。路由跳转时候默认为push
方式 - 开启
replace
模式:<router-link replace ...>News</router-link>
6.10. 编程式路由导航
src/components/Banner.vue
:
<template><div class="col-xs-offset-2 col-xs-8"><div class="page-header"><h2>Vue Router Demo</h2><button @click="back">后退</button><button @click="forward">前进</button><button @click="test">测试一下go</button></div></div>
</template><script>export default {name:'Banner',methods:{back(){this.$router.back()},forward(){this.$router.forward()},test(){this.$router.go(3)}},}
</script>
src/pages/Message.vue
:
<template><div><ul><li v-for="m in messageList" :key="m.id"><router-link :to="{name:'xiangqing',params:{id:m.id,title:m.title}}">{{m.title}}</router-link><button @click="showPush(m)">push查看</button><button @click="showReplace(m)">replace查看</button></li></ul><hr/><router-view></router-view></div>
</template><script>export default {name:'News',data(){return{messageList:[{id:'001',title:'消息001'},{id:'002',title:'消息002'},{id:'003',title:'消息003'}]}},methods:{showPush(m){this.$router.push({name:'xiangqing',query:{id:m.id,title:m.title}})},showReplace(m){this.$router.replace({name:'xiangqing',query:{id:m.id,title:m.title}})}}}
</script>
src/router/index.js
:
//该文件专门用于创建整个应用的路由器
import VueRouter from "vue-router";
//引入组件
import Home from '../pages/Home'
import About from '../pages/About'
import News from '../pages/News'
import Message from '../pages/Message'
import Detail from '../pages/Detail'//创建并暴露一个路由器
export default new VueRouter({routes:[{path:'/about',component:About},{path:'/home',component:Home,children:[{path:'news',component:News},{path:'message',component:Message,children:[{name:'xiangqing',path:'detail',component:Detail,props($route){return {id:$route.query.id,title:$route.query.title,}}}]}]}]
})
src/pages/Detail.vue
:
<template><ul><li>消息编号:{{id}}</li><li>消息标题:{{title}}</li></ul>
</template><script>export default {name:'Detail',props:['id','title']}
</script>
效果:
总结:
作用:不借助
<router-link>
实现路由跳转,让路由跳转更加灵活具体编码:
this.$router.push({name:'xiangqing',params:{id:xxx,title:xxx} })this.$router.replace({name:'xiangqing',params:{id:xxx,title:xxx} }) this.$router.forward() //前进 this.$router.back() //后退 this.$router.go() //可前进也可后退
6.11. 缓存路由组件
src/pages/News.vue
:
<template><ul><li>news001 <input type="text"></li><li>news002 <input type="text"></li><li>news003 <input type="text"></li></ul>
</template><script>export default {name:'News'}
</script>
src/pages/Home.vue
:
<template><div><h2>Home组件内容</h2><div><ul class="nav nav-tabs"><li><router-link replace class="list-group-item" active-class="active" to="/home/news">News</router-link></li><li><router-link replace class="list-group-item" active-class="active" to="/home/message">Message</router-link></li></ul><keep-alive include="News"><router-view></router-view></keep-alive></div></div>
</template><script>export default {name:'Home'}
</script>
效果:
总结:
作用:让不展示的路由组件保持挂载,不被销毁
具体编码:
//缓存一个路由组件 <keep-alive include="News"> //include中写想要缓存的组件名,不写表示全部缓存<router-view></router-view> </keep-alive>//缓存多个路由组件 <keep-alive :include="['News','Message']"> <router-view></router-view> </keep-alive>
6.12. activated和deactivated
`src/pages/News.vue`:
<template><ul><li :style="{opacity}">欢迎学习vue</li><li>news001 <input type="text"></li><li>news002 <input type="text"></li><li>news003 <input type="text"></li></ul>
</template><script>export default {name:'News',data(){return{opacity:1}},activated(){console.log('News组件被激活了')this.timer = setInterval(() => {this.opacity -= 0.01if(this.opacity <= 0) this.opacity = 1},16)},deactivated(){console.log('News组件失活了')clearInterval(this.timer)}}
</script>
效果:
总结:
activated
和deactivated
是路由组件所独有的两个钩子,用于捕获路由组件的激活状态- 具体使用:
activated
路由组件被激活时触发deactivated
路由组件失活时触发
6.13. 路由守卫
6.13.1. 全局路由守卫
src/router/index.js
:
//该文件专门用于创建整个应用的路由器
import VueRouter from "vue-router";
//引入组件
import Home from '../pages/Home'
import About from '../pages/About'
import News from '../pages/News'
import Message from '../pages/Message'
import Detail from '../pages/Detail'//创建一个路由器
const router = new VueRouter({routes:[{name:'guanyv',path:'/about',component:About,meta:{title:'关于'}},{name:'zhuye',path:'/home',component:Home,meta:{title:'主页'},children:[{name:'xinwen',path:'news',component:News,meta:{isAuth:true,title:'新闻'}},{name:'xiaoxi',path:'message',component:Message,meta:{isAuth:true,title:'消息'},children:[{name:'xiangqing',path:'detail',component:Detail,meta:{isAuth:true,title:'详情'},props($route){return {id:$route.query.id,title:$route.query.title,}}}]}]}]
})//全局前置路由守卫————初始化的时候、每次路由切换之前被调用
router.beforeEach((to,from,next) => {console.log('前置路由守卫',to,from)if(to.meta.isAuth){if(localStorage.getItem('school')==='atguigu'){next()}else{alert('学校名不对,无权限查看!')}}else{next()}
})//全局后置路由守卫————初始化的时候被调用、每次路由切换之后被调用
router.afterEach((to,from)=>{console.log('后置路由守卫',to,from)document.title = to.meta.title || '硅谷系统'
})//导出路由器
export default router
6.13.2. 独享路由守卫
src/router/index.js
:
//该文件专门用于创建整个应用的路由器
import VueRouter from "vue-router";
//引入组件
import Home from '../pages/Home'
import About from '../pages/About'
import News from '../pages/News'
import Message from '../pages/Message'
import Detail from '../pages/Detail'//创建一个路由器
const router = new VueRouter({routes:[{name:'guanyv',path:'/about',component:About,meta:{title:'关于'}},{name:'zhuye',path:'/home',component:Home,meta:{title:'主页'},children:[{name:'xinwen',path:'news',component:News,meta:{title:'新闻'},//独享守卫,特定路由切换之后被调用beforeEnter(to,from,next){console.log('独享路由守卫',to,from)if(localStorage.getItem('school') === 'atguigu'){next()}else{alert('暂无权限查看')}}},{name:'xiaoxi',path:'message',component:Message,meta:{title:'消息'},children:[{name:'xiangqing',path:'detail',component:Detail,meta:{title:'详情'},props($route){return {id:$route.query.id,title:$route.query.title,}}}]}]}]
})//全局后置路由守卫————初始化的时候被调用、每次路由切换之后被调用
router.afterEach((to,from)=>{console.log('后置路由守卫',to,from)document.title = to.meta.title || '硅谷系统'
})//导出路由器
export default router
6.13.3. 组件内路由守卫
src/pages/About.vue
:
<template><h2>我是About组件的内容</h2>
</template><script>export default {name:'About',//通过路由规则,离开该组件时被调用beforeRouteEnter (to, from, next) {console.log('About--beforeRouteEnter',to,from)if(localStorage.getItem('school')==='atguigu'){next()}else{alert('学校名不对,无权限查看!')}},//通过路由规则,离开该组件时被调用beforeRouteLeave (to, from, next) {console.log('About--beforeRouteLeave',to,from)next()}}
</script>
总结:
作用:对路由进行权限控制
分类:全局守卫、独享守卫、组件内守卫
全局守卫:
//全局前置守卫:初始化时执行、每次路由切换前执行 router.beforeEach((to,from,next)=>{console.log('beforeEach',to,from)if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制if(localStorage.getItem('school') === 'atguigu'){ //权限控制的具体规则next() //放行}else{alert('暂无权限查看')}}else{next() //放行} })//全局后置守卫:初始化时执行、每次路由切换后执行 router.afterEach((to,from) => {console.log('afterEach',to,from)if(to.meta.title){ document.title = to.meta.title //修改网页的title}else{document.title = 'vue_test'} })
独享守卫:
beforeEnter(to,from,next){console.log('beforeEnter',to,from)if(localStorage.getItem('school') === 'atguigu'){next()}else{alert('暂无权限查看')} }
组件内守卫:
//进入守卫:通过路由规则,进入该组件时被调用 beforeRouteEnter (to, from, next) {...}, //离开守卫:通过路由规则,离开该组件时被调用 beforeRouteLeave (to, from, next) {...},
6.14. 路由器的两种工作模式
对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值
hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器
hash模式:
- 地址中永远带着#号,不美观
- 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法
- 兼容性较好
history模式:
- 地址干净,美观
- 兼容性和hash模式相比略差
- 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题
7. Vue UI组件库
7.1 常用UI组件库
7.1.1 移动端常用UI组件库
- Vant
- Cube UI
- Mint UI
7.1.2. PC端常用UI组件库
- Element UI
- IView UI
7.2. element-ui基本使用
安装 element-ui:
npm i element-ui -S
src/main.js
:import Vue from 'vue' import App from './App.vue' //引入ElementUI组件库 import ElementUI from 'element-ui'; //引入ElementUI全部样式 import 'element-ui/lib/theme-chalk/index.css';Vue.config.productionTip = false //使用ElementUI Vue.use(ElementUI)new Vue({el:"#app",render: h => h(App), })
src/App.vue
:<template><div><br><el-row><el-button icon="el-icon-search" circle></el-button><el-button type="primary" icon="el-icon-edit" circle></el-button><el-button type="success" icon="el-icon-check" circle></el-button><el-button type="info" icon="el-icon-message" circle></el-button><el-button type="warning" icon="el-icon-star-off" circle></el-button><el-button type="danger" icon="el-icon-delete" circle></el-button></el-row></div> </template><script>export default {name:'App',} </script>
效果:
7.3. element-ui按需引入
安装 babel-plugin-component:
npm install babel-plugin-component -D
修改
babel-config-js
:module.exports = {presets: ['@vue/cli-plugin-babel/preset',["@babel/preset-env", { "modules": false }]],plugins: [["component",{"libraryName": "element-ui","styleLibraryName": "theme-chalk"}]] }
src/main.js
:import Vue from 'vue' import App from './App.vue' //按需引入 import { Button,Row } from 'element-ui'Vue.config.productionTip = falseVue.component(Button.name, Button); Vue.component(Row.name, Row); /* 或写为* Vue.use(Button)* Vue.use(Row)*/new Vue({el:"#app",render: h => h(App), })
8. Vue 3
咕咕咕~
Vue学习笔记(尚硅谷天禹老师)相关推荐
- Vue学习笔记(张天禹老师Vue)
文章目录 1 Vue 基础 1.1 介绍 Vue 1.2 Vue 的安装 1.3 Hello 小案例 1.3 Hello 案例分析 1.4 模板语法 1.5 数据绑定 1.6 el 和 data 两种 ...
- maven学习笔记——尚硅谷
文章目录 maven学习笔记--尚硅谷 第一章 Maven概述 第一节 为什么要学习Maven 1.Maven 作为依赖管理工具 1.1 jar 包的规模 1.2 jar 包的来源 1.3 jar 包 ...
- MySQL学习笔记——尚硅谷李玉婷经典版MySQL基础笔记(一)
MySQL学习笔记--尚硅谷李玉婷经典版MySQL基础笔记(一) MySQL学习笔记目录 MySQL学习笔记--尚硅谷李玉婷经典版MySQL基础笔记(一) 一.基础知识 1.MySQL的语法规范 2. ...
- 尚硅谷vue笔记 详细讲解版(尚硅谷 天禹老师)
视频:[尚硅谷Vue2.0+Vue3.0全套教程丨vuejs从入门到精通]https://www.bilibili.com/video/BV1Zy4y1K7SH?vd_source=10e3dfac9 ...
- Vue2【尚硅谷--天禹老师】:Vue核心
目录 1. Vue核心 1.1 介绍与描述 1.2 Vue的特点 1.3 与其他JS框架的关联 1.4 Vue官网的使用指南 2.搭建Vue开发环境 2.1 直接使用 2.2 Hello小案例 2. ...
- 尚硅谷天禹老师Vue2.0笔记
笔记 关于不同版本的 Vue: vue.js 与 vue.runtime.xxx.js 的区别: vue.js是完整版的Vue,包含 核心功能 + 模板解析器 vue.runtime.xxx.js 是 ...
- Rabbitmq学习笔记(尚硅谷2021)
Rabbitmq学习笔记 (尚硅谷) 1.MQ 的概念 1.1 什么是 MQ? 1.2 为什么要用 MQ? 削峰 解耦 异步 1.3 MQ 的分类 ActiveMQ Kafka RocketMQ Ra ...
- 【HBase学习笔记-尚硅谷-Java API shell命令 谷粒微博案例】
HBase学习笔记 HBase 一.HBase简介 1.HBase介绍 2.HBase的逻辑结构和物理结构 3.数据模型 4.基本架构 二.快速入门 1.配置HBase 2.命令 三.API 1.获取 ...
- Java学习笔记 | 尚硅谷项目三详解
该笔记基于B站视频:尚硅谷Java入门视频教程 目录 1,目标 2,需求说明 2.1,功能实现 2.1,菜单显示 2.2,添加功能 2.3,删除功能 2.3,查看团队成员 3,软件设计结构 4,具体实 ...
- 尚硅谷 天禹老师 Vue学习笔记总计(自己复习用)
004 Vue环境搭建 如何关掉这2个提示? 关闭第一个提示,下载Vue的开发者工具 安装 - Vue.js Vue开发者工具GitHub下载地址: https://github.com/vuejs/ ...
最新文章
- RelativeLayout布局,不希望文本盖住其他组件
- cocos2d-x-3.2 lua命名空间前缀
- 2020 年微服务项目活跃度报告
- TokuDB存储引擎
- Altium AD20批量修改丝印大小、更改丝印字体、丝印显示中文、更改位号丝印 相对元件的位置
- go语言 Accept error: accept tcp [::]:5551: too many open files;
- windows10(专业版和家庭版)---禁止自动更新系统
- ubuntu系统使用命令行播放MP3歌曲
- instead of触发器实现复杂视图dml和应用逻辑
- 【云计算】docker registry v2简介
- 浏览器必备6大宝藏网站,再也不用求资源!良心安利速速收藏
- Mstar的Monitor方案笔记(五)——Gamma
- 信道编码中卷积编码器的设计
- 小爱音箱 电脑 麦克风_颜值音质皆出色,还有丰富功能,小米小爱音箱体验
- 计算机老师中专教学论文,职业中专计算机教学思考论文
- 【中文】【吴恩达课后编程作业】Course 5 - 序列模型 - 第三周作业 - 机器翻译与触发词检测
- 移动共享开发(二)各平台SSO(免登录)配置 微信和微信朋友圈、QQ、QQ空间、新浪微博、腾讯微博、人人
- Django整合微信h5支付
- 电脑上做的ppt拿到别的电脑或手机上播放的时候字体错位的解决方法
- ALV GRID类型