为什么学习vue
1可能你的公司正要将原有的项目使用Vue进行重构。
2也可能是你的公司新项目决定使用Vue的技术栈。
3当然,如果你现在正在换工作,你会发现招聘前端的需求中,10个有8个都对Vue有或多或少的要求。
4当然,作为学习者我们知道Vuejs目前非常火,可以说是前端必备的一个技能。
认识vue
Vue (读音 /vjuː/,类似于 view),不要读错。
Vue是一个渐进式的框架,什么是渐进式的呢?
渐进式意味着你可以将Vue作为你应用的一部分嵌入其中,一点一点来开发,带来更丰富的交互体验。
或者如果你希望将更多的业务逻辑使用Vue实现,那么Vue的核心库以及其生态系统,比如【Vue核心+Vue-router+Vuex】,也可以满足你各种各样的需求。
Vue有很多特点和Web开发中常见的高级功能
-
解耦视图和数据
-
可复用的组件
-
前端路由技术
-
状态管理
-
虚拟DOM
Vue的MVVM
View层 -视图层:
在我们前端开发中,通常就是DOM层。
主要的作用是给用户展示各种信息。
Model层-数据层:
数据可能是我们固定的死数据,更多的是来自我们服务器,从网络上请求下来的数据。
VueModel层-视图模型层:
视图模型层是View和Model沟通的桥梁。
一方面它实现了Data Binding,也就是数据绑定,将Model的改变实时的反应到View中
另一方面它实现了DOM Listener,也就是DOM监听,当DOM发生一些事件(点击、滚动、touch等)时,可以监听到,并在需要的情况下改变对应的Data。
Vue的安装
1-直接CDN引入
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- 生产环境版本,优化了尺寸和速度 -->
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
2-下载和引入
开发环境 https://vuejs.org/js/vue.js 生产环境 https://vuejs.org/js/vue.min.js
3-NPM安装
Vue初体验
<div id="app">
<h2>{{message}}</h2>
<h1 >{{name}}</h1>
</div>
<div>{{message}}</div>
<script src="../js/vue.js"></script>
<script>
// let(变量)/const(常量)
// 编程范式: 声明式编程
const app = new Vue({
el: '#app', // 用于挂载要管理的元素
data: { // 定义数据
message: '你好啊,!',
name: 'zy'
}
})
// 原生js的做法(编程范式: 命令式编程)
// 1.创建div元素,设置id属性
// 2.定义一个变量叫message
// 3.将message变量放在前面的div元素中显示
// 4.修改message的数据: 今天天气不错!
// 5.将修改后的数据再次替换到div元素
</script>
创建Vue实例传入的options
el
-
类型:string | HTMLElement
-
作用:决定之后Vue实例会管理哪一个DOM。
data
-
类型:Object | Function (组件当中data必须是一个函数)
-
作用:Vue实例对应的数据对象。
methods
-
类型:{ [key: string]: Function }
-
作用:定义属于Vue的一些方法,可以在其他地方调用,也可以在指令中使用。
Vue的生命周期
vue实例从创建到销毁的过程中有很多个阶段,在每个阶段都有一些函数会被自动执行,可以方便的帮助我们完成自动被执行调用的逻辑。
八个生命周期
<body>
<!-- 生命周期 -->
<div id="demodiv">
<h1>{{text}}</h1>
<button v-on:click="text='我改了'">点我修改</button>
</div>
<script>
new Vue({
el:"#demodiv",
data:{
text:"生命周期"
},
methods:{},
watch:{},
computed:{},
filters:{},
beforeCreate(){
console.log("实例创建之前")
},
created(){
console.log("实例创建之后")
},
beforeMount(){
console.log("开始创建模板")挂载前
},
mounted(){
console.log("创建模板ok")挂载后
},
beforeUpdate(){
console.log("开始更新")
},
updated(){
console.log("更新完成")
},
beforeDestroy(){
console.log("开始销毁")
},
destroyed(){
console.log("销毁完成")
},
})
</script>
</body>
描述每个生命周期
- beforeCreate(创建前) 在数据观测和初始化事件还未开始
- created(创建后) 完成数据观测,属性和方法的运算,初始化事件,实例中的el属性还没有显示出来
- beforeMount(载入前) 在挂载开始之前被调用,相关的render函数首次被调用。实例已完成以下的配置:编译模板,把data里面的数据和模板生成html。注意此时还没有挂载html到页面上。
- mounted(载入后) 在el 被新创建的 vue.el 替换,并挂载到实例上去之后调用。实例已完成以下的配置:用上面编译好的html内容替换el属性指向的DOM对象。完成模板中的html渲染到html页面中。此过程中进行ajax交互。
- beforeUpdate(更新前) 在数据更新之前调用,发生在虚拟DOM重新渲染和打补丁之前。可以在该钩子中进一步地更改状态,不会触发附加的重渲染过程。
- updated(更新后) 在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。调用时,组件DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。
- beforeDestroy(销毁前) 在实例销毁之前调用。实例仍然完全可用。
- destroyed(销毁后) 在实例销毁之后调用。调用后,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。
什么是vue生命周期?
Vue 实例从创建到销毁的过程,就是生命周期。也就是从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、卸载等一系列过程,我们称这是 Vue 的生命周期。
vue生命周期的作用是什么?
生命周期中有多个事件钩子,让我们在控制整个Vue实例的过程时更容易完成指定逻辑。 U�
vue生命周期总共有几个阶段?
它可以总共分为8个阶段:创建前/后, 载入前/后,更新前/后,销毁前/销毁后
第一次页面加载会触发哪几个钩子?
第一次页面加载时会触发 beforeCreate, created, beforeMount, mounted 这几个钩子
DOM 渲染在 哪个周期中就已经完成?
DOM 渲染在 mounted 中就已经完成了。
Vue插值的操作
mustache语法
<!--mustache语法中,不仅仅可以直接写变量,也可以写简单的表达式-->
<!--Mustache: 胡子/胡须-->
<h2>{{firstName + lastName}}</h2>
<h2>{{firstName + ' ' + lastName}}</h2>
<h2>{{firstName}} {{lastName}}</h2>
<h2>{{counter * 2}}</h2>
v-once 只渲染一次
在某些情况下,我们可能不希望界面随意的跟随改变
这个时候,我们就可以使用一个Vue的指令
v-once:
该指令后面不需要跟任何表达式(比如之前的v-for后面是由跟表达式的)
该指令表示元素和组件(组件后面才会学习)只渲染一次,不会随着数据的改变而改变。
<h2 v-once>{{message}}</h2>
v-html
某些情况下,我们从服务器请求到的数据本身就是一个HTML代码
如果我们直接通过{{}}来输出,会将HTML代码也一起输出。
但是我们可能希望的是按照HTML格式进行解析,并且显示对应的内容。
如果我们希望解析出HTML展示
可以使用v-html指令
该指令后面往往会跟上一个string类型
会将string的html解析出来并且进行渲染
<div id="app">
<h2>{{url}}</h2>
<!-- v-html指令把数据按照html标签来解析-->
<h2 v-html="url"></h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
url: '<a href="http://www.baidu.com">百度一下</a>'
}
})
</script>
v-text
v-text作用和Mustache比较相似:都是用于将数据显示在界面中
v-text通常情况下,接受一个string类型
<h2>{{message}}, 李银河!</h2>
<h2 v-text="message">, 李银河!</h2>
v-pre
v-pre用于跳过这个元素和它子元素的编译过程,用于显示原本的Mustache语法。
比如下面的代码:
<div id="app">
<h2>{{message}}</h2>
<h2 v-pre>{{message}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
}
})
</script>
第一个h2元素中的内容会被编译解析出来对应的内容
第二个h2元素中会直接显示{{message}}
v-cloak
在某些情况下,我们浏览器可能会直接显然出未编译的Mustache标签。 ncloak: 斗篷。
用v-cloak先不让它显示出来,加载完成后再显示出来
<div id="app" v-cloak>
<h2>{{message}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
// 在vue解析之前, div中有一个属性v-cloak
// 在vue解析之后, div中没有一个属性v-cloak
setTimeout(function () {
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
}
})
}, 1000)
</script>
v-bind动态绑定属性
为什么有v-bind
除了内容需要动态来决定外,某些属性我们也希望动态来绑定。
比如动态绑定a元素的href属性
比如动态绑定img元素的src属性
这个时候,我们可以使用v-bind指令:
作用:动态绑定属性
缩写::
预期:any (with argument) | Object (without argument)
参数:attrOrProp (optional)
v-bind基本使用
<body>
<div id="app">
<!-- 错误的做法: 这里不可以使用mustache语法-->
<!--<img src="{{imgURL}}" alt="">-->
<!-- 正确的做法: 使用v-bind指令 -->
<img v-bind:src="imgURL" alt="">
<a v-bind:href="aHref">百度一下</a>
<!--<h2>{{}}</h2>-->
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
imgURL: 'https://img11.360buyimg.com/mobilecms/s350x250_jfs/t1/20559/1/1424/73138/5c125595E3cbaa3c8/74fc2f84e53a9c23.jpg!q90!cc_350x250.webp',
aHref: 'http://www.baidu.com'
}
})
</script>
</body>
v-bind语法糖
就是冒号 :
<div id="app">
<!--语法糖的写法-->
<img :src="imgURL" alt="">
<a :href="aHref">百度一下</a>
</div
v-bind动态绑定class属性
很多时候,我们希望动态的来切换class,比如:
当数据为某个状态时,字体显示红色。
当数据另一个状态时,字体显示黑色。
对象语法
绑定方式:对象语法
对象语法的含义是:class后面跟的是一个对象。
用法一:直接通过{}绑定一个类
<h2 :class="{'active': isActive}">Hello World</h2>
用法二:也可以通过判断,传入多个值
<h2 :class="{'active': isActive, 'line': isLine}">Hello World</h2>
用法三:和普通的类同时存在,并不冲突
注:如果isActive和isLine都为true,那么会有title/active/line三个类
<h2 class="title" :class="{'active': isActive, 'line': isLine}">Hello World</h2>
用法四:如果过于复杂,可以放在一个methods或者computed中
注:classes是一个计算属性
<h2 class="title" :class="classes">Hello World</h2>
如果对象语法过与复杂,可以将这个放到函数里或者计算属性里
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.active {
color: red;
}
</style>
</head>
<body>
<div id="app">
<h2 class="title" v-bind:class="{active: isActive, line: isLine}">{{message}}</h2>
<h2 class="title" v-bind:class="getClasses()">{{message}}</h2>
<button v-on:click="btnClick">按钮</button>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
isActive: true,
isLine: true
},
methods: {
btnClick: function () {
this.isActive = !this.isActive
},
getClasses: function () {
return {active: this.isActive, line: this.isLine}
}
}
})
</script>
</body>
</html>
数组语法
绑定方式:数组语法
数组语法的含义是:class后面跟的是一个数组。数组里面如果是常量要加单引号,否则常量会被当成变量来解析,是变量不用加
用法一:直接通过{}绑定一个类
<h2 :class="['active']">Hello World</h2>
用法二:也可以传入多个值
<h2 :class=“[‘active’, 'line']">Hello World</h2>
用法三:和普通的类同时存在,并不冲突
注:会有title/active/line三个类
<h2 class="title" :class=“[‘active’, 'line']">Hello World</h2>
用法四:如果过于复杂,可以放在一个methods或者computed中
注:classes是一个计算属性
<h2 class="title" :class="classes">Hello World</h2>
<body>
<div id="app">
<h2 class="title" :class="[active, line]">{{message}}</h2>
<h2 class="title" :class="getClasses()">{{message}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
active: 'aaaaaa',
line: 'bbbbbbb'
},
methods: {
getClasses: function () {
return [this.active, this.line]
}
}
})
</script>
</body>
v-bind动态绑定style
对象语法
-
:style="{color: currentColor, fontSize: fontSize + ‘px’}"
-
style后面跟的是一个对象类型
-
对象的key是CSS属性名称
-
对象的value是具体赋的值,值可以来自于data中的属性
-
value如果是具体的值,必须加上单引号,否则会被当成变量来解析
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.title {
font-size: 50px;
color: red;
}
</style>
</head>
<body>
<div id="app">
<!--<h2 :style="{key(属性名): value(属性值)}">{{message}}</h2>-->
<!--'50px'必须加上单引号, 否则是当做一个变量去解析-->
<!--<h2 :style="{fontSize: '50px'}">{{message}}</h2>-->
<!--finalSize当成一个变量使用-->
<!--<h2 :style="{fontSize: finalSize}">{{message}}</h2>-->
<h2 :style="{fontSize: finalSize + 'px', backgroundColor: finalColor}">{{message}}</h2>
<h2 :style="getStyles()">{{message}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
finalSize: 100,
finalColor: 'red',
},
methods: {
getStyles: function () {
return {fontSize: this.finalSize + 'px', backgroundColor: this.finalColor}
}
}
})
</script>
</body>
</html>
数组语法
<div v-bind:style="[baseStyles, overridingStyles]"></div>
- style后面跟的是一个数组类型
- 多个值以,分割即可
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<h2 :style="[baseStyle, baseStyle1]">{{message}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
baseStyle: {backgroundColor: 'red'},
baseStyle1: {fontSize: '100px'},
}
})
</script>
</body>
</html>
计算属性
什么是计算属性
- 我们知道,在模板中可以直接通过插值语法显示一些data中的数据。
- 但是在某些情况,我们可能需要对数据进行一些转化后再显示,或者需要将多个数据结合起来进行显示
- 比如我们有firstName和lastName两个变量,我们需要显示完整的名称。
- 但是如果多个地方都需要显示完整的名称,我们就需要写多个{{firstName}} {{lastName}},可以将这样的代码换成计算属性
- 计算属性是写在实例的computed选项中的。
- 希望将我们的数据进行某种变化再来显示,就可以用计算属性,给他重新定义一个属性,并且返回变化之后的数据
计算属性的基本使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<!-- 普通变化 -->
<h2>{{firstName + ' ' + lastName}}</h2>
<h2>{{firstName}} {{lastName}}</h2>
<!-- 调用方法 -->
<h2>{{getFullName()}}</h2>
<!-- 计算属性返回的 -->
<h2>{{fullName}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
firstName: 'Lebron',
lastName: 'James'
},
// computed: 计算属性()
computed: {
fullName: function () {
return this.firstName + ' ' + this.lastName
}
},
methods: {
getFullName() {
return this.firstName + ' ' + this.lastName
}
}
})
</script>
</body>
</html>
计算属性的复杂操作
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<h2>总价格: {{totalPrice}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
books: [
{id: 110, name: 'Unix编程艺术', price: 119},
{id: 111, name: '代码大全', price: 105},
{id: 112, name: '深入理解计算机原理', price: 98},
{id: 113, name: '现代操作系统', price: 87},
]
},
methods: {
getTotalPrice: function () {
let result = 0
for (let i=0; i < this.books.length; i++) {
result += this.books[i].price
}
return result
}
},
computed: {
totalPrice: function () {
let result = 0
for (let i=0; i < this.books.length; i++) {
result += this.books[i].price
}
return result
//es6用法
// for (let i in this.books) {
// this.books[i]
// }
//
// for (let book of this.books) {
//
// }
}
}
})
</script>
</body>
</html>
计算属性的setter和getter
computed: {
fullName: function () {
return this.firstName + ' ' + this.lastName
}
}
- 上面是计算属性一般写法:可以称为简写,但是其实每个属性对应保存的是一个对象,里面有set方法和get方法
- 每次需要改变数据形式的代码都在get方法里面写的,一般不想在set方法写入数据
- 只利用get方法只读属性来编写代码,所以一般set方法省略
- 而只有一个get方法也可以简写省略,用属性名代替get,所以每次调用的时候对应的属性后面都不用加括号,但是本质上写属性其实是调用其本身对应的get函数。
computed: {
fullName: {
set: function(newValue) {
},
get: function () {
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<h2>{{fullName}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
firstName: 'Kobe',
lastName: 'Bryant'
},
computed: {
// fullName: function () {
// return this.firstName + ' ' + this.lastName
// }
// name: 'coderwhy'
// 计算属性一般是没有set方法, 只读属性.
fullName: {
set: function(newValue) {
// console.log('-----', newValue);
const names = newValue.split(' ');
this.firstName = names[0];
this.lastName = names[1];
},
get: function () {
return this.firstName + ' ' + this.lastName
}
},
// fullName: function () {
// return this.firstName + ' ' + this.lastName
// }
}
})
</script>
</body>
</html>
计算属性和methods的对比
methods和computed看起来都可以实现我们的功能,那么为什么还要多一个计算属性这个东西呢?
原因:
- 计算属性会进行缓存,如果多次使用时,计算属性只会调用一次。
- 方法不会进行缓存,多次使用时,方法会被多次调用。
事件监听
在前端开发中,我们需要经常和用于交互。
这个时候,我们就必须监听用户发生的时间,比如点击、拖拽、键盘事件等等
在Vue中如何监听事件呢?使用v-on指令
v-on介绍
- 作用:绑定事件监听器
- 缩写:@
- 预期:Function | Inline Statement | Object
- 参数:event
v-on的基本使用和语法糖
- 可以直接写表达式
- 可以写函数调用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<h2>{{counter}}</h2>
<!--<button v-on:click="counter++">+</button>-->
<!--<button v-on:click="counter--;">-</button>-->
<!--<button v-on:click="increment">+</button>-->
<!--<button v-on:click="decrement">-</button>-->
<!-- 语法糖-->
<button @click="increment">+</button>
<button @click="decrement">-</button>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
counter: 0
},
methods: {
increment() {
this.counter++
},
decrement() {
this.counter--
}
}
})
</script>
</body>
</html>
v-on的参数传递问题
当通过methods中定义方法,以供@click调用时,需要注意参数问题:
-
如果该方法不需要额外参数,那么方法后的()可以不添加,如果方法本身中有一个参数,那么会默认将原生事件event参数传递进去
-
如果需要同时传入某个参数,同时需要event时,可以通过$event传入事件。
-
在事件定义时, 写方法时省略了小括号, 但是方法本身是需要一个参数的, 这个时候, Vue会默认将浏览器生产的event事件对象作为参数传入到方法
-
如果函数需要参数,但是没有传入, 那么函数的形参为undefined
-
方法定义时, 我们需要event对象, 同时又需要其他参数,在调用方式, 就需要手动的获取到浏览器参数的事件对象,给绑定的函数传入 $event参数就行。
<button @click="btn3Click("abc",$event)">按钮3</button>
v-on的修饰符
Vue提供了修饰符来帮助我们方便的处理一些事件:
- .stop - 调用 event.stopPropagation()。阻止事件冒泡
- .prevent - 调用 event.preventDefault()。阻止浏览器默认行为
- .{keyCode | keyAlias} - 只当事件是从特定键触发时才触发回调。
- .native - 监听组件根元素的原生事件。
- .once - 只触发一次回调。
<!--1. .stop修饰符的使用-->
<div @click="divClick">
aaaaaaa
<button @click.stop="btnClick">按钮</button>
</div>
<!--2. .prevent修饰符的使用-->
<br>
<form action="baidu">
<input type="submit" value="提交" @click.prevent="submitClick">
</form>
<!--3. .监听某个键盘的键帽-->
<input type="text" @keyup.enter="keyUp">
<!--4. .once修饰符的使用-->
<button @click.once="btn2Click">按钮2</button>
条件判断
- v-if、v-else-if、v-else
- 这三个指令与JavaScript的条件语句if、else、else if类似。
- Vue的条件指令可以根据表达式的值在DOM中渲染或销毁元素或组件
v-if的使用
v-if的原理:
- v-if后面的条件为false时,对应的元素以及其子元素不会渲染。也就是根本不会有对应的标签出现在DOM中。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<h2 v-if="isShow">
<div>abc</div>
<div>abc</div>
<div>abc</div>
<div>abc</div>
<div>abc</div>
{{message}}
</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
isShow: true
}
})
</script>
</body>
</html>
v-if和v-else的使用
<div id="app">
<h2 v-if="isShow">
{{message}}
</h2>
<h1 v-else>isShow为false时, 显示我</h1>
</div>
v-if和v-else-if和v-else的使用
<div id="app">
<h2 v-if="score>=90">优秀</h2>
<h2 v-else-if="score>=80">良好</h2>
<h2 v-else-if="score>=60">及格</h2>
<h2 v-else>不及格</h2>
<h1>{{result}}</h1>
</div>
v-show的使用
v-show的用法和v-if非常相似,也用于决定一个元素是否渲染:
<h2 v-show="isShow" id="bbb">{{message}}</h2>
v-show和v-if的区别
v-if和v-show对比
- v-if当条件为false时,压根不会有对应的元素在DOM中。
- v-show当条件为false时,仅仅是将元素的display属性设置为none而已。
开发中如何选择呢?
- 当需要在显示与隐藏之间切片很频繁时,使用v-show
- 当只有一次切换时,通过使用v-if
循环遍历
v-for遍历数组
- 当有一组数据需要进行渲染时,我们就可以使用v-for来完成。
- v-for的语法类似于JavaScript中的for循环。
- 格式如下:item in items的形式。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<!--1.在遍历的过程中,没有使用索引值(下标值)-->
<ul>
<li v-for="item in names">{{item}}</li>
</ul>
<!--2.在遍历的过程中, 获取索引值-->
<ul>
<li v-for="(item, index) in names">
{{index+1}}.{{item}}
</li>
</ul>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
names: ['why', 'kobe', 'james', 'curry']
}
})
</script>
</body>
</html>
v-for遍历对象
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<!--1.在遍历对象的过程中, 如果只是获取一个值, 那么获取到的是value-->
<ul>
<li v-for="item in info">{{item}}</li>
</ul>
<!--2.获取key和value 格式: (value, key) -->
<ul>
<li v-for="(value, key) in info">{{value}}-{{key}}</li>
</ul>
<!--3.获取key和value和index 格式: (value, key, index) -->
<ul>
<li v-for="(value, key, index) in info">{{value}}-{{key}}-{{index}}</li>
</ul>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
info: {
name: 'why',
age: 18,
height: 1.88
}
}
})
</script>
</body>
</html
v-for绑定和非绑定key的区别
官方推荐我们在使用v-for时,给对应的元素或组件添加上一个:key属性。这个其实和Vue的虚拟DOM的Diff算法有关系。当某一层有很多相同的节点时,也就是列表节点时,比如ABCDE,我们希望插入一个新的节点,我们希望可以在B和C之间加一个F,Diff算法默认执行起来是这样的。即把C更新成F,D更新成C,E更新成D,最后再插入E,很没有效率所以我们需要使用key来给每个节点做一个唯一标识,Diff算法就可以正确的识别此节点,找到正确的位置区插入新的节点。所以一句话,key的作用主要是为了高效的更新虚拟DOM。
<div id="app">
<ul>
<li v-for="item in letters" :key="item">{{item}}</li>
</ul>
</div>
数组的响应式方法有哪些
数组方法
可以响应式
data:{
letters: ['a', 'b', 'c', 'd']
}
- push方法,可以一次性添加多个值,类似ES6可变参数语法
this.letters.push('aaa')
this.letters.push('aaaa', 'bbbb', 'cccc')
- pop(): 删除数组中的最后一个元素
this.letters.pop();
- shift(): 删除数组中的第一个元素
this.letters.shift();
- unshift(): 在数组最前面添加元素
this.letters.unshift()
this.letters.unshift('aaa', 'bbb', 'ccc')
- splice
// 删除元素: 第二个参数传入你要删除几个元素(如果没有传,就删除后面所有的元素)
// 替换元素: 第二个参数, 表示我们要替换几个元素, 后面是用于替换前面的元素
// 插入元素: 第二个参数, 传入0, 并且后面跟上要插入的元素
- sort() 排序
this.letters.sort()
- reverse() 翻转
this.letters.reverse()
不能响应式
this.letters[0] = 'bbbbbb';//通过下标不能响应式修改
this.letters.splice(0, 1, 'bbbbbb')//可以响应式
//set(要修改的对象, 索引值, 修改后的值)
Vue.set(this.letters, 0, 'bbbbbb')//可以响应式
ES6可变参数语法
function sum(...num) {
console.log(num);
}
sum(20, 30, 40, 50, 601, 111, 122, 33)
过滤器
格式化展示数据,把展示的数据快速的展示成我们想要的内容
vue2x的时候,vue取消了内置过滤器,如果想使用过滤器就必须自定义过滤器
全局过滤器
在所有vue实例都可以使用的叫全局过滤器,必须定义在实例之前
//全局过滤器的定义方法 位置:创建实例之前
Vue.filter(‘sum’, function(val){
return val + 4;
});
<body>
<!-- 全局过滤器
写在实例之前
Vue.filter("过滤器的名字随便写的",(你要过滤的数据会自动传入)=>{
return 逻辑
})
-->
<div id="demodiv">
aaaaaaaaa
<h1>{{text|xiaoming}}</h1>
</div>
<div id="demodivb">
bbbbbbbbbbb
{{text|xiaoming}}
<h1>{{num|xiaohong}}</h1>
</div>
<script>
Vue.filter("xiaoming",(val)=>{
return "《《"+val+"》》"
})
Vue.filter("xiaohong",(val)=>{
return "¥"+val
})
new Vue({
el:"#demodiv",
data:{
text:"demodiv"
}
})
new Vue({
el:"#demodivb",
data:{
text:"demodivb",
num:19.9
}
})
</script>
</body>
局部过滤器
只能在一个指定vue实例中使用的叫局部过滤器,定义在指定vue实例中,与el同级。
只能在当前vue注册内容中使用
在vue实例中与el属性data属性同级定义
filters:{
过滤器名字:function(val){
return 输出内容
}
}
<body>
<div id="demodiv">
aaaaaaaaa
<h1>{{text|xiaoming}}</h1>
<h1>{{textb|xiaoming}}</h1>
</div>
<div id="demodivb">
bbbbbbbbbbb
{{text}}
</div>
<script>
new Vue({
el:"#demodiv",
data:{
text:"demodiv",
textb:"abcdefgbhjfhf"
},
filters:{
xiaoming:(val)=>{
return val.substr(0,4)+"..."
}
}
})
new Vue({
el:"#demodivb",
data:{
text:"demodivb"
}
})
</script>
</body>
v-model 双向绑定
- 表单控件在实际开发中是非常常见的。特别是对于用户信息的提交,需要大量的表单。
- Vue中使用v-model指令来实现表单元素和数据的双向绑定。
v-model的基本使用
<body>
<div id="app">
<input type="text" v-model="message">
{{message}}
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
}
})
</script>
</body>
v-model双向绑定的原理
v-model其实是一个语法糖,它的背后本质上是包含两个操作
- 1 v-bind绑定一个value属性
- 2 v-on指令给当前元素绑定input事件
- input事件用来动态监听用户输入东西的行为
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<!-- 1-可以这样做 -->
<input type="text" v-model="message">
<input type="text" :value="message" @input="valueChange">
<!-- 2-也可以这样写,不用写方法,写表达式 -->
<input type="text" :value="message" @input="message = $event.target.value">
<h2>{{message}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
methods: {
valueChange(event) {
this.message = event.target.value;
}
}
})
</script>
</body>
</html>
v-model结合radio使用
- v-model和radio结合,可以将radio对应的内容,动态的赋值给v-model绑定的变量
- name可以省略
- 可以先给变量一个展示的值,默认显示的就是这个内容
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<label for="male">
<input type="radio" id="male" value="男" v-model="sex">男
</label>
<label for="female">
<input type="radio" id="female" value="女" v-model="sex">女
</label>
<h2>您选择的性别是: {{sex}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
sex: '女'
}
})
</script>
</body>
</html>
v-model结合checkbox使用
单个勾选框
- v-model即为布尔值。
- 此时input的value并不影响v-model的值。
- 一个变量可以控制两个东西的状态
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<label for="agree">
<input type="checkbox" id="agree" v-model="isAgree">同意协议
</label>
<h2>您选择的是: {{isAgree}}</h2>
<button :disabled="!isAgree">下一步</button>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
isAgree: false,
}
})
</script>
</body>
</html>
多个勾选框
- 当是多个复选框时,因为可以选中多个,所以对应的data中属性是一个数组。
- 当选中某一个时,就会将input的value添加到数组中。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<input type="checkbox" value="篮球" v-model="hobbies">篮球
<input type="checkbox" value="足球" v-model="hobbies">足球
<input type="checkbox" value="乒乓球" v-model="hobbies">乒乓球
<input type="checkbox" value="羽毛球" v-model="hobbies">羽毛球
<h2>您的爱好是: {{hobbies}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
hobbies: [], // 多选框,
}
})
</script>
</body>
</html>
v-model结合select使用
单选
- 只选中一个值。
- v-model绑定的是一个值。
- 当我们选中option中的一个时,会将它对应的value赋值到mySelect中
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<!--1.选择一个-->
<select name="abc" v-model="fruit">
<option value="苹果">苹果</option>
<option value="香蕉">香蕉</option>
<option value="榴莲">榴莲</option>
<option value="葡萄">葡萄</option>
</select>
<h2>您选择的水果是: {{fruit}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
fruit: '香蕉',
}
})
</script>
</body>
</html>
多选
- 选中多个值。
- v-model绑定的是一个数组。
- 当选中多个值时,就会将选中的option对应的value添加到数组mySelects中
- 给select添加multiple可以选中多个
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<!--2.选择多个-->
<select name="abc" v-model="fruits" multiple>
<option value="苹果">苹果</option>
<option value="香蕉">香蕉</option>
<option value="榴莲">榴莲</option>
<option value="葡萄">葡萄</option>
</select>
<h2>您选择的水果是: {{fruits}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
fruits: []
}
})
</script>
</body>
</html>
v-model修饰符的使用
lazy修饰符
- 默认情况下,v-model默认是在input事件中同步输入框的数据的。
- 也就是说,一旦有数据发生改变对应的data中的数据就会自动发生改变。
- lazy修饰符可以让数据在失去焦点或者回车时才会更新
number修饰符
- 默认情况下,在输入框中无论我们输入的是字母还是数字,都会被当做字符串类型进行处理。
- 但是如果我们希望处理的是数字类型,那么最好直接将内容当做数字处理。
- number修饰符可以让在输入框中输入的内容自动转成数字类型
trim修饰符
- 如果输入的内容首尾有很多空格,通常我们希望将其去除
- trim修饰符可以过滤内容左右两边的空格
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<!--1.修饰符: lazy-->
<input type="text" v-model.lazy="message">
<h2>{{message}}</h2>
<!--2.修饰符: number-->
<input type="number" v-model.number="age">
<h2>{{age}}-{{typeof age}}</h2>
<!--3.修饰符: trim-->
<input type="text" v-model.trim="name">
<h2>您输入的名字:{{name}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
age: 0,
name: ''
}
})
var age = 0
age = '1111'
age = '222'
</script>
</body>
</html>
组件化简介
为什么有组件化
- 如果我们将一个页面中所有的处理逻辑全部放在一起,处理起来就会变得非常复杂,而且不利于后续的管理以及扩展。
- 但如果,我们讲一个页面拆分成一个个小的功能块,每个功能块完成属于自己这部分独立的功能,那么之后整个页面的管理和维护就变得非常容易了。
- 组件化是Vue.js中的重要思想
- 它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用。
- 任何的应用都会被抽象成一颗组件树。
组件化的好处
- 有了组件化的思想,我们在之后的开发中就要充分的利用它。
- 尽可能的将页面拆分成一个个小的、可复用的组件,复用性强
- 这样让我们的代码更加方便组织和管理,并且扩展性也更强。
注册组件的三个步骤
- 创建组件构造器
- 注册组件
- 使用组件。
- Vue.extend():
调用Vue.extend()创建的是一个组件构造器。 通常在创建组件构造器时,传入template代表我们自定义组件的模板。该模板就是在使用到组件的地方,要显示的HTML代码。
- Vue.component():
调用Vue.component()是将刚才的组件构造器注册为一个组件,并且给它起一个组件的标签名称。所以需要传递两个参数:1、注册组件的标签名 2、组件构造器
- 组件必须挂载在某个Vue实例下,否则它不会生效。
组件化基础开发
组件的基本使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<!--3.使用组件-->
<my-cpn></my-cpn>
</div>
<script src="../js/vue.js"></script>
<script>
// 1.创建组件构造器对象
const cpnC = Vue.extend({
template: `
<div>
<h2>我是标题</h2>
<p>我是内容, 哈哈哈哈</p>
<p>我是内容, 呵呵呵呵</p>
</div>`
})
// 2.注册组件
Vue.component('my-cpn', cpnC)
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
}
})
</script>
</body>
</html>
全局组件和局部组件
全局组件
通过调用Vue.component()注册组件时,组件的注册是全局的,这意味着该组件可以在任意Vue示例下使用。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
</div>
<script src="../js/vue.js"></script>
<script>
// 1.创建组件构造器
const cpnC = Vue.extend({
template: `
<div>
<h2>我是标题</h2>
<p>我是内容,哈哈哈哈啊</p>
</div>
`
})
// 2 注册局部组件
Vue.component('cpn', cpnC)
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
})
</script>
</body>
</html>
局部组件
如果注册的组件是挂载在某个实例中, 那么就是一个局部组件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
</div>
<script src="../js/vue.js"></script>
<script>
// 1.创建组件构造器
const cpnC = Vue.extend({
template: `
<div>
<h2>我是标题</h2>
<p>我是内容,哈哈哈哈啊</p>
</div>
`
})
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
// 2注册局部组件
components: {
// cpn使用组件时的标签名
cpn: cpnC
}
})
</script>
</body>
</html>
父组件和子组件
- 组件和组件之间存在层级关系
- 而其中一种非常重要的关系就是父子组件的关系
- 最大的组件可以理解为是通过vue实例创建出来的对象,挂载的那个元素,也可以一个看成最顶层的一个组件
- 在父组件里定义的子组件只能在该组件里面使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<cpn2></cpn2>
<!--<cpn1></cpn1>-->
</div>
<script src="../js/vue.js"></script>
<script>
// 1.创建第一个组件构造器(子组件)
const cpnC1 = Vue.extend({
template: `
<div>
<h2>我是标题1</h2>
<p>我是内容, 哈哈哈哈</p>
</div>
`
})
// 2.创建第二个组件构造器(父组件)
const cpnC2 = Vue.extend({
//在父组件模板里面使用子组件,<cpn1></cpn1>
template: `
<div>
<h2>我是标题2</h2>
<p>我是内容, 呵呵呵呵</p>
<cpn1></cpn1>
</div>
`,
//在父组件里面注册子组件
components: {
cpn1: cpnC1
}
})
// 根组件
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
cpn2: cpnC2
}
})
</script>
</body>
</html>
组件的语法糖注册方式
- 注册组件有点麻烦,Vue为了简化这个过程,提供了注册的语法糖。
- 主要是省去了调用Vue.extend()的步骤,而是可以直接使用一个对象来代替。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<cpn1></cpn1>
<cpn2></cpn2>
</div>
<script src="../js/vue.js"></script>
<script>
// 正常创建组件构造器
// const cpn1 = Vue.extend({
template: `
<div>
<h2>我是标题1</h2>
<p>我是内容, 哈哈哈哈</p>
</div>
`
}
)
//语法糖分别注册全局组件和局部组件
// 1 注册全局组件,
Vue.component('cpn1', {
template: `
<div>
<h2>我是标题1</h2>
<p>我是内容, 哈哈哈哈</p>
</div>
`
})
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
// 2.注册局部组件的语法糖
components: {
'cpn2': {
template: `
<div>
<h2>我是标题2</h2>
<p>我是内容, 呵呵呵</p>
</div>
`
}
}
})
</script>
</body>
</html>
组件模板的分离写法
如果我们能将其中的HTML分离出来写,然后挂载到对应的组件上,必然结构会变得非常清晰。
Vue提供了两种方案来定义HTML模块内容
使用script标签
搞一个id,注册组件的时候放对应的id,:类型必须是text/x-template
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
</div>
<!--script标签, 注意:类型必须是text/x-template-->
<script type="text/x-template" id="cpn">
<div>
<h2>我是标题</h2>
<p>我是内容,哈哈哈</p>
</div>
</script>
<script src="../js/vue.js"></script>
<script>
// 1.注册一个全局组件
Vue.component('cpn', {
template: '#cpn'
})
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
}
})
</script>
</body>
</html>
使用template标签
搞一个id,注册组件的时候放对应的id
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
</div>
<!--template标签-->
<template id="cpn">
<div>
<h2>我是标题</h2>
<p>我是内容,呵呵呵</p>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
// 1.注册一个全局组件
Vue.component('cpn', {
template: '#cpn'
})
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
}
})
</script>
</body>
</html>
组件中数据存放的问题
- 组件是一个单独功能模块的封装
- 这个模块有属于自己的HTML模板,也应该有属性自己的数据data
- Vue组件应该有自己保存数据的地方
- 组件对象也有一个data属性,也可以有methods等属性
- 只是这个data属性必须是一个函数
- 而且这个函数返回一个对象,对象内部保存着数据
组件中的data为什么是函数?
-
首先,如果不是一个函数,Vue直接就会报错。
-
其次,原因是在于Vue让每个组件对象都返回一个新的对象,因为如果是同一个对象的,组件在多次使用后会相互影响。
-
当一个组件被定义,
data
必须声明为返回一个初始数据对象的函数,因为组件可能被用来创建多个实例。 -
如果
data
是一个纯粹的对象,则所有的实例将共享引用同一个数据对象! -
通过提供
data
函数,每次创建一个新实例后,我们能够调用data
函数,从而返回初始数据的一个全新副本数据对象。 -
函数在每次调用的时候会重新创建,所以data是函数,以后每次用组件的时候,数据都是单独绑定的,不会相互影响。
-
如果像下面这样写,直接定义一个对象放在return后面,那么每次调用组件,用的都是同一个数据,改一个就会全部改动
const obj = { counter: 0 }
// 注册组件
Vue.component(‘cpn’, {
template: ‘#cpn’,
// data() {
// return {
// counter: 0
// }
// },
data() {
return obj
},
methods: {
increment() {
this.counter++
},
decrement() {
this.counter–
}
}
})
const app = new Vue({
el: ‘#app’,
data: {
message: ‘你好啊’
}
})
# 父子组件的通信
子组件是不能引用父组件或者Vue实例的数据的。但是,在开发中,往往一些数据确实需要从上层传递到下层:比如在一个页面中,我们从服务器请求到了很多的数据。其中一部分数据,并非是我们整个页面的大组件来展示的,而是需要下面的子组件进行展示。这个时候,并不会让子组件再次发送一个网络请求,而是直接让大组件(父组件)将数据传递给小组件(子组件)。如何进行父子组件间的通信呢?Vue官方提到
- 通过props向子组件传递数据(properties-属性的意思)
- 通过事件向父组件发送消息
## 父组件向子组件传递数据props
### props第一种用法:props直接用
1. 字符串数组,数组中的字符串就是传递时的名称
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<cpn v-bind:cmovies="movies"></cpn>
</div>
<!--子组件-->
<template id="cpn">
<div>
<ul>
<li v-for="item in cmovies">{{item}}</li>
</ul>
<h2>{{cmessage}}</h2>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
// 父传子: 在子组件中用props定义接受
const cpn = {
template: '#cpn',
props: ['cmovies', 'cmessage'],
},
data() {
return {}
},
}
//父组件
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
movies: ['海王', '海贼王', '海尔兄弟']
},
components: {
cpn
}
})
</script>
</body>
</html>
props第二种用法:props数据验证
- 对象,对象可以设置传递时的类型,也可以设置默认值。
- 当需要对props进行类型等验证时,就需要对象写法了。
- 通过type来设置数据类型
- 可以通过default: ‘aaaaaaaa’,来设置默认值,当父组件没有传值的时候显示默认值。
- 加上required为必须传递的值,不传就会报错。
- 设置默认值的时候,当type类型是对象或者数组时, 默认值default后面必须是一个函数(如果不是函数,vue2.5之前没事,之后会报错)
- 当我们有自定义构造函数时,验证也支持自定义的类型
- 验证都支持哪些数据类型呢?
- String
- Number
- Boolean
- Array
- Object
- Date
- Function
- Symbol
<body>
<div id="app">
<cpn :cmessage="message" :cmovies="movies"></cpn>
</div>
<template id="cpn">
<div>
<ul>
<li v-for="item in cmovies">{{item}}</li>
</ul>
<h2>{{cmessage}}</h2>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
// 父传子: props
const cpn = {
template: '#cpn',
// props: ['cmovies', 'cmessage'],
props: {
// 1.类型限制
// cmovies: Array,
// cmessage: String,
// 2.提供一些默认值, 以及必传值
cmessage: {
type: String,
default: 'aaaaaaaa',
required: true
},
// 类型是对象或者数组时, 默认值必须是一个函数
cmovies: {
type: Array,
default() {
return []
}
}
},
data() {
return {}
},
methods: {
}
}
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
movies: ['海王', '海贼王', '海尔兄弟']
},
components: {
cpn
}
})
</script>
</body>
父传子(props中的驼峰标识)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<cpn :c-info="info" :child-my-message="message" v-bind:class></cpn>
</div>
<template id="cpn">
<div>
<h2>{{cInfo}}</h2>
<h2>{{childMyMessage}}</h2>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const cpn = {
template: '#cpn',
props: {
cInfo: {
type: Object,
default() {
return {}
}
},
childMyMessage: {
type: String,
default: ''
}
}
}
const app = new Vue({
el: '#app',
data: {
info: {
name: 'why',
age: 18,
height: 1.88
},
message: 'aaaaaa'
},
components: {
cpn
}
})
</script>
</body>
</html>
子组件向父组件传递数据,自定义事件
- 什么时候需要自定义事件呢
-
当子组件需要向父组件传递数据时,就要用到自定义事件了。
-
v-on不仅仅可以用于监听DOM事件,也可以用于组件间的自定义事件。
2.自定义事件的流程
-
在子组件中,通过$emit()来触发事件。
-
在父组件中,通过v-on来监听子组件事件。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--父组件模板-->
<div id="app">
<!--以前的事件不传参数,默认把事件对象传递进去,现在不传参数默认把子组件发送过来的数据传递进去,需要在定义的时候在参数里接受-->
<cpn @item-click="cpnClick"></cpn>
</div>
<!--子组件模板-->
<template id="cpn">
<div>
<button v-for="item in categories"
@click="btnClick(item)">
{{item.name}}
</button>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
// 1.子组件
const cpn = {
template: '#cpn',
data() {
return {
categories: [
{id: 'aaa', name: '热门推荐'},
{id: 'bbb', name: '手机数码'},
{id: 'ccc', name: '家用家电'},
{id: 'ddd', name: '电脑办公'},
]
}
},
methods: {
btnClick(item) {
// 发射事件: 自定义事件
this.$emit('item-click', item)
}
}
}
// 2.父组件
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
cpn
},
methods: {
cpnClick(item) {
console.log('cpnClick', item);
}
}
})
</script>
</body>
</html>
父子组件直接访问方式
有时候我们需要父组件直接访问子组件,子组件直接访问父组件,或者是子组件访问跟组件。
父组件访问子组件:使用$children或$refs reference(引用)
子组件访问父组件:使用$parent
父访问子-$children
数组类型,取值需要用下标取,不太方便
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<cpn></cpn>
<cpn></cpn>
<my-cpn></my-cpn>
<y-cpn></y-cpn>
<cpn></cpn>
<button @click="btnClick">按钮</button>
</div>
<template id="cpn">
<div>我是子组件</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
methods: {
btnClick() {
// $children 数组类型
console.log(this.$children);
for (let c of this.$children) {
console.log(c.name);
c.showMessage();
}
console.log(this.$children[3].name);
}
},
components: {
cpn: {
template: '#cpn',
data() {
return {
name: '我是子组件的name'
}
},
methods: {
showMessage() {
console.log('showMessage');
}
}
},
}
})
</script>
</body>
</html>
父访问子-$refs
是一个对象类型,必须在组件上加上ref属性,随便等于一个值,比如aaa,通过this.$refs.aaa,就可以拿到这个对象,再通过对象打点数据的名字,就可以拿到子组件的数据。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<cpn></cpn>
<cpn></cpn>
<my-cpn></my-cpn>
<y-cpn></y-cpn>
<cpn ref="aaa"></cpn>
<button @click="btnClick">按钮</button>
</div>
<template id="cpn">
<div>我是子组件</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
methods: {
btnClick() {
// $refs => 对象类型, 默认是一个空的对象 ref='bbb'
console.log(this.$refs.aaa.name);
}
},
components: {
cpn: {
template: '#cpn',
data() {
return {
name: '我是子组件的name'
}
},
methods: {
showMessage() {
console.log('showMessage');
}
}
},
}
})
</script>
</body>
</html>
子访问父-$parent
如果我们想在子组件中直接访问父组件,可以通过$parent
注意事项:
尽管在Vue开发中,我们允许通过$parent来访问父组件,但是在真实开发中尽量不要这样做。
子组件应该尽量避免直接访问父组件的数据,因为这样耦合度太高了。
如果我们将子组件放在另外一个组件之内,很可能该父组件没有对应的属性,往往会引起问题。
另外,更不好做的是通过$parent直接修改父组件的状态,那么父组件中的状态将变得飘忽不定,很不利于我的调试和维护
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<cpn></cpn>
</div>
<template id="cpn">
<div>
<h2>我是cpn组件</h2>
<ccpn></ccpn>
</div>
</template>
<template id="ccpn">
<div>
<h2>我是子组件</h2>
<button @click="btnClick">按钮</button>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
cpn: {
template: '#cpn',
data() {
return {
name: '我是cpn组件的name'
}
},
components: {
ccpn: {
template: '#ccpn',
methods: {
btnClick() {
//访问父组件$parent
console.log(this.$parent);
console.log(this.$parent.name);
}
}
}
}
}
}
})
</script>
</body>
</html>
访问根组件-$root
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<cpn></cpn>
</div>
<template id="cpn">
<div>
<h2>我是cpn组件</h2>
<ccpn></ccpn>
</div>
</template>
<template id="ccpn">
<div>
<h2>我是子组件</h2>
<button @click="btnClick">按钮</button>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
cpn: {
template: '#cpn',
data() {
return {
name: '我是cpn组件的name'
}
},
components: {
ccpn: {
template: '#ccpn',
methods: {
btnClick() {
// 访问根组件$root
console.log(this.$root);
console.log(this.$root.message);
}
}
}
}
}
}
})
</script>
</body>
</html>
slot插槽
- 组件的插槽:
- 组件的插槽也是为了让我们封装的组件更加具有扩展性。
- 让使用者可以决定组件内部的一些内容到底展示什么。
slot-插槽的基本使用
1.插槽的基本使用 <slot></slot>
2.插槽的默认值 <slot>button</slot>
3.如果有多个值, 同时放入到组件进行替换时, 一起作为替换元素
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<cpn></cpn>
<cpn><span>哈哈哈</span></cpn>
<cpn><i>呵呵呵</i></cpn>
<cpn>
<i>呵呵呵</i>
<div>我是div元素</div>
<p>我是p元素</p>
</cpn>
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
</div>
<template id="cpn">
<div>
<h2>我是组件</h2>
<p>我是组件, 哈哈哈</p>
<slot><button>按钮</button></slot>
<!--<button>按钮</button>-->
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
cpn: {
template: '#cpn'
}
}
})
</script>
</body>
</html>
slot-具名插槽的使用
<slot name="left"><span>左边</span></slot>
<cpn><button slot="left">返回</button></cpn>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<cpn><span slot="center">标题</span></cpn>
<cpn><button slot="left">返回</button></cpn>
</div>
<template id="cpn">
<div>
<slot name="left"><span>左边</span></slot>
<slot name="center"><span>中间</span></slot>
<slot name="right"><span>右边</span></slot>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
cpn: {
template: '#cpn'
}
}
})
</script>
</body>
</html>
什么是编译的作用域
父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译。
isShow这个变量在父子组件都有,不过父组件只会去父组件的作用域去找,子组件只会去子组件的作用域去找
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<cpn v-show="isShow"></cpn>
<cpn v-for="item in names"></cpn>
</div>
<template id="cpn">
<div>
<h2>我是子组件</h2>
<p>我是内容, 哈哈哈</p>
<button v-show="isShow">按钮</button>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
isShow: true
},
components: {
cpn: {
template: '#cpn',
data() {
return {
isShow: false
}
}
},
}
})
</script>
</body>
</html>
作用域插槽
父组件替换插槽的标签,但是内容由子组件来提供。
在子组件绑定属性
<slot :data="pLanguages">
在父组件使用我们的子组件时,从子组件中拿到数据:
1-先用template标签包裹住,2.5版本后可以使用任意标签
2-我们通过<template slot-scope="slotProps">获取到slotProps属性
3-在通过slotProps.data就可以获取到刚才我们传入的data了
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<cpn></cpn>
<cpn>
<!--2-目的是获取子组件中的pLanguages-->
<template slot-scope="slot">
<!--<span v-for="item in slot.data"> - {{item}}</span>-->
<span>{{slot.data.join(' - ')}}</span>
</template>
</cpn>
<cpn>
<!--2-目的是获取子组件中的pLanguages-->
<template slot-scope="slot">
<!--<span v-for="item in slot.data">{{item}} * </span>-->
<span>{{slot.data.join(' * ')}}</span>
</template>
</cpn>
<!--<cpn></cpn>-->
</div>
<template id="cpn">
<div>
<!--1-把子组件的pLanguages绑定到任意属性上,属性名可以随便起-->
<slot :data="pLanguages">
<ul>
<li v-for="item in pLanguages">{{item}}</li>
</ul>
</slot>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
cpn: {
template: '#cpn',
data() {
return {
pLanguages: ['JavaScript', 'C++', 'Java', 'C#', 'Python', 'Go', 'Swift']
}
}
}
}
})
</script>
</body>
</html>
ES6的模块化实现
通过模块化的方式开发,type属性等于这个
<script src="aaa.js" type="module"></script>
导出/导入方式-基本使用
导入接受的名字和导出的要一样
//文件名是aaa.js
var name = '小明'
var age = 18
var flag = true
function sum(num1, num2) {
return num1 + num2
}
if (flag) {
console.log(sum(20, 30));
}
// 1.导出方式一:
export {
flag, sum
}
// 1.导入的{}中定义的变量
//导入接受的名字和导出的要一样
import {flag, sum} from "./aaa.js";
导出/导入方式-直接导出变量
导入接受的名字和导出的要一样
// 2.导出方式二:
export var num1 = 1000;
export var height = 1.88
// 2.直接导入export定义的变量
import {num1, height} from "./aaa.js";
console.log(num1);
console.log(height);
导出/导入方式-导出函数/类
导入接受的名字和导出的要一样
// 3.导出函数/类
export function mul(num1, num2) {
return num1 * num2
}
export class Person {
run() {
console.log('在奔跑');
}
}
// 3.导入 export的function/class
import {mul, Person} from "./aaa.js";
console.log(mul(30, 50));
const p = new Person();
p.run()
导出/导入方式-export default
- 一个模块中包含某个的功能,我们并不希望给这个功能命名,而且让导入者可以自己来命名 ,这个时候就可以使用export default
- pexport default在同一个模块中,不允许同时存在多个。
export default function (argument) {
console.log(argument);
}
// 4.导入 export default中的内容
import addr from "./aaa.js";
addr('你好啊');
导出/导入方式-全部导入*
// 5.统一全部导入
// import {flag, num, num1, height, Person, mul, sum} from "./aaa.js";
import * as aaa from './aaa.js'
console.log(aaa.flag);
console.log(aaa.height);
Webpack
什么是webpack?
这个webpack还真不是一两句话可以说清楚的。
我们先看看官方的解释:
At its core, webpack is a static module bundler for modern JavaScript applications.
从本质上来讲,webpack是一个现代的JavaScript应用的静态模块打包工具。
但是它是什么呢?用概念解释概念,还是不清晰。
我们从两个点来解释上面这句话:模块 和 打包
前端模块化的一些方案:AMD、CMD、CommonJS、ES6。
在ES6之前,我们要想进行模块化开发,就必须借助于其他的工具,让我们可以进行模块化开发。
并且在通过模块化开发完成了项目后,还需要处理模块间的各种依赖,并且将其进行整合打包。
而webpack其中一个核心就是让我们可能进行模块化开发,并且会帮助我们处理模块间的依赖关系。
而且不仅仅是JavaScript文件,我们的CSS、图片、json文件等等在webpack中都可以被当做模块来使用(在后续我们会看到)。
这就是webpack中模块化的概念。
打包如何理解呢?
理解了webpack可以帮助我们进行模块化,并且处理模块间的各种复杂关系后,打包的概念就非常好理解了。
就是将webpack中的各种资源模块进行打包合并成一个或多个包(Bundle)。
并且在打包的过程中,还可以对资源进行处理,比如压缩图片,将scss转成css,将ES6语法转成ES5语法,将TypeScript转成JavaScript等等操作。
但是打包的操作似乎grunt/gulp也可以帮助我们完成,它们有什么不同呢?
grunt/gulp的核心是Task
我们可以配置一系列的task,并且定义task要处理的事务(例如ES6、ts转化,图片压缩,scss转成css)
之后让grunt/gulp来依次执行这些task,而且让整个流程自动化。
所以grunt/gulp也被称为前端自动化任务管理工具。
什么时候用grunt/gulp呢?
如果你的工程模块依赖非常简单,甚至是没有用到模块化的概念。
只需要进行简单的合并、压缩,就使用grunt/gulp即可。
但是如果整个项目使用了模块化管理,而且相互依赖非常强,我们就可以使用更加强大的webpack了。
所以,grunt/gulp和webpack有什么不同呢?
grunt/gulp更加强调的是前端流程的自动化,模块化不是它的核心。
webpack更加强调模块化开发管理,而文件压缩合并、预处理等功能,是他附带的功能。
安装webpack首先需要安装Node.js,Node.js自带了软件包管理工具npm
查看自己的node版本:
node -v
全局安装webpack(这里我先指定版本号3.6.0,因为vue cli2依赖该版本)
npm install webpack@3.6.0 -g
局部安装webpack(后续才需要)
--save-dev`是开发时依赖,项目打包后不需要继续使用的。
cd 对应目录
npm install webpack@3.6.0 -g --save-dev
为什么全局安装后,还需要局部安装呢?
在终端直接执行webpack命令,使用的全局安装的webpack
当在package.json中定义了scripts时,其中包含了webpack命令,那么使用的是局部webpack
Vue-router
路由模式-URL的hash
- URL的hash也就是锚点(#), 本质上是改变window.location的href属性.
- 我们可以通过直接赋值location.hash来改变href, 但是页面不发生刷新
路由模式-HTML5的history模式
history.pushState()
//是将每个添加的地址压入的栈里面,现进后出,前进后退网页类似于进出栈的操作
history.pushState({},"","/home")
history.pushState({},"","/abt")
history.pushState({},"","/demmt")
history.replaceState ()
//直接替换地址,不能返回
history.replaceState ({},"","/home")
history.go()
history.go(-1) //后退一个
history.go(0) //原地刷新
history.go(1) //向前走一个
history.back()
history.back() 等价于 history.go(-1)
history.forward()
history.forward() 则等价于 history.go(1)
Vue-router安装配置
在router中创建index.js,在里面配置
// 配置路由相关的信息
import VueRouter from 'vue-router'
import Vue from 'vue'
import Home from '../components/Home'
import About from '../components/About'
// 1.通过Vue.use(插件), 安装插件
Vue.use(VueRouter)
// 2.创建VueRouter对象
const routes = []
const router = new VueRouter({
// 配置路由和组件之间的应用关系
routes,
mode: 'history',
linkActiveClass: 'active'
})
// 3.将router对象传入到Vue实例
export default router
在main.js中
import Vue from 'vue'
import App from './App'
//会自动找到index.js的文件,可以不用写后面的目录
import router from './router'
Vue.config.productionTip = false
new Vue({
el: '#app',
router,
render: h => h(App)
})
Vue-router路由映射配置-加重定向
// 配置路由相关的信息
import VueRouter from 'vue-router'
import Vue from 'vue'
import Home from '../components/Home'
import About from '../components/About'
// 1.通过Vue.use(插件), 安装插件
Vue.use(VueRouter)
// 2.创建VueRouter对象
const routes = [
{
path: '',
// redirect重定向
redirect: '/home'
},
{
path: '/home',
component: Home
},
{
path: '/about',
component: About
}
]
const router = new VueRouter({
// 配置路由和组件之间的应用关系
routes,
mode: 'history',
linkActiveClass: 'active'
})
// 3.将router对象传入到Vue实例
export default router
展示配置
<template>
<div id="app">
<router-link to="/home" >首页</router-link>
<router-link to="/about">关于</router-link>
<router-view></router-view>
</div>
</template>
<router-link>: 该标签是一个vue-router中已经内置的组件, 它会被渲染成一个<a>标签.
<router-view>: 该标签会根据当前的路径, 动态渲染出不同的组件.
网页的其他内容, 比如顶部的标题/导航, 或者底部的一些版权信息等会和<router-view>处于同一个等级.
在路由切换时, 切换的是<router-view>挂载的组件, 其他内容不会发生改变.
修改路由模式
修改mode属性值就好
const router = new VueRouter({
// 配置路由和组件之间的应用关系
routes,
mode: 'history',
linkActiveClass: 'active'
})
router-link其他属性
<router-link>还有一些其他属性:
tag: tag可以指定<router-link>之后渲染成什么组件, 比如上面的代码会被渲染成一个<li>元素, 而不是<a>
replace: replace不会留下history记录, 所以指定replace的情况下, 后退键返回不能返回到上一个页面中
active-class: 当<router-link>对应的路由匹配成功时, 会自动给当前元素设置一个router-link-active的class, 设置active-class可以修改默认的名称.
在进行高亮显示的导航菜单或者底部tabbar时, 会使用到该类.
但是通常不会修改类的属性, 会直接使用默认的router-link-active即可.
<router-link to="/home" tag="button" replace active-class="active">首页</router-link>
修改路由活跃的类名,在index.js中修改
const router = new VueRouter({
// 配置路由和组件之间的应用关系
routes,
mode: 'history',
linkActiveClass: 'active'//修改类名
})
通过代码跳转路由
<template>
<div id="app">
<button @click="homeClick">首页</button>
<button @click="aboutClick">关于</button>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'App',
methods: {
homeClick() {
// 通过代码的方式修改路由 vue-router
// push => pushState
// this.$router.push('/home')
this.$router.replace('/home')
console.log('homeClick');
},
aboutClick() {
// this.$router.push('/about')
this.$router.replace('/about')
console.log('aboutClick');
}
}
}
</script>
Vue-router动态路由的使用
在某些情况下,一个页面的path路径可能是不确定的,比如我们进入用户界面时,希望是如下的路径:
/user/aaaa或/user/bbbb
除了有前面的/user之外,后面还跟上了用户的ID
这种path和Component的匹配关系,我们称之为动态路由(也是路由传递数据的一种方式)。
{
path: '/user/:id',
component: User,
},
<router-link :to="'/user/'+userId">用户</router-link>
<script>
data() {
return {
userId: 'zhangsan',
}
}
</script>
userId() {
return this.$route.params.id
}
路由懒加载
结合Vue的异步组件和Webpack的代码分析
const Home = resolve => { require.ensure(['../components/Home.vue'], () => { resolve(require('../components/Home.vue')) })};
AMD写法
const About = resolve => require(['../components/About.vue'], resolve);
ES6
const Home = () => import('../components/Home.vue')
路由的嵌套
{
path: '/home',
component: Home,
meta: {
title: '首页'
},
children: [
// {
// path: '',
// redirect: 'news'
// },
{
path: 'news',
component: HomeNews
},
{
path: 'message',
component: HomeMessage
}
]
},
<router-link to="/home/news">新闻</router-link>
<router-link to="/home/message">消息</router-link>
<router-view></router-view>
参数传递
传递参数主要有两种类型: params和query
params的类型:
配置路由格式: /router/:id
传递的方式: 在path后面跟上对应的值
传递后形成的路径: /router/123, /router/abc
query的类型:
配置路由格式: /router, 也就是普通配置
传递的方式: 对象中使用query的key作为传递方式
传递后形成的路径: /router?id=123, /router?id=abc
导航守卫
// 配置路由相关的信息
import VueRouter from 'vue-router'
import Vue from 'vue'
// import Home from '../components/Home'
// import About from '../components/About'
// import User from '../components/User'
const Home = () => import('../components/Home')
const HomeNews = () => import('../components/HomeNews')
const HomeMessage = () => import('../components/HomeMessage')
const About = () => import('../components/About')
const User = () => import('../components/User')
const Profile = () => import('../components/Profile')
// 1.通过Vue.use(插件), 安装插件
Vue.use(VueRouter)
// 2.创建VueRouter对象
const routes = [
{
path: '',
// redirect重定向
redirect: '/home'
},
{
path: '/home',
component: Home,
meta: {
title: '首页'
},
children: [
// {
// path: '',
// redirect: 'news'
// },
{
path: 'news',
component: HomeNews
},
{
path: 'message',
component: HomeMessage
}
]
},
{
path: '/about',
component: About,
meta: {
title: '关于'
},
beforeEnter: (to, from, next) => {
// console.log('about beforeEnter');
next()
}
},
{
path: '/user/:id',
component: User,
meta: {
title: '用户'
},
},
{
path: '/profile',
component: Profile,
meta: {
title: '档案'
},
}
]
const router = new VueRouter({
// 配置路由和组件之间的应用关系
routes,
mode: 'history',
linkActiveClass: 'active'
})
// 前置守卫(guard)
router.beforeEach((to, from, next) => {
// 从from跳转到to
document.title = to.matched[0].meta.title
// console.log(to);
// console.log('++++');
next()
})
// 后置钩子(hook)
router.afterEach((to, from) => {
// console.log('----');
})
// 3.将router对象传入到Vue实例
export default router
keep-alive
keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。
它们有两个非常重要的属性:
include - 字符串或正则表达,只有匹配的组件会被缓存
exclude - 字符串或正则表达式,任何匹配的组件都不会被缓存
router-view 也是一个组件,如果直接被包在 keep-alive 里面,所有路径匹配到的视图组件都会被缓存:
通过create声明周期函数来验证
<keep-alive exclude="Profile,User">
<router-view/>
</keep-alive>