vue-基础语法和组件化开发
vue基础语法
一. 计算属性
1.1. 计算属性的本质
fullname: {set(), get()}
每个计算属性都包含一个getter和一个setter
在上面的例子中,我们只是使用getter来读取。
在某些情况下,你也可以提供一个setter方法(不常用)。
在需要写setter的时候,代码如下:
1.2. 计算属性和methods对比
计算属性在多次使用时, 只会调用一次.
它是由缓存的
我们可能会考虑这样的一个问题:
methods和computed看起来都可以实现我们的功能,
那么为什么还要多一个计算属性这个东西呢?
原因:计算属性会进行缓存,如果多次使用时,计算属性只会调用一次。
我们来看下面的代码:
1.3. ES6 语法
let/var (定义变量)
块级作用域
变量作用域 :
ES5中的var是没有作用域的
ES中的let是有作用域的(if / for)
变量在什么范围内是可用的
var没有作用域限制(会引起一些问题 )
没有块级作用域所引起的问题
没有块级作用域 数据得不到保证,在哪都能修改
const (定义长量)
对象字面量增强写法
二. 事件监听
2.1. 事件监听基本使用
在前端开发中,我们需要经常和用于交互。
这个时候,我们就必须监听用户发生的时间,比如点击、拖拽、键盘事件等等
在Vue中如何监听事件呢?使用v-on指令
v-on介绍
作用:绑定事件监听器
缩写:@
预期:Function | Inline Statement | Object
参数:event
这里,我们用一个监听按钮的点击事件,来简单看看v-on的使用
下面的代码中,我们使用了v-on:click="counter++”
另外,我们可以将事件指向一个在methods中定义的函数
2.2. 参数问题
btnClick
btnClick(event) :!--2.在事件定义时, 写方法时省略了小括号, 但是方法本身是需要一个参数的, 这个时候, Vue会默认将浏览器生产的event事件对象作为参数传入到方法-->
btnClick(abc, event) -> $event : !--3.方法定义时, 我们需要event对象, 同时又需要其他参数-->
!-- 在调用方式, 如何手动的获取到浏览器参数的event对象: $event-->
当通过methods中定义方法,以供@click调用时,需要注意参数问题:
情况一:如果该方法不需要额外参数,那么方法后的()可以不添加。
但是注意:如果方法本身中有一个参数,那么会默认将原生事件event参数传递进去
情况二:如果需要同时传入某个参数,同时需要event时,可以通过$event传入事件
Event 本身包含适用于所有事件的属性和方法。
构造器:可以使用event()创建一个event对象
Event 对象代表事件的状态,比如事件在其中发生的元素、键盘按键的状态、鼠标的位置、鼠标按钮的状态。事件通常与函数结合使用,函数不会在事件发生前被执行!
2.3. 修饰符
stop
prevent
.enter
.once
.native
在某些情况下,我们拿到event的目的可能是进行一些事件处理。
Vue提供了修饰符来帮助我们方便的处理一些事件:
.stop - 调用 event.stopPropagation()。
.prevent - 调用 event.preventDefault()。
.{keyCode | keyAlias} - 只当事件是从特定键触发时才触发回调。
.native - 监听组件根元素的原生事件。
.once - 只触发一次回调。
三. 条件判断
3.1. v-if/v-else-if/v-else
v-if、v-else-if、v-else
这三个指令与JavaScript的条件语句if、else、else if类似。
Vue的条件指令可以根据表达式的值在DOM中渲染或销毁元素或组件
简单的案例演示:
v-if的原理:
v-if后面的条件为false时,对应的元素以及其子元素不会渲染。
也就是根本没有不会有对应的标签出现在DOM中。
3.2. 登录小案例
用户再登录时,可以切换使用用户账号登录还是邮箱地址登录。
类似如下情景:
小问题:
如果我们在有输入内容的情况下,切换了类型,我们会发现文字依然显示之前的输入的内容。
但是按道理讲,我们应该切换到另外一个input元素中了。
在另一个input元素中,我们并没有输入内容。
为什么会出现这个问题呢?
问题解答:
这是因为Vue在进行DOM渲染时,出于性能考虑,会尽可能的复用已经存在的元素,而不是重新创建新的元素。
在上面的案例中,Vue内部会发现原来的input元素不再使用,直接作为else中的input来使用了。
解决方案:
如果我们不希望Vue出现类似重复利用的问题,可以给对应的input添加key
并且我们需要保证key的不同
3.3. v-show
v-show和v-if区别
v-show的用法和v-if非常相似,也用于决定一个元素是否渲染:
v-if和v-show对比
v-if和v-show都可以决定一个元素是否渲染,那么开发中我们如何选择呢?
v-if当条件为false时,压根不会有对应的元素在DOM中。
v-show当条件为false时,仅仅是将元素的display属性设置为none而已。
开发中如何选择呢?
当需要在显示与隐藏之间切片很频繁时,使用v-show
当只有一次切换时,通过使用v-if
四. 循环遍历
4.1. 遍历数组
当我们有一组数据需要进行渲染时,我们就可以使用v-for来完成。
v-for的语法类似于JavaScript中的for循环。
格式如下:item in items的形式。
我们来看一个简单的案例:
如果在遍历的过程中不需要使用索引值
v-for="movie in movies"
依次从movies中取出movie,并且在元素的内容中,我们可以使用Mustache语法,来使用movie
如果在遍历的过程中,我们需要拿到元素在数组中的索引值呢?
语法格式:v-for=(item, index) in items
其中的index就代表了取出的item在原数组的索引值。
totalPrice() {
// 1.普通的for循环
// let totalPrice = 0
// for (let i = 0; i < this.books.length; i++) {
// totalPrice += this.books[i].price * this.books[i].count
// }
// return totalPrice
// 2.for (let i in this.books)
// let totalPrice = 0
// for (let i in this.books) {
// const book = this.books[i]
// totalPrice += book.price * book.count
// }
//
// return totalPrice
// 3.for (let i of this.books)
// let totalPrice = 0
// for (let item of this.books) {
// totalPrice += item.price * item.count
// }
// return totalPrice
return this.books.reduce(function (preValue, book) {
return preValue + book.price * book.count
}, 0)
}
4.2. 遍历对象
value
value, key
value, key, index
v-for可以用户遍历对象:
比如某个对象中存储着你的个人信息,我们希望以列表的形式显示出来。
4.3. 数组哪些方法是响应式的
官方推荐我们在使用v-for时,给对应的元素或组件添加上一个:key属性。
为什么需要这个key属性呢(了解)?
这个其实和Vue的虚拟DOM的Diff算法有关系。
这里我们借用React’s diff algorithm中的一张图来简单说明一下:
当某一层有很多相同的节点时,也就是列表节点时,我们希望插入一个新的节点
我们希望可以在B和C之间加一个F,Diff算法默认执行起来是这样的。
即把C更新成F,D更新成C,E更新成D,最后再插入E,是不是很没有效率?
所以我们需要使用key来给每个节点做一个唯一标识
Diff算法就可以正确的识别此节点
找到正确的位置区插入新的节点。
所以一句话,key的作用主要是为了高效的更新虚拟DOM。
因为Vue是响应式的,所以当数据发生变化时,Vue会自动检测数据变化,视图会发生对应的更新。
Vue中包含了一组观察数组编译的方法,使用它们改变数组也会触发视图的更新。
push() 往最后添加元素 (可以添加多个元素)
pop() 删除数据中的最后一个元素
shift() 删除我们数组中的第一个元素
unshift() 在数组最前面添加元素 (可以添加多个元素)
splice() 作用<删除元素 / 插入元素/ 替换元素>
删除元素 splice(1,2), 第二个参数传入你要删除几个元素(如果没有传删除,就删除后面所有的元素)
插入元素splice(1,3,'m','n','u','x') 第二个参数表示我们要替换几个元素,后面是用于替换前面的元素
替换元素splice(1,0,'x','y','z') 第二个参数传入0 ,并且后面跟上要插入的元素
sort() 排序
reverse() 反转
注意: 通过索引值修改数组中的元素(不是响应式)
// this.letters[0] = 'bbbbbb';
解决办法this.letters.splice(0, 1, 'bbbbbb')
// set(要修改的对象, 索引值, 修改后的值)
// Vue.set(this.letters, 0, 'bbbbbb')
4.3. 数组哪些方法是响应式的小案例 点击 那个那个添加样式
-
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> .active { color: red; } </style> </head> <body> <div id="app"> <ul> <li v-for="(item, index) in movies" :class="{active: currentIndex === index}" @click="liClick(index)"> {{index}}.{{item}} </li> </ul> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { movies: ['海王', '海贼王', '加勒比海盗', '海尔兄弟'], currentIndex: 0 }, methods: { liClick(index) { this.currentIndex = index}}}) </script> </body> </html>
五. v-model的使用
默认情况下再给变量赋值都是字符串类型
5.1. v-model的基本使用
5.2 表单绑定v-model
v-model => v-bind:value v-on:input
表单控件在实际开发中是非常常见的。特别是对于用户信息的提交,需要大量的表单。
Vue中使用v-model指令来实现表单元素和数据的双向绑定。
案例的解析:
当我们在输入框输入内容时
因为input中的v-model绑定了message,所以会实时将输入的内容传递给message,message发生改变。
当message发生改变时,因为上面我们使用Mustache语法,将message的值插入到DOM中,所以DOM会发生响应的改变。
所以,通过v-model实现了双向的绑定。
当然,我们也可以将v-model用于textarea元素
5.3v-model原理
v-model其实是一个语法糖,它的背后本质上是包含两个操作:
1.v-bind绑定一个value属性
2.v-on指令给当前元素绑定input事件
也就是说下面的代码:等同于下面的代码:
<input type="text" v-model="message">
等同于
<input type="text" v-bind:value="message" v-on:input="message = $event.target.value">
5.4. v-model和radio/checkbox/select
当存在多个单选框时
v-model:checkbox
单个勾选框:
v-model即为布尔值。
此时input的value并不影响v-model的值
多个复选框:
当是多个复选框时,因为可以选中多个,所以对应的data中属性是一个数组。
当选中某一个时,就会将input的value添加到数组中。
v-model:select
单选:只能选中一个值。
v-model绑定的是一个值。
当我们选中option中的一个时,会将它对应的value赋值到mySelect中
多选:可以选中多个值。
v-model绑定的是一个数组。
当选中多个值时,就会将选中的option对应的value添加到数组mySelects中
5.5. 修饰符
lazy:
默认情况下,v-model默认是在input事件中同步输入框的数据的。
也就是说,一旦有数据发生改变对应的data中的数据就会自动发生改变。
lazy修饰符可以让数据在失去焦点或者回车时才会更新:
number:
默认情况下,在输入框中无论我们输入的是字母还是数字,都会被当做字符串类型进行处理。
但是如果我们希望处理的是数字类型,那么最好直接将内容当做数字处理。
number修饰符可以让在输入框中输入的内容自动转成数字类型:
trim:
如果输入的内容首尾有很多空格,通常我们希望将其去除
trim修饰符可以过滤内容左右两边的空格
5.6值绑定
初看Vue官方值绑定的时候,我很疑惑:what the hell is that?
但是仔细阅读之后,发现很简单,就是动态的给value赋值而已:
我们前面的value中的值,可以回头去看一下,都是在定义input的时候直接给定的。
但是真实开发中,这些input的值可能是从网络获取或定义在data中的。
所以我们可以通过v-bind:value动态的给value绑定值。
这不就是v-bind吗?
这不就是v-bind在input中的应用吗?搞的我看了很久,搞不清他想讲什么。
这里不再给出对应的代码,因为会用v-bind,就会值绑定的应用了。
六. 组件化开发
6.1. 认识组件化
什么是组件化
人面对复杂问题的处理方式:
任何一个人处理信息的逻辑能力都是有限的
所以,当面对一个非常复杂的问题时,我们不太可能一次性搞定一大堆的内容。
但是,我们人有一种天生的能力,就是将问题进行拆解。
如果将一个复杂的问题,拆分成很多个可以处理的小问题,再将其放在整体当中,你会发现大的问题也会迎刃而解。
组件化也是类似的思想:
如果我们将一个页面中所有的处理逻辑全部放在一起,处理起来就会变得非常复杂,而且不利于后续的管理以及扩展。
但如果,我们讲一个页面拆分成一个个小的功能块,每个功能块完成属于自己这部分独立的功能,那么之后整个页面的管理和维护就变得非常容易了。
Vue组件化思想
组件化是Vue.js中的重要思想
它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用。
任何的应用都会被抽象成一颗组件树。
组件化思想的应用:
有了组件化的思想,我们在之后的开发中就要充分的利用它。
尽可能的将页面拆分成一个个小的、可复用的组件。
这样让我们的代码更加方便组织和管理,并且扩展性也更强。
所以,组件是Vue开发中,非常重要的一个篇章,要认真学习。
6.2. 组件的基本使用
注册组件的基本步骤
组件的使用分成三个步骤:
创建组件构造器
注册组件
使用组件。
我们来看看通过代码如何注册组件
查看运行结果
和直接使用一个div看起来并没有什么区别。
但是我们可以设想,如果很多地方都要显示这样的信息,我们是不是就可以直接使用
注册组件步骤解析
这里的步骤都代表什么含义呢?
1.Vue.extend():
调用Vue.extend()创建的是一个组件构造器。
通常在创建组件构造器时,传入template代表我们自定义组件的模板。
该模板就是在使用到组件的地方,要显示的HTML代码。
事实上,这种写法在Vue2.x的文档中几乎已经看不到了,它会直接使用下面我们会讲到的语法糖,但是在很多资料还是会提到这种方式,而且这种方式是学习后面方式的基础。
2.Vue.component():
调用Vue.component()是将刚才的组件构造器注册为一个组件,并且给它起一个组件的标签名称。
所以需要传递两个参数:1、注册组件的标签名 2、组件构造器
3.组件必须挂载在某个Vue实例下,否则它不会生效。(见下页)
我们来看下面我使用了三次
而第三次其实并没有生效:
6.3. 全局组件和局部组件
当我们通过调用Vue.component()注册组件时,组件的注册是全局的
这意味着该组件可以在任意Vue示例下使用。
如果我们注册的组件是挂载在某个实例中, 那么就是一个局部组件
6.4. 父组件和子组件
在前面我们看到了组件树:
组件和组件之间存在层级关系
而其中一种非常重要的关系就是父子组件的关系
我们来看通过代码如何组成的这种层级关系:
父子组件错误用法:以子标签的形式在Vue实例中使用
因为当子组件注册到父组件的components时,Vue会编译好父组件的模块
该模板的内容已经决定了父组件将要渲染的HTML(相当于父组件中已经有了子组件中的内容了)
类似这种用法,
6.5. 注册的语法糖
在上面注册组件的方式,可能会有些繁琐。
Vue为了简化这个过程,提供了注册的语法糖。
主要是省去了调用Vue.extend()的步骤,而是可以直接使用一个对象来代替。
语法糖注册全局组件和局部组件:
6.6.模板的分离写法
script
template
刚才,我们通过语法糖简化了Vue组件的注册过程,另外还有一个地方的写法比较麻烦,就是template模块写法。
如果我们能将其中的HTML分离出来写,然后挂载到对应的组件上,必然结构会变得非常清晰。
Vue提供了两种方案来定义HTML模块内容:
使用<script>标签
使用<template>标签
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
</head>
<body>
<div id="app">
<cpn1></cpn1>
<cpn2></cpn2>
</div>
<!-- 模板抽离第一种写法 -->
<script type="text/x-template" id="cpn1">
<div>
<h2>模板抽离第一种写法</h2>
<p>1111模板抽离第一种写法</p>
</div>
</script>
<!-- 模板抽离的第二种写法 -->
<template id="cpn2">
<div>
<h2>模板抽离的第二种写法</h2>
<p>2222模板抽离的第二种写法</p>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
//1.注册一个全局组件
Vue.component('cpn1',{
template: '#cpn1'
})
const app = new Vue({
el:'#app',
components: {"cpn2":{
template: "#cpn2"
}}
})
</script>
</body>
</html>
6.7. 数据的存放
组件可以访问Vue实例数据吗?
组件是一个单独功能模块的封装:
这个模块有属于自己的HTML模板,也应该有属性自己的数据data。
组件中的数据是保存在哪里呢?顶层的Vue实例中吗?
我们先来测试一下,组件中能不能直接访问Vue实例中的data
组件数据的存放
组件对象也有一个data属性(也可以有methods等属性,下面我们有用到)
只是这个data属性必须是一个函数
而且这个函数返回一个对象,对象内部保存着数据
子组件不能直接访问父组件
子组件中有自己的data, 而且必须是一个函数.
为什么必须是一个函数.
为什么data在组件中必须是一个函数呢?
首先,如果不是一个函数,Vue直接就会报错。
其次,原因是在于Vue让每个组件对象都返回一个新的对象,因为如果是同一个对象的,组件在多次使用后会相互影响。
6.8. 父子组件的通信
在上一个小节中,我们提到了子组件是不能引用父组件或者Vue实例的数据的。
但是,在开发中,往往一些数据确实需要从上层传递到下层:
比如在一个页面中,我们从服务器请求到了很多的数据。
其中一部分数据,并非是我们整个页面的大组件来展示的,而是需要下面的子组件进行展示。
这个时候,并不会让子组件再次发送一个网络请求,而是直接让大组件(父组件)将数据传递给小组件(子组件)。
如何进行父子组件间的通信呢?Vue官方提到
通过props向子组件传递数据
通过事件向父组件发送消息
在下面的代码中,我直接将Vue实例当做父组件,并且其中包含子组件来简化代码。
真实的开发中,Vue实例和子组件的通信和父组件和子组件的通信过程是一样的。
父传子: props
在组件中,使用选项props来声明需要从父级接收到的数据。
props的值有两种方式:
方式一:字符串数组,数组中的字符串就是传递时的名称。
方式二:对象,对象可以设置传递时的类型,也可以设置默认值等。
我们先来看一个最简单的props传递:
props数据验证
在前面,我们的props选项是使用一个数组。
我们说过,除了数组之外,我们也可以使用对象,当需要对props进行类型等验证时,就需要对象写法了。
验证都支持哪些数据类型呢?
String
Number
Boolean
Array
Object
Date
Function
Symbol
当我们有自定义构造函数时,验证也支持自定义的类型
子传父: $emit
props用于父组件向子组件传递数据,还有一种比较常见的是子组件传递数据或事件到父组件中。
我们应该如何处理呢?这个时候,我们需要使用自定义事件来完成。
什么时候需要自定义事件呢?
当子组件需要向父组件传递数据时,就要用到自定义事件了。
我们之前学习的v-on不仅仅可以用于监听DOM事件,也可以用于组件间的自定义事件。
自定义事件的流程:
在子组件中,通过$emit()来触发事件。
在父组件中,通过v-on来监听子组件事件。
我们来看一个简单的例子:
我们之前做过一个两个按钮+1和-1,点击后修改counter。
我们整个操作的过程还是在子组件中完成,但是之后的展示交给父组件。
这样,我们就需要将子组件中的counter,传给父组件的某个属性,比如total。
自定义事件代码
父子组件通信案例
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
</head>
<body>
<div id="app">
<cpn :number1='num1' :number2='num2' @num1change='num1change' @num2change='num2change' />
</div>
<template id="cpn">
<div>
<h2>props:{{number1}}</h2>
<h2>data:{{dnumber1}}</h2>
<!-- <input type="text" v-model='dnumber1'> -->
<input type="text" :value='dnumber1' @input="num1Input">
<h2>props:{{number2}}</h2>
<h2>data:{{dnumber2}}</h2>
<!-- <input type="text" v-model='dnumber2'> -->
<input type="text" :value='dnumber2' @input="num2Input">
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
num1: 1,
num2: 0,
},
methods: {
num1change(value) {
this.num1 = parseFloat(value)
},
num2change(value) {
this.num2 = parseFloat(value)
},
},
components: {
'cpn': {
template: '#cpn',
props: {
number1: Number,
number2: Number
},
data() {
return {
dnumber1: this.number1,
dnumber2: this.number2
}
},
methods: {
num1Input(event) {
//1,将input中的value赋值到dnumber中
this.dnumber1 = event.target.value;
//2,为了将父组件可以修改值,发出一个事件
this.$emit('num1change', this.dnumber1);
//3,同时修改dnumber2的值
this.dnumber2 = this.dnumber1 * 100;
this.$emit('num2change', this.dnumber2);
},
num2Input(event) {
this.dnumber2 = event.target.value;
this.$emit('num2change', this.dnumber2);
//3,同时修改dnumber2的值
this.dnumber1 = this.dnumber2 / 100;
this.$emit('num1change', this.dnumber1);
}
}
}
}
})
</script>
</body>
</html>
6.9. 项目
npm install 安装依赖node
npm run serve 启动项目
七.JavaScript 高阶函数使用
编程范式: 命令式编程/声明式编程
编程范式: 面向对象编程(第一公民:对象)/函数式编程(第一公民:函数)
filter/map/reduce
filter中的回调函数有一个要求: 必须返回一个boolean值
true: 当返回true时, 函数内部会自动将这次回调的n加入到新的数组中
false: 当返回false时, 函数内部会过滤掉这次的n
const nums = [10, 20, 111, 222, 444, 40, 50]
let total = nums.filter(n => n < 100).map(n => n * 2).reduce((pre, n) => pre + n);
console.log(total);
let total = nums.filter(function (n) {
return n < 100
}).map(function (n) {
return n * 2
}).reduce(function (prevValue, n) {
return prevValue + n
}, 0)
console.log(total);
1.filter函数的使用
// 10, 20, 40, 50
let newNums = nums.filter(function (n) {
return n < 100
})
console.log(newNums);
2.map函数的使用
20, 40, 80, 100
let new2Nums = newNums.map(function (n) { // 20
return n * 2
})
console.log(new2Nums);
3.reduce函数的使用
reduce作用对数组中所有的内容进行汇总
let total = new2Nums.reduce(function (preValue, n) {
return preValue + n
}, 0)
console.log(total);
第一次: preValue 0 n 20
第二次: preValue 20 n 40
第二次: preValue 60 n 80
第二次: preValue 140 n 100
240
1.需求: 取出所有小于100的数字
let newNums = []
for (let n of nums) {
if (n < 100) {
newNums.push(n)
}
}
// 2.需求:将所有小于100的数字进行转化: 全部*2
let new2Nums = []
for (let n of newNums) {
new2Nums.push(n * 2)
}
console.log(new2Nums);
// 3.需求:将所有new2Nums数字相加,得到最终的记过
let total = 0
for (let n of new2Nums) {
total += n
}
console.log(total);