Vue 2 - 入门
1、Vue简介
1.1、Vue是什么
Vue是一套构建用户界面的渐进式JavaScript框架。
- 渐进式:可以自底向上逐层的应用。
- 简单应用:只需一个轻量小巧的核心库;复杂应用:可以引入各式各样的Vue插件。
1.2、谁开发的
尤雨溪。
- 2013年:受到Angular框架的启发,尤雨溪开发出了一个轻量框架Seed。同年12月,Seed更名为Vue,版本号0.6.0;
- 2014年:Vue正式对外发布,版本号0.8.0。Taylor otwell 在 Twitter 上发表动态,说自己正在学习Vue.js;
- 2015年10月27日:正式发布Vue1.0.0 Evangelion(新世纪福音战士);
- 2016年10月1日:正式发布Vue2.0.0 Ghost in the hell(攻壳机动队);
- 2020年9月18日,正式发布Vue3.0.0 One Piece(海贼王)。
1.3、Vue的特点
采用组件化的方式,提高代码复用率,让代码更好维护;
声明式编码,让编码人员无需直接操作DOM,提高开发效率;
1.4、Vue引入
可使用 <script type="text/javascript" src="../js/vue.js"></script>
。
关闭Vue开发环境提示:
<script type="text/javascript" >
<Vue.config.productionTip = false // 阻止Vue在启动时生成生产提示
</script>
1.5、Vue小案例
<!-- 想让Vue工作,就必须创建一个Vue实例且要传入一个配置对象 -->
<!-- root容器里的代码依然符合html规范,只不过混入了一些特殊的Vue语法 -->
<!-- root容器里的代码被称为 Vue模板 -->
<!-- Vue实例和容器是一一对应的 -->
<!-- 真实开发中只有一个Vue实例,并且会配合着组件一起使用 -->
<!-- {{xxx}}中的xxx要写js表达式,且xxx可以自动读取到data中的所有属性 -->
<!-- 一旦data中的数据发生改变,那么模板中用到该数据的地方也会自动更新 -->
<!-- 一旦data中的数据发生改变,那么页面中用到该数据的地方也会自动更新 -->
<!-- 准备好一个容器 -->
<div id="root">
<h1>Hello world</h1>
<h1>年龄:{{name}}</h1>
</div>
<script type="text/javascript" >
// 创建Vue实例
const x = new Vue({
el:'#root', // el 用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符串
// 或者为 el:document.getElementById('root')
// data 中用于存储数据,数据提供el所指定的容器使用,值我们暂时先写成一个对象
data:{
name:'张三'
}
})
</script>
2、模板语法
<div id="root">
<h1>插值语法</h1>
<h3>你好,{{name}}</h3>
<hr/>
<h1>指令语法</h1>
<a v-bind:href="school.url"}>点我去尚硅谷学习1</a> // 数据绑定方式1
<a :href="school.url"}>点我去尚硅谷学习2</a> // 数据绑定方式2
</div>
<script type="text/javascript">
Vue.config.productionTip = false // 阻止vue在启动时生成生产提示
new Vue({
el:'#root',
data:{
name:'jack',
school:{
name:'尚硅谷',
url:"http://www.atguigu.com"
}
}
})
</script>
2.1、插值语法
功能:用于解析标签体内容。
写法:{{xxx}},xxx是js表达式,且可以直接读取到daa中的所有属性;
2.2、指定语法
功能:用于解析标签(包括:标签属性、标签体内容、绑定事件......)
写法:Vue中很多的指令,且形式都是 v-???,此处???可以是bind、if等等。
3、数据绑定
3.1、单向绑定 & 双向绑定
- 单向绑定(v-bind):数据只能从data流向页面;
- 双向绑定(v-model):数据不仅能从data流向页面,还可以从页面流向data。
<div id = "root">
<!-- 普通写法 -->
单向数据绑定:<input type="text" v-bind:value="name"><br/>
双向数据绑定:<input type="text" v-model:value="name"><br/>
<!-- 简单写法 -->
单向数据绑定:<input type="text" :value="name"><br/>
双向数据绑定:<input type="text" v-model="name"><br/>
<!-- 如下代码是错误的,因为v-model只能应用在表单元素(输入类元素) -->
<h2 v-module:x="name">你好啊</h2>
</div>
3.2、el 与 data 的两种写法
Vue.config.productionTip = false
const v = new Vue({
//el:'#root' // el第一种写法
// data的第一种写法
data:{
name:'尚硅谷'
}
// data的第二种写法
data:function(){
return {
name:'尚硅谷'
}
}
})
console.log(v)
setTimeout(()=>{
v.$mount('root'); // el第二种写法
}, 1000);
4、MVVM 模型
- M(模型,Model):对应data中的数据;
- V(视图,View):模板;
- VM(视图模型,ViewModel):Vue实例对象。
vm中所有的属性,最后都出现在了vm身上;vm身上所有的属性及vue原型上所有属性,在vue模板中都可以直接使用。
5、数据代理
5.1、Object.defineProperty() 方法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=dege" />
<meta name="viewpoint" content="width=device-width, initial-scale=1.0">
<title>Title</title>
<style>
</style>
</head>
<body>
<script>
let person = {
name: '张三', // 这里定义的属性是既能改也能删
sex: '男',
}
let number = 18
Object.defineProperty(person, 'age', {
// 方式1:定义value
value: number,
enumerable: true, // 控制属性是否可枚举,默认是false
writable: true, // 控制属性是否可以被修改,默认值是false
configurable: true, // 控制属性是否可以被删除,默认是false
// 方式2:定义get/set
// 当有人读取person的age属性时,get函数就会被调用,且返回值就是age的值
get:function() {
console.log('有人读取age属性了')
return 'hello';
},
// 当有人修改person的age属性时,set函数就会被调用,且会收到修改的具体的值
set:(value)=>{
console.log('有人修改了age属性')
number = value
}
// 方式1和方式2不同同时存在
})
console.log(person)
// 遍历不到 age
for(let index in person) {
console.log(person[index])
}
console.log(person)
</script>
</body>
</html>
5.2、何为数据代理
Vue中的数据代理:通过vm对象来代理data对象中属性的操作(读/写)
Vue中数据代理的好处:更加方便地操作data中的数据。
基本原理:通过Object.defineProperty()把data对象中的所有属性添加到vm上,为每一个添加到vm上的属性,都指定一个getter/setter,在getter/setter内部去操作(读/写)data中对应的属性。
<!-- 数据代理:通过一个对象代理对另一个对象中属性的操作 (读写)-->
<script>
let obj = {x: 100}
let obj2 = {y: 100}
Object.defineProperty(obj2, 'x', {
get(){
return obj.x;
},
set(value){
obj.x = value;
}
})
</script>
6、事件处理
6.1、事件的基本使用
事件的基本使用:
- 使用v-on:xxx 或 @xxx 绑定事件,其中xxx是事件名;
- 事件的回调需要配置在methods对象中,最终会在vm上;
- methods中配置的函数,不要用箭头函数,否则this就不是vm了;
- methods中配置的函数,都是被vue所管理的函数,this的指向是vm或组件实例对象;
- @click="demo" 和 @click="demo($event)" 效果一致,但后者可以传参。
<body>
<div id="root">
<h2>欢迎来到{{name}}学习</h2>
<button v-on:click="showInfo1">点我提示信息(不传参)</button>
<button @click="showInfo2($event, 66)">点我提示信息(传参)</button> <!-- 第二种写法 -->
</div>
</body>
<script tyype="text/javascript">
Vue.config.productionTip = false // 阻止vue在启动时生成生产提示
const vm = new Vue({
el: '#root',
// 只有在data里面才会进行数据代理
data: {
name: '尚硅谷',
},
methods: {
showInfo1(event, a, b, c, d) {
console.log(event, a, b, c, d)
console.log(this === vm)
alert('同学你好1')
},
showInfo2(event, number) {
alert('同学你好2' + number)
}
}
})
</script>
6.2、事件修饰符
Vue中的事件修饰符:
- prevent:阻止默认事件;
- stop:阻止事件冒泡(常用);
- once:事件只触发依次(常用);
- capture:使用事件的捕获模式;
- self:只有event.target是当前操作的元素才是触发事件;
- passive:事件的默认行为立即执行,无需等待事件回调执行完毕。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=dege" />
<meta name="viewpoint" content="width=device-width, initial-scale=1.0">
<title>Title</title>
<!-- 引入 Vue -->
<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: 20px;
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">点我提示信息1</button>
</div>
<!-- 事件只触发一次-->
<button @click.once="showInfo">点我提示信息2</button>
<!-- 使用事件的捕获方式-->
<div class="box1" @click.capture="showMsg(1)">
div1
<div class="box2" @click="showMsg(2)">
div2
</div>
</div>
<!-- 只有event.target是当前操作的元素时才触发事件-->
<div class="demo3" @click="showInfo">
<button @click="showInfo">点我提示信息3</button>
</div>
<!-- 事件的默认行为立即执行,无需等待事件回调执行完毕 -->
<ul class="list" @scroll="demo">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
</div>
</body>
<script tyype="text/javascript">
Vue.config.productionTip = false // 阻止vue在启动时生成生产提示
let vm = new Vue({
el: '#root',
data:{
name: 'atguigu'
},
methods:{
showInfo(e) {
// event.preventDefault()
// e.stopPropagation()
alert("同学你好")
},
showMsg(msg) {
alert(msg)
},
demo() {
console.log('@')
}
}
})
</script>
</html>
6.3、键盘事件
Vue中常见的按键别名:
- 回车:enter
- 删除:delete(捕获“删除”和“退格”键)
- 退出:esc
- 空格:space
- 换行:tab(特殊,必须配合keydown去使用)
- 上:up
- 下:down
- 左:left
- 右:right
Vue未提供别名的按键,可以使用按键原始的key值去绑定,但注意要转为kebab-case(短横线命名)
系统修饰键(用法特殊):ctrl、alt、shift、meta
- 配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发;
- 配合keydown使用:正常触发事件
也可以使用keycode去指定具体的按键(不推荐)
Vue.config.keyCodes.自定义键名 = 键码,可以去定制按键别名。
7、计算属性
计算属性:要用的属性不存在,要通过已有属性计算得来。
原理:底层借助了Object.defineProperty()方法提供的getter和setter。
get()函数什么时候执行:① 当初次读取时会执行一次;② 当依赖的数据发生改变时会被再次调用。
优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便。
备注:① 计算属性最终会出现在vm上,直接读取使用即可;② 如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生修改。
<body>
<div id="root">
姓:<input type="text" v-model="firstName"><br/>
名:<input type="text" v-model="lastName"><br/>
全名:<span>{{firstName.slice(0, 3)}}-{{lastName}}</span>
全名:<span>{{fullName()}}</span>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false // 阻止 vue 在启动时生成生产提示
new Vue({
el: '#root',
// 普通属性
data:{
firstName: '张',
lastName: '三'
},
// 计算属性
computed:{
// get作用:当读取fullName2时,get就会被调用,且返回值就作为fullName2的值
// get在初次读取fullName2时被调用,在所依赖的数据发生变化时也会被调用
fullName2: {
get() {
console.log('get被调用了')
return this.firstName +this.lastName; // this为vm
},
// 当fullName2被修改时被调用
set(value) {
console.log(value)
const arr = value.split('-')
this.firstName = arr[0]
this.lastName = arr[1]
}
}
},
methods:{
showInfo(e) {
console.log(e.target.value)
},
fullName() {
return '小猪佩奇'
}
}
})
</script>
计算属性的简写:
<script type="text/javascript">
Vue.config.productionTip = false // 阻止 vue 在启动时生成生产提示
new Vue({
el: '#root',
// 普通属性
data:{
firstName: '张',
lastName: '三'
},
// 计算属性
computed:{
// 计算属性的完整写法
// // get作用:当读取fullName2时,get就会被调用,且返回值就作为fullName2的值
// // get在初次读取fullName2时被调用,在所依赖的数据发生变化时也会被调用
// fullName2: {
// get() {
// console.log('get被调用了')
// return this.firstName +this.lastName; // this为vm
// },
// // 当fullName2被修改时被调用
// set(value) {
// console.log(value)
// const arr = value.split('-')
// this.firstName = arr[0]
// this.lastName = arr[1]
// }
// }
// 计算属性的简写(适用于只获取值(get)的情况)
fullName() {
return this.firstName + this.lastName;
}
},
methods:{
showInfo(e) {
console.log(e.target.value)
},
fullName() {
return '小猪佩奇'
}
}
})
</script>
8、监视属性
监视属性watch:
- 当被监视的属性变化时,回调函数自动调用,进行相关操作;
- 监视的属性必须存在,才能进行监视;
- 监视属性有两种写法(下面案例有演示)。
深度监视:
- Vue中的watch默认不监测对象内部值的改变(一层);
- 配置deep:true可以监测对象内部值改变(多层)。
备注:
- Vue自身可以监测对象内部值的改变,但Vue提供的watch默认不可以;
- 使用watch时根据数据的具体结构,决定是否采用深度监视。
案例1:
<body>
<div id="root">
<h2>今天天气很{{info}}</h2>
<button @click="changeWeather">切换天气</button>
<hr/>
<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>
</body>
<script type="text/javascript">
Vue.config.productionTip = false
const vm = new Vue({
el: '#root',
data:{
isHot: true,
numbers:{
a: 1,
b: 1
}
},
computed: {
info(){
return this.isHot ? '炎热' : '凉爽';
}
},
methods:{
changeWeather(){
this.isHot = !this.isHot;
}
},
// 监视方式1
watch:{
isHot:{
immediate: true, // 初始化时让handler调用一下
// handler 在isHot发生改变时调用
handler(newValue, oldValue){
console.log('isHot修改了', newValue, oldValue)
}
},
info:{
handler(newValue, oldValue){
console.log('info修改了', newValue, oldValue)
}
},
// 深度监视:监视多级结构中某个属性的变化
'numbers.a':{
handler(){
console.log('a被改变了')
}
},
// 监视多级结构中所有属性的变化
numbers:{
deep: true, // 开启深度监视
handler(){
}
},
// 监视属性的简写形式(当只需要handler时可以)
isHot(newValue, oldValue){
console.log('isHot被修改了3', newValue, oldValue)
}
}
})
// 监视方式2
vm.$watch('isHot', {
immediate: true, // 初始化时让handler调用一下
// handler 在isHot发生改变时调用
handler(newValue, oldValue){
console.log('isHot修改了2', newValue, oldValue)
}
})
// 监视属性的简写形式(不能是箭头函数)
vm.$watch('isHot', function(newValue, oldValue){
console.log('isHot修改了4', newValue, oldValue)
})
</script>
computed 和 watch 之间的区别:
- computed 能完成的功能,watch 都可以完成;
- watch 能完成的功能,computed不一定能完成,例如:watch可以进行异步操作。
两个重要的小原则:
- 所被Vue管理的函数,最好写成普通函数,这样this的指向才是vm或组件实例对象;
- 所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等,Promise的回调函数),最好写成箭头函数,这样this的指向才是vm或组件实例对象。
9、class 与 style 绑定
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=dege" />
<meta name="viewpoint" content="width=device-width, initial-scale=1.0">
<title>绑定样式</title>
<!-- 引入 Vue -->
<script type="text/javascript" src="./js/vue.js"></script>
<style>
.basic{}
.happy{}
.sad{}
.normal{}
.atguigu1{}
.atguigu2{}
.atguigu3{}
</style>
</head>
<body>
<div id="root">
<!-- 绑定class样式的字符串写法,适用于样式的类名不确定,需要动态指定-->
<div class="basic" :class="mood" @click="changeMood">{{name}}</div>
<!-- 绑定class样式的数组写法,适用于绑定的样式个数不确定,名字也不确定-->
<div class="basic" :class="classArr">{{name}}</div> <!-- 方式1-->
<div class="basic" :class="['atguigu1', 'atguigu2', 'atguigu3']">{{name}}</div> <!-- 方式2-->
<!-- 绑定class样式的对象写法,适用于要绑定的样式个数确定,名字也确定,但要动态决定用不用-->
<div class="basic" :class="classObj">{{name}}</div>
<!-- 绑定style样式的对象写法-->
<div class="basic" :style="styleObj">{{name}}</div>
<!-- 绑定style样式的数组写法-->
<div class="basic" :style="[styleObj, styleObj2]">{{name}}</div>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false
const vm = new Vue({
el: '#root',
data:{
name: '尚硅谷',
mood: 'normal', // 常规样式的名称
classArr: ['atguigu1', 'atguigu2', 'atguigu3'],
classObj:{
atguigu1: false,
atguigu2: true
},
styleObj:{
fontSize: '40px'
},
styleObj2:{
fontSize: '40px'
}
},
methods:{
changeMood(){
const moods = ['happy', 'sad', 'normal']
const index = Math.floor(Math.random() * 3);
this.mood = moods[index];
}
}
})
</script>
</html>
10、条件渲染
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=dege" />
<meta name="viewpoint" content="width=device-width, initial-scale=1.0">
<title>Title</title>
<!-- 引入 Vue -->
<script type="text/javascript" src="./js/vue.js"></script>
<style>
</style>
</head>
<body>
<div id="root">
<!-- 使用v-show做条件渲染(适用于切换频率高场景),不展示的DOM元素不会被移除,而是被隐藏掉-->
<h2 v-show="a">欢迎来到尚硅谷</h2>
<h2 v-show="1 === 1">欢迎来到尚硅谷</h2>
<!-- 使用v-if做条件渲染(适用于切换频率低场景,不展示的DOM元素直接被移除)-->
<h2 v-if="false">欢迎来到{{name}}</h2>
<h2 v-if="1 === 1">欢迎来到{{name}}</h2>
<h2>当前的n值是:{{n}}</h2>
<button @click="n++">点我n+1</button>
<div v-show="n === 1">Angular</div>
<div v-show="n === 2">React</div>
<div v-show="n === 3">Vue</div>
<!-- 结构是一个整体,不能被打断 -->
<div v-if="n === 1">Angular</div>
<div v-else-if="n === 2">React</div>
<div v-else-if="n === 3">Vue</div>
<div v-else>XXXXX</div>
<!-- 模板只能和v-if配合使用,template在解析之后会消失-->
<template v-if="n === 1">
<h2 v-show="n === 1">你好</h2>
<h2 v-show="n === 1">atguigu</h2>
<h2 v-show="n === 1">北京</h2>
</template>
<!-- 使用v-if时,元素可能无法获取到,而使用v-show一定可以获取到-->
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false
const vm = new Vue({
el: '#root',
data:{
name: 'atguigu',
a: false,
n: 0
}
})
</script>
</html>
11、列表渲染
react、vue中的key有什么作用(key的内部原理):
- 虚拟DOM中key的作用:key是虚拟DOM对象的标记,当状态中的数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】,随后Vue进行【新虚拟DOM】与【旧虚拟DOM】。
- 对比规则:① 旧虚拟DOM中内容没变,直接使用之前的真实DOM;若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM。② 旧虚拟DOM中未找到与新虚拟DOM相同的key,创建新的真实DOM,随后渲染到页面。
- 用index作为key可能会引发的问题:① 若对数据进行:逆序添加、逆序删除等破坏顺序操作,会产生没有必要的真实DOM更新,界面效果没问题,但效率低;② 如果结构中还包含输入类的DOM,会产生错误的DOM更新,界面有问题。
- 开发中如何选择key:① 最好使用每条数据的唯一表示作为key,比如id,手机号,身份证号等唯一值;② 如果不存在对数据的逆序添加,逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。
正确方式:
代码示例:
<body>
<div id="root">
<h2>遍历数组-人员列表</h2>
<ul>
<!-- v-for 用于遍历 -->
<li v-for="p in persons" :key="p.id">
{{p.name}}-{{p.age}}
</li>
</ul>
<button @click.once="add">添加一个老刘</button>
<h2>遍历对象-汽车信息</h2>
<ul>
<!-- 使用 () of persons 也行-->
<li v-for="(value, key) in car" :key="key">
{{value}}-{{key}}
</li>
</ul>
<h2>遍历字符串</h2>
<ul>
<li v-for="(char, index) of str" :key="index">
{{char}}-{{index}}
</li>
</ul>
<h2>遍历指定次数</h2>
<ul>
<li v-for="(number, index) of 5">
{{number}}-{{index}}
</li>
</ul>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false
new Vue({
el: '#root',
data:{
persons:[
{id:'001', name:'张三', age:18},
{id:'002', name:'李四', age:19},
{id:'003', name:'王五', age:20}
],
car: {
name: '奥迪A8',
price: 20,
},
str: 'hello'
},
methods:{
add(){
const p = {id:'004', name:'老刘', age:40}
this.persons.unshift(p)
}
}
})
</script>
12、列表过滤
<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>
</body>
<script type="text/javascript">
Vue.config.productionTip = false
// 监视实现
// new Vue({
// el: '#root',
// data:{
// persons:[
// {id:'001', name:'马冬梅', age:18, sex:'女'},
// {id:'002', name:'周冬雨', age:19, sex:'女'},
// {id:'003', name:'周杰伦', age:20, sex:'男'},
// {id:'003', name:'温兆伦', age:20, sex:'男'},
// ],
// filPersons:[],
// keyword: ""
// },
// watch:{
// keyword:{
// immediate: true,
// handler(val){
// // filter 会返回新数组
// this.filPersons = this.persons.filter((p)=>{
// return p.name.indexOf(val) !== -1
// })
// }
// }
// },
// })
// 计算属性实现
new Vue({
el: '#root',
data:{
persons:[
{id:'001', name:'马冬梅', age:18, sex:'女'},
{id:'002', name:'周冬雨', age:19, sex:'女'},
{id:'003', name:'周杰伦', age:20, sex:'男'},
{id:'003', name:'温兆伦', age:20, sex:'男'},
],
keyword: ""
},
computed: {
filPersons(){
this.persons.filter((p)=>{
return p.name.indexOf(this.keyword) !== -1
})
}
}
})
</script>
13、列表排序
<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 filPerson" :key="p.id">
{{p.name}}-{{p.age}}-{{p.sex}}
</li>
</ul>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false
new Vue({
el: '#root',
data:{
keyword: '',
sortType: 0, // 0原顺序,1降序,2升序
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:{
filPerson(){
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>
14、Vue监测数据的原理
Vue会监视data中所有层次的数据。
如何监测对象中的数据:通过setter实现监视,且要在nuew 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()。
特别注意:Vue.set() 和 vm.$set() 不能给vm或vm的根数据对象添加属性。
模拟数据监测:
<script type="text/javascript">
// 模拟数据监测
let data = {
name: '尚硅谷',
address: '北京'
}
// 创建一个监视的实例对象,用于监视data中属性的变化
const obs = new Observer(data)
console.log(obs)
// 准备一个vm实例对象
let vm = {}
vm._data = data = obs
function Observer(obj) {
// 汇总对象中所有的属性形成一个数组
const keys = Object.keys(obj)
// 遍历
keys.forEach((k)=>{
Object.defineProperty(this, k, {
get(){
return obj[k];
},
set(val){
console.log('${val}被修改了,我要去解析模板,生成虚拟DOM')
obj[k] = val
}
})
})
}
</script>
15、收集表单数据
若<input type="text"/>
,则v-model收集的是value值,用户输入的就是value值;
若<input type="radio"/>
,则v-model收集的是value值,且要给标签配置value值;
若<input type="checkbox"/>
:
- 没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,是布尔值);
- 配置input的value属性:① v-model的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值);② v-model的初始值是数组,那么收集的就是value组成的数组。
备注:v-model的3个修饰符:① lazy:失去焦点再收集数据;② number:输入字符串转为有效的数字;③ trim:输入首尾空格过滤。
<body>
<div id="root">
<!-- 表单提交的默认行为是跳转-->
<form @submit.prevent="demo">
<!-- trim 修饰表示去除前后的空格-->
账号:<input type="text" v-model.trim="userInfo.account"><br/>
密码:<input type="password" v-model="userInfo.password"><br/>
<!-- 加number是说明为数字-->
年龄:<input type="number" v-model.number="userInfo.age"><br/>
性别:
男<input type="radio" name="sex" v-model="userInfo.sex" value="male">
女<input type="redio" name="sex" v-model="userInfo.sex" value="female"><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/>
所属校区
<select v-model="userInfo.city">
<option value="">请选择校区</option>
<option value="beigjing">北京</option>
<option value="shanghai">上海</option>
<option value="shenzhen">深圳</option>
<option value="wuhan">武汉</option>
</select>
<br/>
其他信息:
<!-- lazy 修饰符表示失去焦点才会更新-->
<textarea v-model.lazy="userInfo.other"></textarea><br/>
<input type="checkbox" v-model="userInfo.agree">阅读并接受<a href="http://www.baidu.com">《用户协议》</a>
<button>提交</button>
</form>
</div>
</body>
<script type="text/javascript">
const vm = new Vue({
el: '#root',
data:{
userInfo:{
account: '',
password: '',
age: '',
sex: 'female',
hobby: [],
city: '',
agree: ''
}
},
methods:{
demo(){
alert('表单提交了' + JSON.stringify(this.userInfo))
}
}
})
</script>
16、过滤器
过滤器:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)。
语法:
- 注册过滤器:Vue.filter(name, callback) 或 new Vue(filters:{}}
- 使用过滤器: {{xxx | 过滤器名}} 或 v-bind:属性='xxx | 过滤器名'
备注:
- 过滤器也可以接收额外参数,多个过滤器也可以串联;
- 并没有改变原本的数据,是产生新的对应的数据。
17、内置指令
- v-bind:单向绑定解析表达式,可简写为:xxx;
- v-model:双向数据绑定;
- v-for:遍历数组/对象/字符串;
- v-on:绑定事件监听,可简写为@;
- v-if:条件渲染(动态控制节点是否存在);
- v-else:条件渲染(动态控制节点是否存在);
- v-show:条件渲染(动态控制节点是否存在);
- v-text:向其所在的节点中渲染文本内容,与插值语法的区别:v-text会替换掉节点中的内容,{{xxx}}则不会。
- v-html:向指定节点中渲染包含html结构的内容。与插值语法的区别:① v-html会替换掉节点中所有内容,{{xx}}则不会;② v-html可以识别html结构。注意v-html有安全性问题,在网站上动态渲染任意htl是非常危险的,容易导致XSS攻击,一定要在可信的内容上使用v-html,永不要用在用户提交的内容上。
- v-cloak:没有值。本质上是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性。使用css配合v-cloak可以解决网速慢时页面展现出{{xxx}}的问题。
- v-once:没有值。v-once所在节点在初次动态渲染后,就视为静态内容了。以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。
- v-pre:没有值。可以跳过其所在节点的编译过程,可利用它跳过没有使用指令语法、没有使用插值语法的节点,会加快编译。
18、自定义指令
定义语法:
- 局部指令:
new Vue({ directives:{指令名:配置对象} }) 或 new Vue({ directives(){指令名, 回调函数} })
- 全局指令:
Vue.directive(指令名, 配置对象) 或 Vue.directive(指令名, 回调函数)
配置对象中常用的3个回调:
- .bind:指令与元素成功绑定时调用;
- .inserted:指令所在元素被插入页面时调用;
- .update:指令所在模板结构被重新解析时调用。
备注:
- 指令定义时不加v-,但使用时要加v-;
- 指令名如果是多个单词,要使用kebab-case命名方式,不要用camelCase命名。
<!-- 需求:
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>
<div id="root2">
<input type="text" v-fbind:value="x">
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false
Vue.directives('fbind', {
sth(){
// todo
}
})
new Vue({
el: '#root',
data:{
n: 1
},
directives:{
// 表示在element元素上的v指令,并且绑定了binding值
// 调用时机:1、指令与元素成功绑定时(一上来);2、指令所用到的数据发生更新时(实际是指令所在的模板被重新解析时)
big(element, binding){
console.log(this) // 注意此处的this为window
element.innerText = binding.value * 10
},
fbind:{
// 调用时机:指令与元素成功绑定时(一上来)
bind(element, binding){
element.value = binding.value
},
// 指令所在元素被插入页面时
inserted(element, binding){
element.focus()
},
// 指令所在模板被重新解析时
update(element, binding){
element.value = binding.value
element.focus()
}
}
}
})
new Vue({
el: '#root2',
data:{
x: 1
}
})
</script>
19、生命周期
生命周期又称生命周期回调函数、生命周期函数、生命周期钩子。它是Vue在关键时刻帮我们调用的一些特殊名称的函数。生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的。生命周期函数中的this指向是vm或组件实例对象。
常用的生命周期钩子:① mounted:发送ajax请求、启动定时器、绑定自定义事件、订阅消息等初始化操作;② beforeDestroy:清除定时器、解绑自定义事件、取消订阅消息等收尾工作。
关于销毁Vue实例:
- 销毁后借助Vue开发者工具看不到任何信息;
- 销毁后自定义事件会失效,但原生DOM事件依然有效;
- 一般不会再把beforeDestroy操作数据,因为即使操作数据,也不会再触发更新流程了。
<body>
<div id="root">
<h2 v-if="a">你好啊</h2>
<h2 :style="{opacity}">欢迎学习Vue</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 = false
const vm = new Vue({
el: '#root',
// template:`
// <div>
// <h2>当前的n值是:{{n}}</h2>
// <button @click="add">点我n+1</button>
// </div>
// `,
data:{
a: true,
opacity: 1,
n: 0,
id: 0
},
methods:{
add(){
this.n++
},
stop(){
clearInterval(this.id)
},
bye(){
console.log('bye')
// this.$destroy()
}
},
// Vue 生命周期
// 0:new Vue() :创建Vue
// 1、初始化生命周期、事件,但数据代理还未开始
// 2、执行钩子
beforeCreate(){
console.log('beforeCreate')
console.log(this) // 是vm,初始化生命周期、事件,但数据代理还未开始
// debugger; // 卡一个断点
},
// 3、初始化数据监测、数据代理
// 4、执行钩子
created(){
console.log('created')
console.log(this) // 是vm
},
// 5、开始解析模板,生成虚拟DOM(内存中),页面还不能显示解析好的内容
// 6、执行钩子
beforeMount(){
console.log('beforeMount')
console.log(this) // 是vm,此时页面呈现的是未经vue编译的DOM,所有对DOM的操作都不奏效
},
// 7、将内存中的虚拟DOM转为真实DOM呈现到页面
// 8、执行钩子:Vue完成模板的解析并把初始的真实的DOM元素放入页面后(挂载完毕)调用mounted,只会调用一次
mounted(){
console.log(this) // 是vm,此时页面中呈现的是经过Vue编译的DOM,对DOM的操作均有效(尽可能避免)
// 至此初始化过程结束,一般在此进行:开启定时器;发送网络请求;订阅消息;绑定自定义事件等初始化操作
this.id = setInterval(()=>{
this.opacity -= 0.01
if (vm.opacity <= 0) {
vm.opacity = 1
}
}, 16)
},
// 9、初始化结束,开始更新流程。
// 10、当数据发生改变时触发该钩子,此时数据是最新的,但页面是旧的,未保持同步
beforeUpdate(){
console.log('beforeUpdate')
},
// 11、根据新数据,生成新的虚拟DOM,随后与旧的虚拟DOM进行比较,最终完成页面更新,即完成了Model->View的更新
// 12、执行钩子
updated(){
// 此时,数据是新的,页面也是新的,保持了同步
},
// 13、如果不销毁,则回到第10步,不断更新,如果触发销毁,则往后进行
// 14。执行钩子
beforeDestroy(){
// 此时,vm中所有的data、methods、指令等等,都处于可用状态,马上要执行销毁过程,一般在此阶段:
// 关闭定时器、取消订阅消息、解绑自定义事件等收尾操作
console.log('beforeDestroy')
this.stop() // 防止推出前没有停止定时器
},
// 15、执行销毁工作
// 16、执行钩子
destroyed(){
console.log('destroyed')
}
})
</script>
20、Vue 组件编程
20.1、模块与组件、模块化与组件化
模块:向外提供特定功能的js程序,一般就是一个js文件。作用是复用js,简化js的编写,提高js运行效率。
组件:用来实现局部(特定)功能效果的代码集合(html/css/js/image)。作用是复用编码,简化项目编码,提高运行效率。
模块化:当应用中的js都以模块来编写的,那这个应用就是一个模块化的应用。
组件化:当应用中的功能都是多组件的方式来编写的,那这个应用就是一个组件化的应用。
20.2、非单文件组件
非单文件组件:一个文件中包含有n个组件。
Vue中使用组件的三大步骤:
- 定义组件(创建组件);
- 注册组件;
- 使用组件(写组件标签)。
如何定义一个组件:使用Vue.extend(options)创建,其中options和new Vue(options)时传入的哪个options几乎一样,但区别如下:
- el不要写:最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器;
- data必须写成函数:避免组件被复用时,数据存在引用关系。
注意:使用 template 可以配置组件结构。
如何注册组件:
- 局部注册:靠new Vue的时候传入components选项;
- 全局注册:靠Vue.component('组件名', 组件)
编写组件标签:<school></school>
关于组件名:
- 一个单词组成:① 首字母小写: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
<body>
<!-- 准备好一个容器-->
<div id="root">
<hello></hello>
<!-- 编写组件标签-->
<school></school>
<hr>
</div>
<div id="root2">
<hello></hello>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false
// 创建局部student组件(简写形式)
const studentComp = {
template:`
<div>
<h2>学生姓名:{{studentName}}</h2>
<h2>学生年龄:{{age}}</h2>
</div>
`,
// 一定不要写el配置项,因为最终所有的组件都要被一个vm管理
data(){
return {
studentName: '张三',
age: 18
}
}
}
// 创建局部school组件
const schoolComp = Vue.extend({
template:`
<div>
<h2>学校名称:{{schoolName}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click='showName'>点我提示学校名</button>
<student></student>
</div>
`,
// 一定不要写el配置项,因为最终所有的组件都要被一个vm管理
data(){
return {
schoolName: '尚硅谷',
address: '北京昌平',
}
},
methods:{
showName(){
alert(this.schoolName)
}
},
components:{
student: studentComp
}
})
// 创建全局的组件
const helloComp = Vue.extend({
name: 'hello', // 开发者提示工具中的名称
template:`
<div?
<h2>你好 {{name}}</h2>
</div>
`,
data(){
return {
name: 'Tom'
}
}
})
const hello = Vue.component('hello', helloComp) // 注册全局组件,所有的vm都能使用
// 创建vm
const vm = new Vue({
el: '#root',
data: {
msg: '你好'
},
// 注册组件(局部注册)
components:{
school: schoolComp,
}
})
const vm2 = new Vue({
el: '#root2',
})
</script>
20.3、单文件组件
关于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()配置中,data函数、methods中的函数、watch中的函数、computed中的函数,它们的this均是Vue实例对象。
- VueComponent的实例对象,以后简称vc(也可称之为组件实例对象)。Vue的实例对象,以后简称vm。
单文件组件:一个文件中只包含1个组件。
一个重要的内置关系:VueComponent.prototype.__proto__ === Vue.prototype。目的是为了让组件实例对象(vc)可以访问到Vue原型上的属性、方法。
模块化拆分版本:
index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Title</title>
</head>
<body>
<!-- 准备一个容器 -->
<div id="root">
</div>
<!-- <script type="text/javascript" src="../../js/vue.js"></script>
<script type="text/javascript" src="./main.js"></script> -->
</body>
</html>
main.js:
import App from './App.vue'
new Vue({
el: '#root',
template:`<App></App>`,
components:{
App
}
})
App.vue:
<template>
<div>
<School></School>
<Student></Student>
</div>
</template>
<script>
// 引入组件
import School from './School'
import Student from './Student'
export default {
name: 'App',
components:{
School,
Student
}
}
</script>
School.Vue:
<template>
<div class="demo">
<h2>学校名称:{{ schoolName }}</h2>
<h2>学校地址:{{ address }}</h2>
<button @click="showName">点我提示学校名</button>
</div>
</template>
<script>
// export const school = Vue.extend({})
// 简写:
export default {
name: 'School', // 与文件名保持一致
data(){
return {
schoolName: '尚硅谷',
address: '北京昌平'
}
},
methods:{
showName(){
alert(this.schoolName)
}
}
}
</script>
<style>
.demo{
background-color: orange;
}
</style>
Student.vue:
<template>
<div class="demo">
<h2>学生名称:{{name}}</h2>
<h2>年龄{{ age }}</h2>
<button @click="showName">点我提示学生名</button>
</div>
</template>
<script>
// export const student = Vue.extend({})
// 简写:
export default {
name: 'Student', // 与文件名保持一致
data(){
return {
name: '张三',
age: '18'
}
},
methods:{
showName(){
alert(this.name)
}
}
}
</script>
<style>
</style>
21、使用Vue脚手架
Vue脚手架是Vue官方提供的标准化开发工具。文档:https://cli.vuejs.org/zh/
21.1、具体步骤
以管理员身份执行以下步骤:
- 第一步:仅第一次执行。全局安装@vue/cli:npm install -g @vue/cli
- 第二步:切换到你要创建项目的目录,然后使用命令创建项目:vue create xxxx
- 第三步:启动项目:npm run serve
备注:
- 如出现下载缓慢请配置npm淘宝镜像:npm config set registry https://registry.npmmirror.com
- Vue脚手架隐藏了所有webpack相关的配置,若想看具体的webpack配置,请执行:vue inspect > output.js
生成的项目的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">
<!-- 配置页签图标 public目录中的资源需要使用 BASE_URL-->
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<!-- 配置网页标题 -->
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<!-- 如果浏览器不支持js,则该标签中的元素就会被渲染 -->
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<!-- 容器 -->
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
生成的main.js:
/**
* 该文件是整个项目的入口文件
*/
// 引入Vue
// import Vue from 'vue/dist/vue' // 引入完整版的vue
import Vue from 'vue' // 引入残缺版的vue(缺少模板解析器)
// 引入App组件,它是所有组件的父组件
import App from './App.vue'
Vue.config.productionTip = false
/**
* 关于不同版本的Vue:
* ① vue.js 与 vue.runtime.xxx.js 的区别:
* 1)vue.js是完整版的Vue,包含:核心功能+模板解析器;
* 2)vue.runtime.xxx.js是运行版的Vue,只包含:核心功能,没有模板解析器
* ② 因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用render函数接收到的createElement函数去指定具体内容
*/
// 创建Vue的实例对象
new Vue({
// 下面这行代码完成了:将App组件放入容器中
// render: h => h(App),
render(createElement){ // createElement 是一个创建元素的函数
return createElement('h1', '你好啊')
}
}).$mount('#app') // $mount()作用是将vue挂在到app元素上,与el:'#app'相同
22、ref属性
ref属性:
- 被用来给元素或子组件注册引用信息(id的替代者);
- 应用在html标签上获取真实DOM元素,应用在组件是上是组件实例对象(vc);
- 使用方式:① 打标识:<h1 ref='xx'></h1>;② 获取:this.$refs.xxx
<template>
<div>
<h1 v-test='msg' ref='title'></h1>
<button @click='showDom'>点我输出上方的DOM元素</button>
<School ref='sch'/>
<School/>
<School/>
</div>
</template>
<script>
// 引入School组件
import School from './components/School'
export default {
name: 'App',
components:{School},
data(){
return {
msg: '欢迎学习Vue'
}
},
methods:{
showDom(){
console.log(this.$refs.title) // 获取到的是真实DOM元素
console.log(this.$refs.sch) // 获取到的是组件实例对象
}
}
}
</script>
<style>
</style>
23、props配置
功能:让组件接收外部传过来的数据。
- 传递数据:<Demo name='xxx' />
- 接收数据:三种方式,下面会演示。
备注:props是只读的,Vue底层会渐层你对props的修改,如果进行了修改,就会发出警告。若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。
App.vue:
<template>
<div>
<Student name='李四' sex='女' :age='18'/>
<br>
<Student name='王五' sex='男' :age='19'/>
<br>
</div>
</template>
<script>
import Student from './components/Student.vue'
export default {
name: 'App',
components:{Student},
data(){
return {
msg: '欢迎学习Vue'
}
},
}
</script>
<style>
</style>
Student.vue:
<template>
<div>
<h2>学生姓名:{{name}}</h2>
<h2>学生性别:{{sex}}</h2>
<h2>学生年龄:{{myAge}}</h2>
<button @click='updateAge'>点我修改年龄</button>
</div>
</template>
<script>
export default {
name: 'Student',
data(){
return {
myAge: this.age // 因为props优先级更高
}
},
methods:{
updateAge(){
this.myAge++
}
},
// props中的属性从外部传入,且无法修改
// props:['name', 'sex', 'age'] // 外部传入的属性(简单声明接收)
//
// props:{
// name: String,
// age: Number,
// sex: String
// }
// 接受的同时对数据进行类型限制+默认值的指定+必要性的限制
props:{
name:{
type: String, // name类型是字符串
required: true // name是必要的
},
age:{
type: Number,
default: 99, // 默认值
},
sex:{
type: String,
required: true
}
}
}
</script>
<style>
.student{
background-color: aqua;
}
</style>
24、mixin (混合/混入)属性
mixin(混合/混入):可以把多个组件共用的配置提取成一个混入对象。
使用方式:
- 定义混合:
export const hunhe = { methods:{ showName(){ alert(this.name) } } }
- 使用混合:① 全局混合:Vue.mixin(xxx);② 局部混合:mixins:['xxx']
25、插件
插件功能:用于增强Vue。
本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据。
定义插件(plugins.js):
export default {
install(Vue, a){
console.log('@@@install', Vue)
// 全局过滤器
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
}
}
})
// 给原型上添加方法(vm和vc都能用了)
Vue.prototype.hello = () => {alert('你好')}
}
}
使用插件(main.js):
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
// 引入插件
import plugins from './plugins'
// 使用插件
Vue.use(plugins, 1) // 可以加自定义参数
new Vue({
el: '#app',
render: h => h(App)
})
School.vue:
<template>
<div>
<h2>学校名称:{{name | mySlice}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click='test'>点我测试一下hello方法</button>
</div>
</template>
<script>
export default {
name: 'School',
data(){
return {
name: '尚硅谷atguigu',
address: '北京昌平'
}
},
methods:{
test(){
this.hello()
}
}
}
</script>
<style>
.school{
background-color: aqua;
}
</style>
Student.vue:
<template>
<div>
<h2>学生姓名:{{name}}</h2>
<h2>学生性别:{{sex}}</h2>
<input type='text' v-fbind:value='name'>
</div>
</template>
<script>
export default {
name: 'Student',
data(){
return {
name: '张三',
sex: '男'
}
},
}
</script>
<style>
.student{
background-color: aqua;
}
</style>
26、scoped 样式
scoped:让样式在局部生效,防止冲突。
School.vue:
<template>
<div class='demo'>
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
</div>
</template>
<script>
export default {
name: 'School',
data(){
return {
name: '尚硅谷atguigu',
address: '北京昌平'
}
},
}
</script>
<style scoped>
.demo{
background-color: orange;
}
</style>
Student.vue:
<template>
<div class='demo'>
<h2>学生姓名:{{name}}</h2>
<h2>学生性别:{{sex}}</h2>
</div>
</template>
<script>
export default {
name: 'Student',
data(){
return {
name: '张三',
sex: '男'
}
},
}
</script>
<style scoped>
.demo{
background-color: blue;
}
</style>
27、浏览器本地存储
浏览器通过Window.sessionStorage 和 Window.localStorage 属性来实现本地存储机制。
localStorage.setItem('msg', 'hello!')
localStorage.getItem('msg')
localStorage.removeItem('msg')
sessionStorage.setItem('msg', 'hello')
sessionStorage.getItem('msg')
sessionStorage.removeItem('msg')
备注:
- SessionStorage 存储的内容会随着浏览器窗口关闭而消失;
- LocalStorage存储的内容,需要手动清除才消失;
- xxxStorage.getItem(xxx)如果xxx对应的value获取不到,那么getItem()的返回值是null;
- JSON.parse(null)的结果依然是null。
28、组件的自定义事件
组件的自定义事件是一种组件间的通信方式,适用于:子组件===>父组件。
使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中);
绑定自定义事件方式:如下演示。
App.vue:
<template>
<div class='app'>
<h1>{{msg}},学校姓名是:{{schoolName}}</h1>
<!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 -->
<School :getSchoolName='getSchoolName'/>
<hr>
<!-- 写法1:通过父组件给子组件绑定一个自定义事件实现:子给父传递数据 -->
<Student v-on:atguigu='getStudentName'/> <!-- 使用once表示只触发一次-->
<!-- 写法2(更灵活):通过父组件给子组件绑定一个自定义事件实现:子给父传递数据 -->
<Student ref='student' @click.native='show'/>
</div>
</template>
<script>
import School from './components/School.vue'
import Student from './components/Student.vue'
export default {
name: 'App',
components:{Student, School},
data(){
return {
msg: '你好',
schoolName: '',
studentName: ''
}
},
methods:{
getSchoolName(name){
console.log('App 收到了学校名', name)
this.schoolName = name
},
getStudentName(name){
console.log('App 收到了学生名1', name)
},
show(){
console.log('show')
}
},
mounted(){
// 延迟绑定
setTimeout(()=>{
// 要使用箭头函数,否则this指向会出问题(this指向的是产生事件的那个组件)
this.$refs.student.$on('atguigu', (name, ...params)=>{
console.log('App 收到了学生名2', name)
this.studentName = name
})
// this.$refs.student.$once('atguigu', this.getStudentName) // 只触发一次
}, 3)
}
}
</script>
<style scoped>
.app{
background-color: gray;
}
</style>
School.vue:
<template>
<div class='demo'>
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="sendSchoolName">把学校名给App</button>
</div>
</template>
<script>
export default {
name: 'School',
data(){
return {
name: '尚硅谷atguigu',
address: '北京昌平'
}
},
props:['getSchoolName'],
methods:{
sendSchoolName(){
this.getSchoolName(this.name)
}
}
}
</script>
<style scoped>
.demo{
background-color: orange;
padding: 5px;
margin-top: 30px;
}
</style>
Stuent.vue:
<template>
<div class='demo'>
<h2>学生姓名:{{name}}</h2>
<h2>学生性别:{{sex}}</h2>
<button @click="sendStudentName">把学生名给App</button>
<button @click='unbind'>解绑atguigu事件</button>
</div>
</template>
<script>
export default {
name: 'Student',
data(){
return {
name: '张三',
sex: '男'
}
},
methods:{
sendStudentName(){
// 触发Student组件实例上的atguigu事件
this.$emit('atguigu', this.name)
},
unbind(){
this.$off('atguigu') // 解绑一个自定义事件
// this.$off(['event1', 'event2']) // 解绑多个自定义事件
// this.$off() // 解绑所有自定义事件
}
}
}
</script>
<style scoped>
.demo{
background-color: blue;
padding: 5px;
margin-top: 30px;
}
</style>
29、全局事件总线
全局事件总线(GlobalEventBus)是一种组件间通信的方式,适用于任意组件间通信。
安装全局事件总线:
new Vue({
...
beforeCreate(){
Vue.prototype.$bus = this // 安装全局事件总线,$bus就是当前应用的vm
}
...
})
使用事件总线:
// 接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身
methods(){
demo(data){...}
}
...
mounted(){
this.$bus.$on('xxx', this.demo)
}
// 提供数据
this.$bus.$emit('xxx', 数据)
最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。
30、消息订阅与发布
使用pubsub.js。一种组件间通信的方式,适用于任意组件间通信。
使用步骤:
- 安装pubsub:npm i pubsub-js
- 引入:import pubsub from 'pubsub-js'
- 接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身
- 提供数据:pubsub.publish('xxx', 数据)
- 最好在beforeDestroy钩子中,用PubSub.unsubscribe(pid)去取消订阅
31、nextTick
nextTick:
- 语法:
this.$nextTick(回调函数)
- 作用:在下一次DOM更新结束后执行其指定的回调。
- 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。
32、过渡与动画
Vue封装的过渡与动画:在插入、更新或移除DOM元素时,在合适的时候给元素添加样式类名。
图示:
写法:
- 准备好样式:
// 元素进入的样式 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值。
<template>
<div>
<button @click='isShow = !isShow'>显示/隐藏</button>
<!-- 一个元素 -->
<transition name='tname' :appear='true'>
<h1 v-show='isShow'>你好啊!</h1>
</transition>
<!-- 多个元素 -->
<transition-group name='tname' :appear='true'>
<h1 v-show='!isShow' key='1'>g1</h1>
<h1 v-show='isShow' key='2'>g2</h1>
</transition-group>
</div>
</template>
<script>
export default {
name: 'Test',
data(){
return {
isShow: true
}
}
}
</script>
<style scoped>
h1{
background-color: orange;
}
.tname-enter-active{
animation: atguigu 1s;
}
.tname-leave-active{
animation: atguigu 1s reverse;
}
@keyframes atguigu {
from{
transform: translateX(-100%);
}
to{
transform: translateX(0px);
}
}
</style>
<template>
<div>
<button @click='isShow = !isShow'>显示/隐藏</button>
<transition name='tname' :appear='true'>
<h1 v-show='isShow'>你好啊!</h1>
</transition>
</div>
</template>
<script>
export default {
name: 'Test2',
data(){
return {
isShow: true
}
}
}
</script>
<style scoped>
h1{
background-color: orange;
}
/*进入的起点 和 离开的终点*/
.tname-enter, .tname-leave-to{
transform: translateX(-100%);
}
/*进入过程中,离开过程中*/
.tname-enter-active, .tname-enter-active {
transition: 0.5s linear;
}
/*进入的终点 和 离开的起点*/
.tname-enter-to, .tname-leave{
transform: translateX(0);
}
</style>
<template>
<div>
<button @click='isShow = !isShow'>显示/隐藏</button>
<!-- 多个元素 -->
<transition-group name='animate__animated animate_bounce'
enter-active-class="animate__swing"
leave-active-class="animate__backOutUp"
:appear='true'
>
<h1 v-show='isShow' key='2'>g2</h1>
</transition-group>
</div>
</template>
<script>
//使用 npm install animation.css
import 'animate.css'
export default {
name: 'Test3',
data(){
return {
isShow: true
}
}
}
</script>
<style scoped>
h1{
background-color: orange;
}
</style>
32、配置代理
在vue.config.js中配置:
module.exports = {
// 方式1
devServer: {
proxy: 'http://localhost:5000'
},
// 方式2
devServer: {
proxy: {
'/api': {
target: '<url>',
pathRewrite: {'^/api': '/'},
ws: true, // 用于支持websocket
changeOrigin: true // 用于控制请求头中host值
},
'/foo': {
target: '<other_url>'
}
}
}
}
33、插槽
作用:让父组件可以向子组件指定位置插入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>
- 作用域插槽:组件在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(game数据在Category组件中,但使用数据所遍历出来的结构由App组件决定)
父组件中: <Category> <template scope="scopeData"> <!-- 生成的是ul列表 --> <ul> <li v-for="g in scopeData.games" :key="g">{{g}}</li> </ul> </template> </Category> 子组件中: <template> <div> <slot :games="games"></slot> </div> </template> <script> export default { name: 'Category', props:['title'], // 数据在子组件自身 data(){ return { games:['红色警戒', '穿越火线', '劲舞团', '超级玛丽'] } }, } </script>
34、vuex
vuex是专门在Vue中实现集中式状态(数据)管理的一个Vue插件,对vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。
Github地址:https://github.com/vuejs/vuex
什么时候使用vuex:
- 多个组件依赖同一个状态;
- 来自不同组件的行为需要变更同一状态。
34.1、vuex原理图
34.2、搭建Vuex环境
// src/store/index.js
// 该文件用于创建Vuex中最为核心的store
// 引入Vue核心库
import Vue from 'vue'
// 引入Vuex
import Vuex from 'vuex'
// 应用Vuex插件
Vue.use(Vuex)
// 准备actions用于响应组件中的动作
const actions = {
jia: function(context, value){
context.commit("JIA", value)
}
}
// 准备mutaions用于操作数据(state)
const mutations = {
JIA(state, value){
state.sum += value
}
}
// 准备state用于存储数据
const state = {
sum: 98
}
// 准备getters
const getters = {
bigSum(state){
return state.sum * 10
}
}
// 创建并暴露store
export default new Vuex.Store({
actions,
mutations,
state,
getters
})
// main.js
...
// 引入store
import store from './store'
...
// 关闭Vue的生产提示
Vue.config.productionTip = false
// 使用插件
Vue.use(vueResource)
Vue.use(Vuex)
new Vue({
el: '#app',
render: h => h(App),
store
})
组件中读取vuex中的数据:$store.state.sum
组件中修改vuex中的数据:$store.dispatch('action中的方法名', 数据) 或 $store.commit('mutations中的方法名', 数据)
注:若没有网络请求或其他业务逻辑,组件中也可以越过actions,即不写dispatch,直接写commit。
34.3、4个map方法的使用
- 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']) }
- 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']) }
34.4、模块化
// index.js
const options1 = {
namespaced: true, // 启动命名空间
state:{},
actions:{},
mutations:{},
}
const options2 = {
namespaced: true, // 启动命名空间
state:{},
actions:{},
mutations:{},
}
export default new Vuex.Store({
modules:{
a: options1, // 启用命名空间后可以使用
b: options2
}
})
Count.vue:
...
export default {
name: 'Count',
computed:{
...mapState('a', ['xx', 'xx']) // 启用命名空间可以使用
},
methods:{
...mapMutations('a', {increment:'JIA', ...})
}
}
...
35、路由(vue-router)
vue-router是vue的一个插件库,专门用来实现SPA应用。
SPA(signle page web application):单页web应用,整个应用只有一个完整的页面,点击页面中的导航链接不会刷新页面,只会做页面的局部更新,数据需要通过ajax请求获取。
路由:一个路由就是一组映射关系(key-value),key为路径,value可能是function或component。
路由分类:
- 后端路由:value是function,用于处理客户端提交的请求。服务器接收到一个请求时,根据请求路径找到匹配的函数来处理请求,返回响应数据。
- 前端路由:value是component,用于展示页面内容。当浏览器的路径改变时,对应的组件就会显示。
vue2中vue-router只能使用版本3:npm i vue-router@3
应用插件:Vue.use(VueRouter)
router/index.js
// 该文件专门用于创建整个应用的路由器
import VueRouter from 'vue-router'
// 引入自己的组件
import MyComp1 from './components/MyComp1'
import MyComp2 from './components/MyComp2'
// 创建并暴露一个路由器
export default router = new VueRouter({
routes:[
{
path:'/mycomp1',
component:MyComp1
},
{
path:'/mycomp2',
component:MyComp2
}
],
})
注意:
- 路由组件通常存放在pages文件夹,一般组件通常存放在components文件夹;
- 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载;
- 每个组件都有自己的$route属性,里面存储着自己的路由信息;
- 整个应用只有一个router,可以通过组件的$router属性获取到。
35.1、嵌套(多级)路由
配置路由规则,使用children配置项:
routes:[
{
path: '/home',
children:[
{
path:'news', // 此处不要写成/news
component: News
},
{
...
}
]
}
]
// 跳转
<router-link to='/home/news'>News</router-link>
35.2、路由的query参数
接收参数:
$route.query.id
35.3、命名路由
作用:简化路由的跳转。
35.4、路由的params参数
35.5、路由的props配置
作用:让路由组件更方便地收到参数。
35.6、<router-link>的replace属性
作用:控制路由跳转时操作浏览器历史记录的模式。
浏览器的历史记录有两种写入方式:push和replace,push是追加历史记录,replace是替换当前记录。路由跳转时候默认为push。
如何开启replace模式:<router-link replace ......>dsaddsa</router-link>
35.7、编程式路由导航
作用:不借助<route-link>实现路由跳转,让路由跳转更加灵活。
35.8、缓存路由组件
作用:让不展示的路由组件保持挂载,不被销毁。
35.9、2个新的生命周期钩子
作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。
具体名字:
- activatd:路由组件被激活时触发。
- deactivated:路由组件失活时触发。
35.10、路由守卫
作用:对路由进行权限控制。
分类:全局守卫、独享守卫、组件内守卫。
全局守卫:
独享路由守卫:
组件内路由守卫:
35.11、路由器的两种工作模式
对于一个url来说,#及其后面的内容就是hash值。
hash值不会包含在HTTP请求中,即hash值不会带给服务器。
hash模式:
- 地址中永远带着#号,不美观。
- 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
- 兼容性较好。
history模式:
- 地址干净,美观。
- 兼容性和hash模式相比略差。
- 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。
36、Vue UI 组件库