0.资料地址
0.1视频地址
https://www.bilibili.com/video/BV1Zy4y1K7SH?p=4&spm_id_from=pageDriver
0.2笔记地址
https://blog.csdn.net/weixin_44972008/category_10622253.html
1.初识Vue
1.1特点
- 组件化模式
每一个模块一个vue组件,每一个vue都有html+css+js
- 声明式编码
不需要直接使用Document操作
- 使用虚拟DOM+优秀比较算法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MzmpLXTC-1646275953129)(https://secure2.wostatic.cn/static/jSeZEipadsVdfmFrfTg6Pd/image.png?auth_key=1644978426-h9ismKpgtrZDWT7MHTK1a2-0-e8320e19c82a14efeb1f472062fb51b9&image_process=resize,w_1516/format,webp)]
主要用于更新数据中,如下图,已存在DOM经过比较无变化,则不更新,只更新新增的
1.2安装
- 地址
https://cn.vuejs.org/v2/guide/installation.html#%E7%9B%B4%E6%8E%A5%E7%94%A8-lt-script-gt-%E5%BC%95%E5%85%A5
//脚手架安装npm,先不使用
//使用直接用 <script> 引入
//建议安装开发版本,包含完整的警告和调试模式
1.3使用
- 创建项目,引入vue.js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<!-- 引入vue -->
<script src="../js/vue.js"></script>
</head>
<body>
</body>
</html>
- 右键浏览器打开,打开控制台console
两个报错及解决方式:
1)没有vue开发者工具
1、下载vue_dev_tools.crx开发者工具
2、打开浏览器--扩展程序,打开开发者模式,将文件拖到里面
注意;
如果错误提示还没有消失,
点击扩展程序的详情,打开【无痕使用】以及【允许访问文件网址】选项
- 2)开发环境vue提示
1、打开vue官网,选择学习--API--全局配置--productionTip
https://cn.vuejs.org/v2/api/
2.配置
<body>
<script>
Vue.config.productionTip = false
</script>
</body>
1.4案例
- 代码
<!--
1.想让vue工作就需要创建vue对象,且传入参数
2.vue接管的元素依旧符合html规范,但是要同时满足vue语法
3.vue接管的元素叫root容器,内叫做【vue模板】
-->
<body>
<div id="root">
<h1>1111,{{name}}</h1><!--使用双花括号作为vue的数据变量,范围仅限vue接管的元素-->
</div>
<script>
Vue.config.productionTip = false //关闭生产提示
const x = new Vue({
el:'#root', //el指定哪个元素作为vue的使用元素,也可以用js找到该元素
data:{ //暂时使用data作为数据源
name: 'hello'
}
})
</script>
</body>
- 注意事项
1、容器与Vue实例 一一对应
2、{{}}可以是【vue实例中的数据】,也可以是【js表达式】,vm及原型所有东西也可以用
例如:Date.now(), a, a+b, fun(a), a===b?'a':'b'等
3、data数据变化,对应页面数据随之改变
2.基础语法
2.1 模板语法
- 插值语法&指令语法
1.插值语法
# {{}}
2.指令语法
# v-bind:xxx='yyy' // yyy会作为表达式解析执行
# 简写::xxx='yyy'
使用
# 插值语法一般用在【标签体】里面
# 指令语法一般用于【标签】中
- 代码
<div id="root">
<!-- 1 插值 -->
<h1>1111,{{name}}</h1>
<!-- 2 指令 -->
<!-- <a v-bind:href="url"></a> -->
<a :href="url">跳转</a>
</div>
<script>
new Vue({
el:'#root',
data:{
name: 'hello',
url:'http://www.baidu.com/'
}
})
</script>
2.2 数据绑定
1)单向绑定&双向绑定
<div id="root">
<!-- 单项数据绑定:<input type="text" v-bind:value="name"><br/> -->
单项数据绑定:<input type="text" :value="name"><br/>
<!-- 双向数据绑定:<input type="text" v-model:value="name"> -->
双向数据绑定:<input type="text" v-model="name">
</div>
<script>
const vm = new Vue({
el:'#root',
data:{
name: 'hello',
}
})
</script>
2)总结
1.单向绑定(v-bind):
# 数据只能从data流向页面
2.双向绑定:
# 数据不仅可以从data流向页面,也可以从页面流向data
注意:
1.双向绑定一般用于表单类元素(input,select等) #标签中必需要有value属性
2.v-model默认收集value值,可以简写# v-model:value --> v-model
2.3 el&data两种写法
1)el
<script>
Vue.config.productionTip = false
const vm = new Vue({
//el:'#root', //第一种写法
})
x.$mount('#root'), //第二种写法
</script>
2)data
<script>
const vm = new Vue({
//第一种 对象式
// data:{
// name: 'hello',
// }
//第二种 函数式
data:function() { //:function可以省略
return {
name: 'hello',
}
},
})
</script>
3)注意
1.组件使用时,data必须使用函数式
2.由Vue管理的函数,一定不要写成箭头式,箭头式指的是window,普通函数指的是Vue
2.4 MVVM模型
1)介绍
- M 模型(Model) :data中的数据
- V 视图(View) :模板代码(不是静态页面) (两个语法:指令,大括号表达式)
- VM viewModel: 视图模型(Vue的实例)
- Dom Listeners (Dom 监听)
- Data Bindings (数据绑定)
发现:
- data中所有的属性,最后都出现在了vm身上。
- vm身上所有的属性 及 Vue原型上所有属性,在Vue模板中都可以直接使用。
MVVM 本质上是 MVC (Model-View- Controller)的改进版。即模型-视图-视图模型。
# 模型model指的是后端传递的数据,
# 视图view指的是所看到的页面。
#视图模型viewModel是 mvvm 模式的核心,它是连接 view 和 model 的桥梁。它有两个方向:
1.将模型转化成视图,即将后端传递的数据转化成所看到的页面。实现的方式是:数据绑定
2.将视图转化成模型,即将所看到的页面转化成后端的数据。实现的方式是:DOM 事件监听
这两个方向都实现的,我们称之为数据的双向绑定
2.5数据代理
1)方法Object.defineProperties()
- 用处(对象添加属性)
1.数据劫持
2.数据监听
3.计算属性
- 代码
<script>
let number = 19
let person = {
name:'monkey',
// age:16
}
Object.defineProperty(person, 'age', {
// value:20,
// enumerable:true, //是否可以被枚举(遍历里面数据),默认false
// writable:true, //是否可以被修改
// configurable:true, //是否可以被删除
get(){
return number
},
set(value){
number = value
}
})
</script>
2)数据代理
1.vue数据代理
通过vm对象来代理data对象中的属性进行操作(读、写)
2.好处
更加方便操作data中的数据
3.【原理】
# 通过Object.defineProperty()把data对象添加到vm上
# 每一个属性都添加到vm上的属性,都指定一个getter/setter
# 使用getter/setter去操作(读/写)data中的数据
2.6事件处理
1)事件使用
<div id="root">
<!-- <button v-on:click="show1"></button> 可以简写[v-on: --> @} -->
<button @click="show1()">不带参数</button>
<button @click="show2($event, 66)">带参数</button>
</div>
<script>
const vm = new Vue({
el:'#root',
methods: {
show1(){
console.log("123")
},
show2(envent, number){
console.log(number)
console.log(event.target.innerText)
}
},
})
</script>
总结
1.使用v-on:xxx 或 @xxx 绑定事件,其中xxx是事件名;
2.事件的回调需要配置在methods对象中,最终会在vm上;
3.methods中配置的函数,不要用箭头函数!否则this就不是vm了;
4.methods中配置的函数,都是被Vue所管理的函数,this的指向是vm 或 组件实例对象;
5.@click="demo"和 @click="demo($event)"效果一致,但后者可以传参;
2)事件修饰符
- 介绍
1.prevent:阻止默认事件(常用);event.preventDefault()
2.stop:阻止事件冒泡(常用);event.stopPropagation()
3.once:事件只触发一次(常用);
4.capture:使用事件的捕获模式;
5.self:只有event.target是当前操作的元素时才触发事件;
6.passive:事件的默认行为立即执行,无需等待事件回调执行完毕;
- 代码
<div id="root">
<a href="http://www.baidu.com/" @click.prevent="show1">1.阻止跳转页面</a>
<div @click="show1()">
<button @click.stop="show1()">2.阻止冒泡(叠加执行多次)</button>
</div>
<button @click.once="show1()">3.只执行一次</button>
</div>
<script>
const vm = new Vue({
el:'#root',
methods: {
show1(){
alert("123")
}
}
})
</script>
3)键盘事件
- 介绍
1.常见
# 回车 => enter
# 删除 => delete (捕获“删除”和“退格”键)
# 退出 => esc
# 空格 => space
# 换行 => tab (特殊,必须配合keydown去使用)
# 上 => up
# 下 => down
# 左 => left
# 右 => right
2.Vue未提供别名的按键,可以使用按键原始的key值去绑定,但注意要转为kebab-case(短横线命名)
3.系统修饰键(用法特殊):ctrl、alt、shift、meta
# (1). 配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发。
# (2). 配合keydown使用:正常触发事件。
4.也可以使用keyCode去指定具体的按键(不推荐)
5.Vue.config.keyCodes.自定义键名 = 键码,可以去定制按键别名
- 代码
<div id="root">
输入(常见的):<input type="text" placeholder="按下回车提示" @keyup.enter="show1"><br>
输入(自定义):<input type="text" placeholder="按下切换大小写提示" @keyup.caps-lock="show2"><br>
输入(自定义按下触发):<input type="text" placeholder="按下tab提示" @keydown.tab="show1">
</div>
<script>
const vm = new Vue({
el:'#root',
methods: {
show1(){
alert("123")
},
show2(e){
console.log(e.key, e.keyCode), //获取键盘名称和code值
alert("123")
}
},
})
</script>
4)小技巧
#修饰符连写
@keyup.stop.prevent = 'xxx'
#键盘事件连写
@keyuo.ctrl.y = 'xxx'
2.7计算属性
1)使用
- 代码
<div id="root">
姓:<input type="text" v-model='fristName' ><br>
名:<input type="text" v-model='lastName' ><br>
<span>姓名:{{fullName}}</span>
</div>
<script>
Vue.config.productionTip = false
const vm = new Vue({
el:'#root',
data:{
fristName: '张',
lastName: '三',
},
computed:{
//1.原来写法
// fullName:{
// get(){
// return this.fristName + '-' + this.lastName
// },
// set(value){
// console.log(this)
// const arr = value.split('-')
// this.fristName = arr[0]
// this.lastName = arr[1]
// }
// }
//2.简写,默认只有getter方法
fullName(){
return this.fristName + '-' + this.lastName
}
}
})
</script>
- 总结
1。定义:要用的属性不存在,要通过已有属性计算得来。
2。原理:底层借助了Objcet.defineproperty方法提供的getter和setter。
3。get函数什么时候执行?
(1). 初次读取时会执行一次。
(2). 当依赖的数据发生改变时会被再次调用。
4。优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便。
5。备注:
1.计算属性最终会出现在vm上,直接读取使用即可。
2.如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生改变。
2.8 监视属性
1)使用
- 代码
<div id="root">
<h1>天气很{{info}}</h1>
<button @click="exchange">切换</button>
</div>
<script>
Vue.config.productionTip = false
const vm = new Vue({
el:'#root',
data:{
isHot: true
},
methods: {
exchange(){
this.isHot = !this.isHot
}
},
computed:{
info(){
return this.isHot?'炎热':'寒冷'
}
},
//第一种写法
// watch:{
// isHot:{
// immediate: true, //初始化调用handler
// handler(news, old){ //数据变化时被检测
// console.log(old + "被修改了" + news)
// }
// }
// }
})
//第二种写法(监视属性要加引号)
vm.$watch('isHot', {
immediate: true, //初始化调用handler
handler(news, old){ //数据变化时被检测
console.log(old + "被修改了" + news)
}
})
</script>
- 总结
1.通过通过vm对象的$watch()或watch配置来监视指定的属性
2.当属性变化时, 回调函数自动调用, 在函数内部进行计算
当被监视的属性变化时, 回调函数自动调用, 进行相关操作
监视的属性必须存在,才能进行监视!!
监视的两种写法:
(1). new Vue时传入watch配置
(2). 通过vm.$watch监视
2)深度监视
<script>
vm.$watch('number', {
deep:true, //深度监视
handler(news, old){
console.log(old + "被修改了" + news)
}
})
</script>
深度监视:
(1). Vue中的watch默认不监测对象内部值的改变(一层)。
(2). 配置deep:true可以监测对象内部值改变(多层)。
备注:
(1). Vue自身可以监测对象内部值的改变,但Vue提供的watch默认不可以!
(2). 使用watch时根据数据的具体结构,决定是否采用深度监视。
3)简写
//1.简写(没有其他属性)
watch:{
isHot(news, old){
console.log(old + "被修改了" + news)
}
}
//2.$watch
vm.$watch('number.a', function(news, old){
console.log(old + "被修改了" + news)
}
)
4)computed&watch
1.computed和watch之间的区别:
# computed能完成的功能,watch都可以完成。
# watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作(定时器)。
2.两个重要的小原则:
# 所被Vue管理的函数,最好写成普通函数,这样this的指向才是vm 或 组件实例对象。
# 所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数),最好写成箭头函数,这样this的指向才是vm 或 组件实例对象。
2.9 绑定样式
1)绑定class样式
<head>
<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;
}
</style>
</head>
<body>
<div id="root">
<!-- 字符串写法, 适用于:类名不确定,需要动态绑定 -->
<div class="basic" :class="calssStr" @click="changeStr">{{name}}</div><br>
<!-- 数组写法, 适用于:个数不确定,名字也不确定 -->
<div class="basic" :class="calssArr" @click="changeArr">{{name}}</div><br>
<!-- 对象写法, 适用于:个数,名字确定,动态确定用不用 -->
<div class="basic" :class="calssObj">{{name}}</div><br>
</div>
<script>
Vue.config.productionTip = false
const vm = new Vue({
el:'#root',
data:{
name: '小张',
calssStr: 'normal',
calssArr: '',
calssObj:{
sad: true,
happy: false,
}
},
methods: {
changeStr(){
this.calssStr = 'happy'
},
changeArr(){
const arr = ['normal', 'happy', 'bad']
const index = Math.floor(Math.random()*3)
this.calssArr = arr[index]
}
},
})
</script>
</body>
2)绑定style样式
<!-- 绑定style样式--对象写法 -->
<div class="basic" :style="styleObj, styleObj2">{{name}}</div>
<script>
const vm = new Vue({
el: '#root',
data: {
name: 'hello',
styleObj: {
fontSize: '40px',
color: 'red',
},
styleObj2: {
backgroundColor: 'orange'
},
},
})
</script>
2.10 条件渲染
1)v-if & v-show
<div id="root">
<button @click="n++">数字{{n}}</button>
<!-- v-if, 必须连续使用,页面不显示源代码不显示 -->
<div v-if="n===1">v-if-1</div>
<div v-else-if="n===2">v-if-2</div>
<div v-else-if="n===3">v-if-3</div>
<div v-else>v-if-4</div>
<!-- template不改变原有的样式,只能搭配v-if -->
<template v-if="n===0">
<h2>111</h2>
<h2>222</h2>
<h2>333</h2>
</template>
<!-- v-show 默认显示display:none-->
<div v-show="n===1">v-show</div>
</div>
<script>
Vue.config.productionTip = false
const vm = new Vue({
el:'#root',
data:{
n: 0
}
})
</script>
2)总结
1.v-if
#适用于:切换频率较低的场景。
#特点:不展示的DOM元素直接被移除。
#注意:v-if可以和:v-else-if、v-else一起使用,但要求结构不能被“打断”。
2.v-show
#写法:v-show="表达式"
#适用于:切换频率较高的场景。
#特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉
#【备注】使用v-if的时候,元素可能无法获取到,而使用v-show一定可以获取到。
2.11列表渲染
1)遍历列表
<div id="root">
<ul>
<!-- 数组 -->
<li v-for='(p, index) in personArr' :key="index">{{index}}: {{p.name}}--{{p.age}}</li><br>
</ul>
<ul>
<!-- 对象 -->
<li v-for='(v, k, index) in personObj' :key="index">{{index}}: {{k}}--{{v}}</li><br>
</ul>
<ul>
<!-- 字符串 -->
<li v-for='(pos, index) in personStr' :key="index">{{index}}: {{pos}}--{{index}}</li><br>
</ul>
<ul>
<!-- 指定次数 -->
<li v-for='(conunt, index) in 4' :key="index">{{index}}: {{conunt}}--{{index}}</li>
</ul>
</div>
<script>
Vue.config.productionTip = false
const vm = new Vue({
el:'#root',
data:{
personArr:[
{id:'001', name:'张三', age:18},
{id:'002', name:'里斯', age:28},
{id:'003', name:'王五', age:8}
],
personObj:{
id:'001',
name:'张三',
age:18
},
personStr:'abcd'
},
})
</script>
2)key作用及原理(难)
-
虚拟DOM中key的作用:
key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】, 随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下: -
对比规则:
(1). 旧虚拟DOM中找到了与新虚拟DOM相同的key:
①若虚拟DOM中内容没变, 直接使用之前的真实DOM
②若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM(2). 旧虚拟DOM中未找到与新虚拟DOM相同的key创建新的真实DOM,随后渲染到到页面。
-
用index作为key可能会引发的问题:
(1). 若对数据进行:逆序添加、逆序删除等破坏顺序操作: 会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低
(2). 如果结构中还包含输入类的DOM: 会产生错误DOM更新 ==>界面有问题 -
开发中如何选择key:
(1). 最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
(2). 如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。
3)列表过滤
- 代码
<div id="root">
<input type="text" placeholder="输入名称" v-model="keyName">
<ul>
<li v-for='(p, index) in personShow' :key="index">{{index}}: {{p.name}}--{{p.age}}</li><br>
</ul>
</div>
<script>
Vue.config.productionTip = false
const vm = new Vue({
el:'#root',
data:{
keyName:'',
personArr:[
{id:'001', name:'马冬梅', age:18},
{id:'002', name:'周冬雨', age:28},
{id:'003', name:'周杰伦', age:8},
{id:'004', name:'周兆伦', age:8}
],
// personShow:[],
},
//计算属性实现
computed:{
personShow(){
return this.personArr.filter((p)=>{
return p.name.indexOf(this.keyName) !== -1
})
}
},
//监视属性实现
// watch:{
// keyName:{
// immediate: true,
// handler(value){
// this.personShow = this.personArr.filter((p)=>{
// return p.name.indexOf(value) !== -1
// })
// }
// }
// }
})
</script>
4)列表排序
<div id="root">
<input type="text" placeholder="输入名称" v-model="keyName">
<button @click='sortType=2'>年龄升序</button>
<button @click='sortType=1'>年龄降序</button>
<button @click='sortType=0'>重置</button>
<ul>
<li v-for='(p, index) in personShow' :key="index">{{index}}: {{p.name}}--{{p.age}}</li><br>
</ul>
</div>
<script>
Vue.config.productionTip = false
const vm = new Vue({
el:'#root',
data:{
keyName:'',
personArr:[
{id:'001', name:'马冬梅', age:38},
{id:'002', name:'周冬雨', age:28},
{id:'003', name:'周杰伦', age:19},
{id:'004', name:'周兆伦', age:21}
],
sortType:0, //0原顺序 1倒叙 2正序
},
computed:{
personShow(){
const arr = this.personArr.filter((p)=>{
return p.name.indexOf(this.keyName) !== -1
})
if(this.sortType){
arr.sort((p1, p2)=>{
return this.sortType === 1 ? p2.age - p1.age : p1.age - p2.age
})
}
return arr
}
},
})
</script>
2.12监视数据原理
1)总结
1.vue会监视data中所有层次的数据。
2.如何监测对象中的数据?
通过setter实现监视,且要在new Vue时就传入要监测的数据。
(1).对象中后追加的属性,Vue默认不做响应式处理
(2).如需给后添加的属性做响应式,请使用如下API:
Vue.set(target,propertyName/index,value) 或
vm.$set(target,propertyName/index,value)
3.如何监测数组中的数据?
通过包裹数组更新元素的方法实现,本质就是做了两件事:
(1). 调用原生对应的方法对数组进行更新。
(2). 重新解析模板,进而更新页面。
4.在Vue修改数组中的某个元素一定要用如下方法:
(1). 使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
(2). Vue.set() 或 vm.$set()
# 特别注意:Vue.set() 和 vm.$set() 不能给vm 或 vm的根数据对象 添加属性!!!
2.13收集表单数据
1)代码
<div id="root">
<form action="" @submit.prevent='submitInfo'>
用户名:<input type="text" v-model.trim="name"><br>
密码:<input type="password" v-model.trim="password"><br>
年龄:<input type="number" v-model.number="age"><br>
性别:男<input type="radio" id="male" value="男" v-model="sex">
女<input type="radio" id="female" value="女" v-model="sex"><br>
爱好:篮球<input type="checkbox" id="basket" value="basket" v-model="likes">
足球<input type="checkbox" id="foot" value="foot" v-model="likes">
乒乓<input type="checkbox" id="pingpang" value="pingpang" v-model="likes"><br>
城市:<select v-model="city">
<option value="">--未选择--</option>
<option value="xian">西安</option>
<option value="beijiang">北京</option>
<option value="shanghai">上海</option>
<option value="guizhou">贵州</option>
</select><br>
介绍:<textarea rows="5" v-model.lazy="desc"></textarea><br>
<input type="checkbox" v-model='agree'>阅读并接受<a href="http://www.baidu.com/">《用户协议》</a><br>
<input type="submit" value="提交">
</form>
</div>
<script>
Vue.config.productionTip = false
const vm = new Vue({
el:'#root',
data:{
name:'',
password:'',
age:'',
sex:'男',
likes:[],
city:'',
desc:'',
agree:''
},
methods: {
submitInfo(){
console.log(this._data)
}
},
})
</script>
2)总结
收集表单数据:
若:<input type="text"/>,则v-model收集的是value值,用户输入的就是value值。
若:<input type="radio"/>,则v-model收集的是value值,且要给标签配置value值。
若:<input type="checkbox"/>
1.没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,是布尔值)
2.配置input的value属性:
(1)v-model的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值)
(2)v-model的初始值是数组,那么收集的的就是value组成的数组
备注:v-model的三个修饰符:
# lazy:失去焦点再收集数据
# number:输入字符串转为有效的数字
# trim:输入首尾空格过滤
2.14过滤器
1.定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)。
2.语法:
1.注册过滤器:Vue.filter(name,callback) 或 new Vue{filters:{}}
2.使用过滤器:{undefined{ xxx | 过滤器名}} 或 v-bind:属性 = “xxx | 过滤器名”
备注:
1.过滤器也可以接收额外参数、多个过滤器也可以串联
2.并没有改变原本的数据, 是产生新的对应的数据
2.15内置指令
1)v-text
<div v-text='name'>你好,</div> //会覆盖div里面原有的值
<div>你好,{{name}}</div> //拼接起来(常用)
1)v-html
1作用:向指定节点中渲染包含html结构的内容。
2与插值语法的区别:
(1). v-html会替换掉节点中所有的内容,{{xx}}则不会。
(2). v-html可以识别html结构。
3严重注意:v-html有安全性问题!!!!
(1). 在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击。
(2). 一定要在可信的内容上使用v-html,永不要用在用户提交的内容上!
1)v-cloak
指令(没有值)
1.本质是一个特殊属性,Vue实例创建完成病接管容器后,会销毁
2.使用css配合可以解决网速过慢时页面出现{{xxx}}问题
使用
<h2 v-cloak>{{name}}</h2>
[vocloak]{
display:none
}
2.8监视属性
1)v-once
1.v-once所在节点在初次动态渲染后,就视为静态内容了。
2.以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。
1)v-pre
1.跳过其所在节点的编译过程。
2.可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译。
2.16自定义指令
2.17生命周期
1)mounted
<div id="root">
<h2 :style="{opacity}">生命周期</h2>
</div>
<script>
Vue.config.productionTip = false
const vm = new Vue({
el:'#root',
data:{
opacity: 1,
},
mounted() {
console.log('aa')
setInterval(()=>{
this.opacity -= 0.01
if(this.opacity <= 0){
this.opacity = 1
}
}, 16)
},
})
</script>
2)生命周期
1 又名:生命周期回调函数、生命周期函数、生命周期钩子。
2 是什么:Vue在关键时刻帮我们调用的一些特殊名称的函数。
3 生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的。
4 生命周期函数中的this指向是vm 或 组件实例对象。
1)总结
常用的生命周期钩子
1.mounted(): 发送ajax请求, 启动定时器、绑定自定义事件、订阅消息等异步任务【初始化操作】
2.beforeDestroy(): 做收尾工作, 如: 清除定时器、解绑自定义事件、取消订阅消息等【首尾工作】
销毁Vue实例
1.销毁后借助Vue开发者工具看不到任何信息
2.销毁后自定义事件会失效,但原生DOM事件依然有效
3.一般不会在beforeDestroy操作数据,因为即使操作数据,也不会再触发更新流程了。
2.18组件
1)介绍
1.依赖关系混乱,不好维护
2.代码复用率不高
2)非单文件组件
- 代码
<div id="root">
<school></school>
<hr>
<student></student>
</div>
<div id="root1">
<student></student>
</div>
<script>
Vue.config.productionTip = false
//school局部组件
const school = Vue.extend({
template:`
<div>
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{addr}}</h2>
</div>
`,
data(){
return{
name:'石沟子小学',
addr:'西安'
}
}
})
//student局部组件
const student = Vue.extend({
template:`
<div>
<h2>学生名称:{{name}}</h2>
<h2>学生年龄:{{age}}</h2>
</div>
`,
data(){
return{
name:'张三',
age:18
}
}
})
//注册全局组件
Vue.component('student', student)
//Vue实例
const vm = new Vue({
el:'#root',
components:{
school,
student
}
})
//Vue实例使用全局组件
new Vue({
el:'#root1',
})
</script>
- 总结
1.1 使用组件的三大步骤
#定义组件(创建组件)
#注册组件
#使用组件(写组件标签)
1.2 如何定义一个组件
使用Vue.extend(options)创建,其中options和new Vue(options)时传入的那个options几乎一样,但有以下区别
#不要写el——最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器
#data必须写成函数——避免组件被复用时,数据存在引用关系
【备注】使用tempalte可以配置组件结构
1.3 如何注册组件
#局部注册:new Vue的时候传入components选项
#全局注册:Vue.component(‘组件名’, 组件)
- 注意点
1.关于组件名
一个单词组成
#第一种写法(首字母小写):school
#第二种写法(首字母大写):School
多个单词组成
#第一种写法(kebab-case命名):my-school
#第二种写法(CamelCase命名):MySchool(需要Vue脚手架支持)
2.备注
#① 组件名尽可能回避HTML中已有的元素名称,例如h2、H2
#② 可以使用name配置项指定组件在开发者工具中呈现的名字
3.关于组件标签
#第一种写法:<school></school>
#第二种写法:<school/> (不使用脚手架会导致后续组件不能渲染)
4.简写方式
#const school = Vue.extend(options) 可以简写成 const school = options
3)VueComponent
1.app组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的
2.我们只需要写<app/>或<app></app>,Vue解析时会帮我们创建app组件的实例对象,即Vue帮我们执行new VueComponent(options)
3.特别注意:每次调用Vue.extend,返回的都是一个全新的VueComponent
4.关于this指向
#① 组件配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【VueComponent实例对象】
#② new Vue(options)配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【Vue实例对象】
5.VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象)
6.Vue的实例对象,以后简称为vm
4)重要的内置关系
5)单文件组件
- School.Vue
<template>
<div>
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{addr}}</h2>
</div>
</template>
<script>
export default {
name:'School'
data(){
return{
name:'石沟子小学',
addr:'西安'
}
}
}
</script>
- App.Vue
<template>
<div>
<School></School>
</div>
</template>
<script>
import School from './School'
export default {
name:'App'
components:{
School,
}
}
</script>
- main.js
import App from './App.Vue'
new Vue({
el:'#root',
components:{
App
}
})
- index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="root">
<App></App>
</div>
<script src="../../js/vue.js"></script>
<script src="./main.js"></script>
</body>
</html>
- 问题
1.无法运行
报错:Uncaught SyntaxError: Cannot use import statement outside a module
#需要安装脚手架运行
3.高级(使用脚手架)
3.1脚手架
1)安装
- 1.设置淘宝镜像
npm config set registry https://registry.npm.taobao.org
- 2.安装脚手架
npm install -g @vue/cl
- 3.测试
vue
- 4.切换项目路径,创建
vue create xxxx(项目名称)
- 5.启动项目
npm run serve
2)目录结构
├── 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:包版本控制文件
3)render函数
render: h => h(App)
总结:
#1.mian.js不能使用components:{App},脚手架引入的是残缺的vue,render函数处理
4)说明
1.关于不同版本的Vue
vue.js与vue.runtime.xxx.js的区别:
#vue.js是完整版的Vue,包含:核心功能 + 模板解析器。
#vue.runtime.xxx.js是运行版的Vue,只包含:核心功能;没有模板解析器。
#因为vue.runtime.xxx.js没有模板解析器,所以不能使用template这个配置项,需要使用render函数接收到的createElement函数去指定具体内容。
2.vue.config.js配置文件
#使用vue inspect > output.js可以查看到Vue脚手架的默认配置。
#使用vue.config.js可以对脚手架进行个性化定制,详情见:https://cli.vuejs.org/zh
3.2 小知识点
1)ref属性
<template>
<div>
<img src="./assets/logo.png" alt="login">
<h2 v-text="msg" ref="info"></h2>
<button @click="get" ref="but">获取dom元素</button>
<School ref="comp"></School>
</div>
</template>
<script>
import School from './components/School.vue'
export default {
name:'App',
data() {
return {
msg: 'hello',
}
},
methods: {
get(){
console.log(this.$refs.info) //真实DOM对象 <h2>hello</h2>
console.log(this.$refs.but) //真实DOM对象 <h2>获取dom元素</h2>
console.log(this.$refs.comp) //School组件得实例对象(vc) VueComponent
}
},
components:{
School,
}
}
</script>
- 总结
1.被用来给元素或子组件注册引用信息(id的替代者)
2.应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)
3.使用方式:
#打标识:<h1 ref="xxx">.....</h1>或 <School ref="xxx"></School>
#获取:this.$refs.xxx
2)props配置
#父组件传参数
<Student name='张三' :age='18'></Student>
#子组件接受参数
<template>
<div>
<h2>学生名称:{{name}}</h2>
<h2>学生年龄:{{age+1}}</h2>
</div>
</template>
<script>
export default {
// props:['name', 'age'], //1.简单接受
// props:{ //2.限制数据类型
// name:String,
// age:Number
// },
props:{ //3.限制数据类型+默认值+是否必填
name:{
type:String,
required:true,
default:'小花'
},
age:{
type:Number,
}
},
}
</script>
- 总结
1.功能:让组件接收外部传过来的数据
2.传递数据:<Demo name="xxx"/>
3.接收数据:
#第一种方式(只接收):props:['name']
#第二种方式(限制类型):props:{name:String}
#第三种方式(限制类型、限制必要性、指定默认值):
#备注:props是只读的,Vue底层会监测你对props的修改,如果进行了【修改】,就会发出【警告】,若业务需求确实需要修改,那么请【复制】props的内容到data中一份,然后去修改data中的数据。
3)mixin混入
1)代码
- 定义混合mixin.js
export const mixin = {
methods: {
show(){
alert("hello!"+this.name)
}
},
}
- 局部混合
<template>
<div>
<h2 @click="show">学生名称:{{name}}</h2>
</div>
</template>
<script>
import {mixin} from '../mixin.js' //1.引入混合
export default {
mixins:[mixin] , //2.使用
}
</script>
- 全局混合mian.js
import Vue from 'vue'
import App from './App.vue'
import {mixin} from './mixin' //1.引入混合
Vue.config.productionTip = false
Vue.mixin(mixin) //2.使用
new Vue({
render: h => h(App),
}).$mount('#app')
2)总结
全局混入:Vue.mixin(xxx)
局部混入:mixins:['xxx']
4)插件
- 代码
export default {
install(Vue, x, y, z) {
console.log(x, y, z);
//全局过滤器
Vue.filter("mySlice", function (value) {
return value.slice(0, 4);
});
//定义全局指令
Vue.directive("fbind", {
//指令与元素成功绑定时(一上来)
bind(element, binding) {
element.value = binding.value;
},
//指令所在元素被插入页面时
inserted(element, binding) {
element.focus();
},
//指令所在的模板被重新解析时
update(element, binding) {
element.value = binding.value;
},
});
//定义混入
Vue.mixin({
data() {
return {
x: 100,
y: 200,
};
},
});
//给Vue原型上添加一个方法(vm和vc就都能用了)
Vue.prototype.hello = () => {
alert("你好啊");
};
},
}
#使用
import plugins from "./plugins"; //1.引入插件
Vue.use(plugins, 1, 2, 3); //2.使用
- 总结
1.功能:用于增强Vue
2.本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据。
5)scoped样式
- 代码
<style scoped> //标记scoped
.demo{
background-color:bisque;
}
</style>
- 总结
1.作用:让样式在局部生效,防止冲突。
2.写法:<style scoped>
3.3 练习案例
1)代码
- TopInfo.vue
<template>
<div class="todo-header">
<input type="text" placeholder="请输入你的任务名称,按回车键确认" @keyup.enter="addLike"/>
</div>
</template>
<script>
import {nanoid} from 'nanoid'
export default {
name:'TopInfo',
methods: {
addLike(event){
if(!event.target.value.trim()) return alert("输入不能为空!!!")
const obj = {id:nanoid(), name:event.target.value, finished:false}
this.getLike(obj)
event.target.value=''
}
},
props:['getLike']
}
</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>
- ListInfo.vue
<template>
<ul class="todo-main">
<ItemInfo v-for="like in LikesList" :key="like.id" :likeObj='like' :IsSelect='IsSelect' :isDelete='isDelete'></ItemInfo>
</ul>
</template>
<script>
import ItemInfo from './ItemInfo.vue'
export default {
name:'ListInfo',
components:{
ItemInfo
},
props:['LikesList', 'IsSelect', 'isDelete']
}
</script>
<style scoped>
/*main*/
.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>
- ItemInfo.vue
/* eslint-disable vue/no-dupe-keys */
<template>
<li>
<label>
<input type="checkbox" :checked='likeObj.finished' @change="IsChange"/>
<span>{{likeObj.name}}</span>
</label>
<button class="btn btn-danger" @click="deleteLike">删除</button>
</li>
</template>
<script>
export default {
name:'ItemInfo',
props:['likeObj', 'IsSelect', 'isDelete'],
methods: {
IsChange(){
this.IsSelect(this.likeObj.id)
},
deleteLike(){
this.isDelete(this.likeObj.id)
}
},
}
</script>
<style scoped>
/*item*/
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:hover {
background-color: #ddd;
}
li:hover button {
display: block;
}
li:before {
content: initial;
}
li:last-child {
border-bottom: none;
}
</style>
- FloorInfo.vue
<template>
<div class="todo-footer" v-show="total">
<label>
<input type="checkbox" v-model='isCheck'/>
</label>
<span>
<span>已完成{{finishNum}}</span> / 全部{{LikesList.length}}
</span>
<button class="btn btn-danger" @click="clearFinish">清除已完成任务</button>
</div>
</template>
<script>
export default {
name:'FloorInfo',
props:['LikesList', 'isCheckAll', 'isClear'],
methods: {
clearFinish(){
this.isClear()
}
},
computed:{
total(){
return this.LikesList.length
},
finishNum(){
return this.LikesList.reduce((num, like) => num + (like.finished?1:0), 0)
},
isCheck:{
get(){
return this.total == this.finishNum
},
set(value){
this.isCheckAll(value)
}
},
}
}
</script>
<style scoped>
/*footer*/
.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>
- App.vue
<template>
<div id="app">
<div class="todo-container">
<div class="todo-wrap">
<TopInfo :getLike="getLike"/>
<ListInfo :LikesList='LikesList' :IsSelect='IsSelect' :isDelete='isDelete'/>
<FloorInfo :LikesList='LikesList' :isCheckAll='isCheckAll' :isClear='isClear'/>
</div>
</div>
</div>
</template>
<script>
import TopInfo from './components/TopInfo.vue'
import ListInfo from './components/ListInfo.vue'
import FloorInfo from './components/FloorInfo.vue'
export default {
name: 'App',
components: {
TopInfo,
ListInfo,
FloorInfo
},
data() {
return {
LikesList:[
{id:'001', name:'吃饭', finished:true},
{id:'002', name:'睡觉', finished:false},
{id:'003', name:'打豆豆', finished:false}
]
}
},
methods: {
getLike(obj){
this.LikesList.unshift(obj)
},
IsSelect(id){
this.LikesList.forEach((like)=>{
if(id === like.id){
like.finished = !like.finished
}
})
},
isDelete(id){
this.LikesList = this.LikesList.filter(like => like.id!==id)
},
isCheckAll(isCheck){
this.LikesList.forEach((like)=>{
like.finished = isCheck
})
},
isClear(){
this.LikesList = this.LikesList.filter((like)=>{
return like.finished == false
})
}
},
}
</script>
<style>
/*base*/
body {
background:white;
}
.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>
2)总结
1.组件化编码流程:
#(1).拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。
#(2).实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:
1).一个组件在用:放在组件自身即可。
2). 一些组件在用:放在他们共同的父组件上(状态提升)。
#(3).实现交互:从绑定事件开始。
2.props适用于:
#(1).父组件 ==> 子组件 通信
#(2).子组件 ==> 父组件 通信(要求父先给子一个函数)
3.使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的!
4.props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。
3)本地存储
1.webStorage
#存储内容大小一般支持5MB左右(不同浏览器可能还不一样)
#浏览器端通过 Window.sessionStorage 和 Window.localStorage 属性来实现本地存储机制。
2.相关API:
#xxxxxStorage.setItem('key', 'value');
该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值。
#xxxxxStorage.getItem('person');
该方法接受一个键名作为参数,返回键名对应的值。
#xxxxxStorage.removeItem('key');
该方法接受一个键名作为参数,并把该键名从存储中删除。
#xxxxxStorage.clear()
该方法会清空存储中的所有数据。
3.备注:
#SessionStorage存储的内容会随着浏览器窗口关闭而消失。
#LocalStorage存储的内容,需要手动清除才会消失。
#xxxxxStorage.getItem(xxx)如果xxx对应的value获取不到,那么getItem的返回值是null。
#JSON.parse(null)的结果依然是null
- 代码
data() {
return {
LikesList: JSON.parse(localStorage.getItem('likes')) || [] //获取本地数据,否则得到空数组
// [{id:'001', name:'吃饭', finished:true},
// {id:'002', name:'睡觉', finished:false},
// {id:'003', name:'打豆豆', finished:false}]
}
},
watch:{
LikesList:{
deep:true, //深度监视(是否勾选)
handler(value){
localStorage.setItem('likes', JSON.stringify(value)) //保存
}
}
},
3.4自定义事件-全局事件总线-消息订阅发布
1)自定义事件
- App.vue
<template>
<div id="app">
<!-- 1.自定义事件 -->
<!-- <Student @monkey='getName'/> -->
<!-- 2.使用ref -->
<Student ref='getName'/>
</div>
</template>
<script>
import Student from './components/Student.vue'
export default {
name: 'App',
components: {
Student
},
methods:{
getName(name){
alert(name)
}
},
mounted() { //方式二使用
this.$refs.getName.$on('monkey',this.getName)
},
}
</script>
- Student.vue
<template>
<div>
<h2>姓名:{{name}}</h2>
<h2>年纪:{{age}}</h2>
<button @click="getNameInfo">点击获取名称</button>
<button @click="death">销毁</button>
</div>
</template>
<script>
export default {
name:'Student',
data() {
return {
name:'小张',
age:18
}
},
methods: {
getNameInfo(){
this.$emit('monkey', this.name)
},
death(){
// this.$destroy('monkey') //单个
// this.$destroy(['monkey', 'monkey1']) //多个
this.$destroy() //所有
}
},
}
</script>
- 总结
1.一种组件间通信的方式,适用于:子组件 ===> 父组件
2.使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)。
3.绑定自定义事件:
第一种方式,在父组件中:<Demo @atguigu="test"/>或 <Demo v-on:atguigu="test"/>
第二种方式,在父组件中:
<Demo ref="demo"/>
......
mounted(){
this.$refs.xxx.$on('atguigu',this.test)
}
4.若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法。
5.触发自定义事件:this.$emit('atguigu',数据)
6.解绑自定义事件this.$off('atguigu')
7.组件上也可以绑定原生DOM事件,需要使用native修饰符。
注意:通过this.$refs.xxx.$on('atguigu',回调)绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!
2)全局事件总线
- mian.js
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
beforeCreate(){
Vue.prototype.$bus = this //安装全局事件
}
}).$mount('#app')
- Student.vue
<template>
<div class="demo">
<h2>学生名称:{{name}}</h2>
<h2>学生年龄:{{age+1}}</h2>
<button @click="sendData">给school传数据</button>
</div>
</template>
<script>
export default {
name:'Student',
data(){
return{
name:'张三',
age:18
}
},
methods: {
sendData(){
this.$bus.$emit('data', this.age) //触发事件
}
},
}
</script>
- School.vue
<template>
<div class="demo">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{addr}}</h2>
</div>
</template>
<script>
export default {
name:'School',
data(){
return{
name:'石沟子小学',
addr:'西安'
}
},
mounted() {
this.$bus.$on('data', (data)=>{ //接收数据
console.log('aaa',data)
})
},
beforeDestroy(){
this.$bus.$off('data') //销毁全局事件
}
}
</script>
- 总结
1.一种组件间通信的方式,适用于任意组件间通信。
2.安装全局事件总线:
new Vue({
......
beforeCreate() {
Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
},
......
})
3.使用事件总线:
(1)接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。
methods(){
demo(data){......}
}
......
mounted() {
this.$bus.$on('xxxx',this.demo)
}
(2)提供数据:this.$bus.$emit('xxxx',数据)
4.最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。
3)消息订阅发布
- School.vue
<template>
<div class="demo">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{addr}}</h2>
</div>
</template>
<script>
import pubsub from 'pubsub-js'
export default {
name:'School',
data(){
return{
name:'石沟子小学',
addr:'西安'
}
},
mounted() {
this.pubId=pubsub.subscribe('hello', (name,data) => {
console.log(name, data);
})
},
beforeDestroy(){
pubsub.unsubscribe(this.pubId)
}
}
</script>
- Student.vue
<template>
<div class="demo">
<h2>学生名称:{{name}}</h2>
<h2>学生年龄:{{age}}</h2>
<button @click="sendData">给school传数据</button>
</div>
</template>
<script>
import pubsub from 'pubsub-js'
export default {
name:'Student',
data(){
return{
name:'张三',
age:18
}
},
methods: {
sendData(){
pubsub.publish('hello', this.age)
}
},
}
</script>
- 总结
1.一种组件间通信的方式,适用于任意组件间通信。
2.使用步骤:
安装pubsub:npm i pubsub-js
引入: import pubsub from 'pubsub-js'
3.接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。
methods(){
demo(data){......}
}
......
mounted() {
this.pid = pubsub.subscribe('xxx',this.demo) //订阅消息
}
4.提供数据:pubsub.publish('xxx',数据)
5.最好在beforeDestroy钩子中,用PubSub.unsubscribe(pid)去取消订阅。
4)小知识点
1.nextTick
1.语法:this.$nextTick(回调函数)
2.作用:在下一次 DOM 更新结束后执行其指定的回调。
3.什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。
插槽slot
2.过渡动画
<template>
<div>
<button @click="isShow = !isShow">显示/隐藏</button>
<transition-group name="hello" appear>
<h1 v-show="!isShow" key="1">你好啊!</h1>
<h1 v-show="isShow" key="2">尚硅谷!</h1>
</transition-group>
</div>
</template>
<script>
export default {
name:'Test',
data() {
return {
isShow:true
}
},
}
</script>
<style scoped>
h1{
background-color: orange;
}
/* 进入的起点、离开的终点 */
.hello-enter,.hello-leave-to{
transform: translateX(-100%);
}
.hello-enter-active,.hello-leave-active{
transition: 0.5s linear;
}
/* 进入的终点、离开的起点 */
.hello-enter-to,.hello-leave{
transform: translateX(0);
}
</style>
3.第三方动画库
https://animate.style/
<template>
<div>
<button @click="isShow = !isShow">显示/隐藏</button>
<transition-group
appear
name="animate__animated animate__bounce"
enter-active-class="animate__swing"
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:'Test',
data() {
return {
isShow:true
}
},
}
</script>
<style scoped>
h1{
background-color: orange;
}
</style>
3.5 axios
1)代理两种方式
方式1
- vue.config.js
module.exports = {
devServer: {
proxy: 'http://localhost:1234', //代理地址
port: 8081 //修改前端端口
}
}
//说明:
//1.优点:配置简单,请求资源时直接发给前端(8080)即可。
//2.缺点:不能配置多个代理,不能灵活的控制请求是否走代理。
//3.工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器 (优先匹配前端资源)
- 使用
<template>
<div>
<button @click="test">测试</button>
</div>
</template>
<script>
import axios from 'axios' //1.导包
export default {
name:'App',
methods: {
test(){ //2.使用
axios.get('http://localhost:8080/test?id=1').then( //本来地址
(response)=>{
console.log(response.data)
console.log(response)
},
)
}
}
</script>
方式2
- 配置
module.exports = {
devServer: {
proxy: {
'/test': { //注意:必须后端地址带有/test
target: 'http://localhost:1234', //后端地址
ws: true, //支持websoket
changeOrigin: true, //默认,用于请求头中host(true:后端地址,false:前端地址)
pathRewrite:{ //将路径中api替换掉
'^/test':'/test'
}
}
}
}
}
//说明:
//1.优点:可以配置多个代理,且可以灵活的控制请求是否走代理。
//2.缺点:配置略微繁琐,请求资源时必须加前缀。
2)get/post请求使用
get
//方式一
data(){
return{
id:1
}
}
axios.get(`/test?id=${this.id}`).then( //注意:拼接参数必须用``不是单引号''
(response)=>{
console.log(response.data)
},
error=>{
console.log('err', error.massage)
}
)
//方式二
axios.get('/test',{ //以参数形式传进来
params:{
id : 3
}
}).then(
(response)=>{
console.log(response.data)
},
error=>{
console.log('err', error.massage)
}
)
post
data() {
return {
post:{
name: 'aaa',
addr: 'bbb'
}
}
}
axios.post('/test1', this.post).then(
(response)=>{
console.log(response.data)
},
error=>{
console.log('err', error.massage)
}
)
3)GitHub案例
- 公共样式
1.目录 pubulic--css--bootstrap.css
2.在index.html引入
<!-- 引入静态资源 -->
<link rel="myStype" href="<%= BASE_URL %>css/bootstrap.css">
- Header.vue
<template>
<div>
<section class="jumbotron">
<h3 class="jumbotron-heading">Search Github Users</h3>
<div>
<input type="text" placeholder="enter the name you search" v-model="keyword" @keyup.enter='getInfo'/>
<button @click="getInfo">Search</button>
</div>
</section>
</div>
</template>
<script>
import axios from 'axios'
export default {
name:'Header',
data() {
return {
keyword:'' ,
}
},
methods: {
getInfo(){
this.$bus.$emit('userList',{isFirst:false,isLoad:true,errMsg:'', users:[]})
axios.get(`https://api.github.com/search/users?q=${this.keyword}`).then(
response=>{
this.$bus.$emit('userList',{isLoad:false,errMsg:'', users:response.data.items})
},
error=>{
this.$bus.$emit('userList',{isLoad:false,errMsg: error.message, users:[]})
})
}
},
}
</script>
- List.vue
<template>
<div class="row">
<div v-show="info.users.length" class="card" v-for="user in info.users" :key="user.login">
<a :href="user.html_url" target="_blank">
<img :src="user.avatar_url" style='width: 100px'/>
</a>
<p class="card-text">{{user.login}}}</p>
</div>
<h1 v-show="info.isFirst">欢迎使用!!!</h1>
<h1 v-show="info.isLoad">Loading。。。</h1>
<h1 v-show="info.errMsg">{{info.errMsg}}</h1>
</div>
</template>
<script>
export default {
data() {
return {
info:{
isFirst:true,
isLoad:false,
errMsg:'',
users:[]
}
}
},
mounted(){
this.$bus.$on('userList', (users)=>{
this.info = {...this.info, ...users} //比较放进info
})
}
}
</script>
<style>
.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>
- App.vue
<template>
<div class="container">
<Header/>
<List/>
</div>
</template>
<script>
import Header from "./components/Header.vue";
import List from "./components/List.vue";
export default{
name:'App',
components:{
Header,
List
},
}
</script>
4)vue-resource请求数据
1.安装
npm install vue-resource
2.main.js引入
import vueResource form 'vue-resource'
Vue.use(vueResource)
3.使用(和axios相似)
将axios.get(url)替换为this.$http.get(url)
注意:
//维护比较少,axios好一点
5)插槽Solt
默认卡槽
- App.vue
<template>
<div class="container">
<Category title="美食">
<img src="https://img2.baidu.com/it/u=1476158892,3155909481&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=281" alt="">
</Category>
<Category title="游戏">
<ul>
<li v-for="(game,index) in games" :key="index">{{game}}</li>
</ul>
</Category>
<Category title="电影">
<video src="https://www.bilibili.com/video/BV1Zy4y1K7SH/"></video>
</Category>
</div>
</template>
<script>
import Category from './components/Category.vue'
export default{
name:'App',
components:{
Category
},
data() {
return {
foods:['火锅','烧烤','小龙虾','牛排'],
games:['红色警戒','穿越火线','劲舞团','超级玛丽'],
films:['《教父》','《拆弹专家》','《你好,李焕英》','《尚硅谷》']
}
},
}
</script>
<style>
.container{
display: flex;
justify-content: space-around;
}
h3{
text-align: center;
background-color: orange;
}
img{
width: 100%;
}
video{
width: 100%;
}
</style>
-
Category.vue
<template> <div class="category"> <h3>{{title}}分类</h3> <slot></slot> //相当于填充 </div> </template> <script> export default { name:'Category', props:['title'] } </script> <style> .category{ background-color: skyblue; width: 200px; height: 300p; } </style>
具名卡槽
1.app.vue
<Category title="美食">
<img slot="image" src="https://img2.baidu.com/it/u=1476158892,3155909481&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=281" alt="">
<a slot="dump" href="http://ww.baidu.com">百度</a>
</Category>
2.category.vue
<slot name="image"></slot>
<slot name="dump"></slot>
作用域卡槽
1.父组件
<Category title="游戏">
<template scope="data">
<ul>
<li v-for="(game,index) in data.games" :key="index">{{game}}</li>
</ul>
</template>
</Category>
2.子组件
<slot :games="games"></slot>
games:['红色警戒','穿越火线','劲舞团','超级玛丽'],
3.6 vuex
1)介绍
1.对vue 应用中多个组件的共享状态进行集中式的管理(读/写)
适用于单页面组件之间数据交换比较频繁
用于任意组件间通信
2)
3.7 路由
1)介绍
vue-router理解
vue的一个插件库,专门来实现SPA应用
SPA理解
1.单页面应用,整个应用只有一个完整的页面
2.点击页面导航连接不会刷新页面,指挥做页面的局部更新
3.数据通过Ajax获取请求
路由
1.一组映射关系(key-value)
2.key为路径。value可能为function或component
路由分类
1.后端路由
1)value是function,用于处理客户端提交的请求
2)过程:服务器收到请求时,根据请求路径找到匹配的函数来处理请求,返回响应数据
2.前端路由
1)value是component,用于展示页面内容
2)过程:当浏览器路径改变时,对应的组件就会显示
2)基本使用
安装
1.npm默认安装是vue-router 4版本
vue2对应的vue-router版本是3 #安装命令(npm i vue-router@3)
代码
- 引入bootstrap.css文件
1.在public--css下存放bootstrap.css文件
2.在index.html引入css
<!-- 引入静态资源 -->
<link rel="stylesheet" href="<%= BASE_URL %>css/bootstrap.css">
- 在router文件夹创建index.js
import VueRouter from 'vue-router'
import Vue from 'vue'
import About from '../components/About.vue'
import Home from '../components/Home.vue'
Vue.use(VueRouter)
export default new VueRouter({
routes: [
{
path:'/about',
component: About
},
{
path:'/home',
component: Home
}
]
})
- main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router/index'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
router
}).$mount('#app')
- 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">
<!-- 使用路由-->
<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">
<!-- <h2>我是About的内容</h2> -->
<router-view msg='aaa'></router-view>
</div>
</div>
</div>
</div>
</template>
<script>
export default{
name:'App',
}
</script>
- About.vue
<template>
<div>
<h2>我是About的内容</h2>
</div>
</template>
- Home.vue
<template>
<div>
<h2>我是Home的内容</h2>
</div>
</template>
3)路由嵌套
- 配置
routes:{
path:'/home',
component: Home,
children:[
{
path:'news', // 路径注意不能加'/'
component: News,
},
]
}
- 使用
<router-link to="/home/news">News</router-link>
4)query参数传递
- 方式一(拼接参数)
<router-link :to="`/home/message/detail?id=${info.id}&name=${info.name}`">{{info.name}}</router-link>
- 方式二(对象式)
<router-link :to="{
path:'/home/message/detail',
query:{
id:info.id,
name:info.name
}}">
{{info.name}}
</router-link>
- 使用
$route.query
5)params参数传递
- 命名路由
{
name:'setName',
path:'detail',
component: Detail,
}
<!--简化前,需要写完整的路径 -->
<router-link :to="`/home/message/detail?id=${info.id}&name=${info.name}`">{{info.name}}</router-link>
<!--简化后,直接通过名字跳转 -->
<router-link :to="`setName?id=${info.id}&name=${info.name}`">{{info.name}}</router-link>
<!--简化写法配合传递参数 -->
<router-link
:to="{
name:'hello',
query:{
id:666,
name:'你好'
}
}"
>跳转</router-link>
- params参数
<!-- 第一种-->
<router-link :to="/home/message/detail/666/你好">跳转</router-link>
<!-- 第二种-->
<router-link :to="{
name:'setName', //注意:只能是name
params:{
id:info.id,
name:info.name
}
}">
{{info.name}}
</router-link>
- 使用
$route.params
6)props配置
- 配置
//第一种写法:props值为对象,该对象中所有的key-value的组合最终都会通过props传给Detail组件
// props:{
// id: 1,
// name:"小藏"
// }
//第二种写法:props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给Detail组件
// props:true
//第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件
props($route){
return{
id:$route.params.id,
name:$route.params.name,
}
}
- 使用
<template >
<div>
<ul>
<li>{{id}}</li>
<li>{{name}}</li>
</ul>
</div>
</template>
<script>
export default {
props:['id','name']
}
</script>
7)replace属性
1.作用:控制路由跳转时操作浏览器历史记录的模式
2.浏览器的历史记录有两种写入方式:分别为push和replace,push是追加历史记录,replace是替换当前记录。路由跳转时候默认为push
3.如何开启replace模式:<router-link replace .......>News</router-link>
8)响应式编程
//$router的两个API,不借助reouter-link
//1.点击按钮,显示子路由。push是追加历史记录
this.$router.push({
name:'xiangqing',
params:{
id:xxx,
title:xxx
}
})
//2.点击按钮,显示子路由。replace是替换当前记录
this.$router.replace({
name:'xiangqing',
params:{
id:xxx,
title:xxx
}
})
//使用
this.$router.forward() //前进
this.$router.back() //后退
this.$router.go(2) //可前进也可后退(例:2.前进2步;-3.后退3步)
9)缓存路由组件
<!--输入文本框数据,切换路由,返回时数据没有被销毁。不写include默认全部,News表示组件名-->
<keep-alive include="News">
<router-view></router-view>
</keep-alive>
注意:
多个缓存路由使用 :include="['News','Message']"
10)两个新的生命周期钩子
1.作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。
2.具体名字:
activated路由组件被激活时触发。
deactivated路由组件失活时触发。
11)路由守卫
- 前置守卫&后置路由
//全局前置守卫:初始化时执行、每次路由切换前执行
router.beforeEach((to,from,next)=>{
console.log('beforeEach',to,from)
if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
if(localStorage.getItem('school') === 'atguigu'){ //权限控制的具体规则
next() //放行
}else{
alert('暂无权限查看')
// next({name:'guanyu'})
}
}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(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
if(localStorage.getItem('school') === 'atguigu'){
next()
}else{
alert('暂无权限查看')
// next({name:'guanyu'})
}
}else{
next()
}
}
- 组件内路由守卫
//进入守卫:通过路由规则,进入该组件时被调用
beforeRouteEnter (to, from, next) {
},
//离开守卫:通过路由规则,离开该组件时被调用
beforeRouteLeave (to, from, next) {
}
12)路由的两种工作模式
1.对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值。
2.hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。
#hash模式:
地址中永远带着#号,不美观 。
若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
兼容性较好。
#history模式:
地址干净,美观 。
兼容性和hash模式相比略差。
应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。
3.路由配置
mode:'history',
3.8 elment-ui
1)导入 Elment-UI 相关资源
//安装
npm i element-ui
//导入组件库
import ElmentUI from 'element-ui';
//导入组件相关样式
import 'element-ui/lib/theme-chalk/index.css';
//配置Vue插件
Vue.use(ElementUI);
2)使用Mint UI
https://blog.csdn.net/weixin_44972008/article/details/113921339
补充:
1.快捷键
-
:生成html模板 (!+ enter)
-
:生成vue实例**(newVue)**
-
:生成vue模板**(vueInit)**
-
:()
-
:()
-
:()
-
:()
-
:()
2.插件
- :Vue提示插件**(Vue 3 Snippets)**
- :右键直接浏览器打开**(Live Server)**
- :Vue文件高亮显示**(Vetur)**
- :语法纠错**(EsLint )**
- :ES6语法智能提示以及快速输入**(JavaScript(ES6) code snippets)**
- :css样式提示**(IntelliSense for CSS class names)**
- :()
- :()
- :()
ript>
### 7)replace属性
```vue
1.作用:控制路由跳转时操作浏览器历史记录的模式
2.浏览器的历史记录有两种写入方式:分别为push和replace,push是追加历史记录,replace是替换当前记录。路由跳转时候默认为push
3.如何开启replace模式:<router-link replace .......>News</router-link>
8)响应式编程
//$router的两个API,不借助reouter-link
//1.点击按钮,显示子路由。push是追加历史记录
this.$router.push({
name:'xiangqing',
params:{
id:xxx,
title:xxx
}
})
//2.点击按钮,显示子路由。replace是替换当前记录
this.$router.replace({
name:'xiangqing',
params:{
id:xxx,
title:xxx
}
})
//使用
this.$router.forward() //前进
this.$router.back() //后退
this.$router.go(2) //可前进也可后退(例:2.前进2步;-3.后退3步)
9)缓存路由组件
<!--输入文本框数据,切换路由,返回时数据没有被销毁。不写include默认全部,News表示组件名-->
<keep-alive include="News">
<router-view></router-view>
</keep-alive>
注意:
多个缓存路由使用 :include="['News','Message']"
10)两个新的生命周期钩子
1.作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。
2.具体名字:
activated路由组件被激活时触发。
deactivated路由组件失活时触发。
11)路由守卫
- 前置守卫&后置路由
//全局前置守卫:初始化时执行、每次路由切换前执行
router.beforeEach((to,from,next)=>{
console.log('beforeEach',to,from)
if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
if(localStorage.getItem('school') === 'atguigu'){ //权限控制的具体规则
next() //放行
}else{
alert('暂无权限查看')
// next({name:'guanyu'})
}
}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(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
if(localStorage.getItem('school') === 'atguigu'){
next()
}else{
alert('暂无权限查看')
// next({name:'guanyu'})
}
}else{
next()
}
}
- 组件内路由守卫
//进入守卫:通过路由规则,进入该组件时被调用
beforeRouteEnter (to, from, next) {
},
//离开守卫:通过路由规则,离开该组件时被调用
beforeRouteLeave (to, from, next) {
}
12)路由的两种工作模式
1.对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值。
2.hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。
#hash模式:
地址中永远带着#号,不美观 。
若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
兼容性较好。
#history模式:
地址干净,美观 。
兼容性和hash模式相比略差。
应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。
3.路由配置
mode:'history',
3.8 elment-ui
1)导入 Elment-UI 相关资源
//安装
npm i element-ui
//导入组件库
import ElmentUI from 'element-ui';
//导入组件相关样式
import 'element-ui/lib/theme-chalk/index.css';
//配置Vue插件
Vue.use(ElementUI);
2)使用Mint UI
https://blog.csdn.net/weixin_44972008/article/details/113921339
补充:
1.快捷键
-
:生成html模板 (!+ enter)
-
:生成vue实例**(newVue)**
-
:生成vue模板**(vueInit)**
-
:()
-
:()
-
:()
-
:()
-
:()
2.插件
- :Vue提示插件**(Vue 3 Snippets)**
- :右键直接浏览器打开**(Live Server)**
- :Vue文件高亮显示**(Vetur)**
- :语法纠错**(EsLint )**
- :ES6语法智能提示以及快速输入**(JavaScript(ES6) code snippets)**
- :css样式提示**(IntelliSense for CSS class names)**
- :()
- :()
- :()