vue-基础语法和组件化开发

vue基础语法

一. 计算属性

1.1. 计算属性的本质

fullname: {set(), get()}

每个计算属性都包含一个getter和一个setter

在上面的例子中,我们只是使用getter来读取。

在某些情况下,你也可以提供一个setter方法(不常用)。

在需要写setter的时候,代码如下:

image-20201214182819692

1.2. 计算属性和methods对比

计算属性在多次使用时, 只会调用一次.

它是由缓存的

我们可能会考虑这样的一个问题:

methods和computed看起来都可以实现我们的功能,

那么为什么还要多一个计算属性这个东西呢?

原因:计算属性会进行缓存,如果多次使用时,计算属性只会调用一次。

我们来看下面的代码:

image-20201214182856040

1.3. ES6 语法

let/var (定义变量)

image-20201214184516259

块级作用域

变量作用域 :

ES5中的var是没有作用域的

ES中的let是有作用域的(if / for)

变量在什么范围内是可用的

var没有作用域限制(会引起一些问题 )

没有块级作用域所引起的问题

没有块级作用域 数据得不到保证,在哪都能修改

const (定义长量)

image-20201214203659831

对象字面量增强写法

image-20201214204140177

二. 事件监听

2.1. 事件监听基本使用

在前端开发中,我们需要经常和用于交互。

这个时候,我们就必须监听用户发生的时间,比如点击、拖拽、键盘事件等等

在Vue中如何监听事件呢?使用v-on指令

v-on介绍

作用:绑定事件监听器

缩写:@

预期:Function | Inline Statement | Object

参数:event

这里,我们用一个监听按钮的点击事件,来简单看看v-on的使用

下面的代码中,我们使用了v-on:click="counter++”

另外,我们可以将事件指向一个在methods中定义的函数

image-20201214205527840

2.2. 参数问题

btnClick

btnClick(event) :!--2.在事件定义时, 写方法时省略了小括号, 但是方法本身是需要一个参数的, 这个时候, Vue会默认将浏览器生产的event事件对象作为参数传入到方法-->

btnClick(abc, event) -> $event : !--3.方法定义时, 我们需要event对象, 同时又需要其他参数-->

!-- 在调用方式, 如何手动的获取到浏览器参数的event对象: $event-->

当通过methods中定义方法,以供@click调用时,需要注意参数问题:
情况一:如果该方法不需要额外参数,那么方法后的()可以不添加。
但是注意:如果方法本身中有一个参数,那么会默认将原生事件event参数传递进去
情况二:如果需要同时传入某个参数,同时需要event时,可以通过$event传入事件

image-20201214205920482

Event 本身包含适用于所有事件的属性和方法。

构造器:可以使用event()创建一个event对象

Event 对象代表事件的状态,比如事件在其中发生的元素、键盘按键的状态、鼠标的位置、鼠标按钮的状态。事件通常与函数结合使用,函数不会在事件发生前被执行!

2.3. 修饰符

stop

prevent

.enter

.once

.native

在某些情况下,我们拿到event的目的可能是进行一些事件处理。

Vue提供了修饰符来帮助我们方便的处理一些事件:

.stop - 调用 event.stopPropagation()。

.prevent - 调用 event.preventDefault()。

.{keyCode | keyAlias} - 只当事件是从特定键触发时才触发回调。

.native - 监听组件根元素的原生事件。

.once - 只触发一次回调。

image-20201214205944591

三. 条件判断

3.1. v-if/v-else-if/v-else

v-if、v-else-if、v-else

这三个指令与JavaScript的条件语句if、else、else if类似。

Vue的条件指令可以根据表达式的值在DOM中渲染或销毁元素或组件

简单的案例演示:

image-20201214213741711

v-if的原理:

v-if后面的条件为false时,对应的元素以及其子元素不会渲染。

也就是根本没有不会有对应的标签出现在DOM中。

3.2. 登录小案例

用户再登录时,可以切换使用用户账号登录还是邮箱地址登录。
类似如下情景:

image-20201214214209247

小问题:
如果我们在有输入内容的情况下,切换了类型,我们会发现文字依然显示之前的输入的内容。
但是按道理讲,我们应该切换到另外一个input元素中了。
在另一个input元素中,我们并没有输入内容。
为什么会出现这个问题呢?

问题解答:
这是因为Vue在进行DOM渲染时,出于性能考虑,会尽可能的复用已经存在的元素,而不是重新创建新的元素。
在上面的案例中,Vue内部会发现原来的input元素不再使用,直接作为else中的input来使用了。

解决方案:
如果我们不希望Vue出现类似重复利用的问题,可以给对应的input添加key
并且我们需要保证key的不同

image-20201214214259714

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

image-20201214214428410

四. 循环遍历

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在原数组的索引值。

image-20201214221501062

    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可以用户遍历对象:

比如某个对象中存储着你的个人信息,我们希望以列表的形式显示出来。

image-20201214221616627

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。

image-20201214222650238

image-20201214222707283

因为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() 反转

image-20201214223241537

注意: 通过索引值修改数组中的元素(不是响应式)

    // 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的基本使用

image-20201215184836334

5.2 表单绑定v-model

v-model => v-bind:value v-on:input

表单控件在实际开发中是非常常见的。特别是对于用户信息的提交,需要大量的表单。

Vue中使用v-model指令来实现表单元素和数据的双向绑定。

image-20201215185250338

案例的解析:

当我们在输入框输入内容时

因为input中的v-model绑定了message,所以会实时将输入的内容传递给message,message发生改变。

当message发生改变时,因为上面我们使用Mustache语法,将message的值插入到DOM中,所以DOM会发生响应的改变。

所以,通过v-model实现了双向的绑定。

当然,我们也可以将v-model用于textarea元素

image-20201215185255225

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">

image-20201215185740013

5.4. v-model和radio/checkbox/select

当存在多个单选框时

image-20201215185852366

v-model:checkbox

单个勾选框:

v-model即为布尔值。

此时input的value并不影响v-model的值

多个复选框:

当是多个复选框时,因为可以选中多个,所以对应的data中属性是一个数组。

当选中某一个时,就会将input的value添加到数组中。

image-20201215190004950

image-20201215190013367

v-model:select

单选:只能选中一个值。

v-model绑定的是一个值。

当我们选中option中的一个时,会将它对应的value赋值到mySelect中

多选:可以选中多个值。

v-model绑定的是一个数组。

当选中多个值时,就会将选中的option对应的value添加到数组mySelects中

image-20201215190201063

5.5. 修饰符

lazy:

默认情况下,v-model默认是在input事件中同步输入框的数据的。

也就是说,一旦有数据发生改变对应的data中的数据就会自动发生改变。

lazy修饰符可以让数据在失去焦点或者回车时才会更新:

number:

默认情况下,在输入框中无论我们输入的是字母还是数字,都会被当做字符串类型进行处理。

但是如果我们希望处理的是数字类型,那么最好直接将内容当做数字处理。

number修饰符可以让在输入框中输入的内容自动转成数字类型:

trim:

如果输入的内容首尾有很多空格,通常我们希望将其去除

trim修饰符可以过滤内容左右两边的空格

image-20201215191550999

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. 认识组件化

什么是组件化

人面对复杂问题的处理方式:

任何一个人处理信息的逻辑能力都是有限的

所以,当面对一个非常复杂的问题时,我们不太可能一次性搞定一大堆的内容。

但是,我们人有一种天生的能力,就是将问题进行拆解。

如果将一个复杂的问题,拆分成很多个可以处理的小问题,再将其放在整体当中,你会发现大的问题也会迎刃而解。

组件化也是类似的思想:

如果我们将一个页面中所有的处理逻辑全部放在一起,处理起来就会变得非常复杂,而且不利于后续的管理以及扩展。

但如果,我们讲一个页面拆分成一个个小的功能块,每个功能块完成属于自己这部分独立的功能,那么之后整个页面的管理和维护就变得非常容易了。

image-20201215202521145

image-20201215202528845

image-20201215202535501

Vue组件化思想

组件化是Vue.js中的重要思想

它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用。

任何的应用都会被抽象成一颗组件树。

image-20201215202729534

组件化思想的应用:

有了组件化的思想,我们在之后的开发中就要充分的利用它。

尽可能的将页面拆分成一个个小的、可复用的组件。

这样让我们的代码更加方便组织和管理,并且扩展性也更强。

所以,组件是Vue开发中,非常重要的一个篇章,要认真学习。

6.2. 组件的基本使用

注册组件的基本步骤

组件的使用分成三个步骤:

创建组件构造器

注册组件

使用组件。

我们来看看通过代码如何注册组件

查看运行结果

和直接使用一个div看起来并没有什么区别。

但是我们可以设想,如果很多地方都要显示这样的信息,我们是不是就可以直接使用来完成呢?

image-20201215203128326

注册组件步骤解析

这里的步骤都代表什么含义呢?

1.Vue.extend():

调用Vue.extend()创建的是一个组件构造器。

通常在创建组件构造器时,传入template代表我们自定义组件的模板。

该模板就是在使用到组件的地方,要显示的HTML代码。

事实上,这种写法在Vue2.x的文档中几乎已经看不到了,它会直接使用下面我们会讲到的语法糖,但是在很多资料还是会提到这种方式,而且这种方式是学习后面方式的基础。

2.Vue.component():

调用Vue.component()是将刚才的组件构造器注册为一个组件,并且给它起一个组件的标签名称。

所以需要传递两个参数:1、注册组件的标签名 2、组件构造器

3.组件必须挂载在某个Vue实例下,否则它不会生效。(见下页)

我们来看下面我使用了三次

而第三次其实并没有生效:

image-20201215204257160

6.3. 全局组件和局部组件

当我们通过调用Vue.component()注册组件时,组件的注册是全局的

这意味着该组件可以在任意Vue示例下使用。

如果我们注册的组件是挂载在某个实例中, 那么就是一个局部组件

image-20201215205324631

6.4. 父组件和子组件

在前面我们看到了组件树:

组件和组件之间存在层级关系

而其中一种非常重要的关系就是父子组件的关系

我们来看通过代码如何组成的这种层级关系:

父子组件错误用法:以子标签的形式在Vue实例中使用

因为当子组件注册到父组件的components时,Vue会编译好父组件的模块

该模板的内容已经决定了父组件将要渲染的HTML(相当于父组件中已经有了子组件中的内容了)
是只能在父组件中被识别的。

类似这种用法,是会被浏览器忽略的。

image-20201215205539627

6.5. 注册的语法糖

在上面注册组件的方式,可能会有些繁琐。

Vue为了简化这个过程,提供了注册的语法糖。

主要是省去了调用Vue.extend()的步骤,而是可以直接使用一个对象来代替。

语法糖注册全局组件和局部组件:

image-20201215210323574

6.6.模板的分离写法

script

template

刚才,我们通过语法糖简化了Vue组件的注册过程,另外还有一个地方的写法比较麻烦,就是template模块写法。

如果我们能将其中的HTML分离出来写,然后挂载到对应的组件上,必然结构会变得非常清晰。

Vue提供了两种方案来定义HTML模块内容:

使用<script>标签

使用<template>标签

image-20201215210414193

<!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

image-20201216100420693

组件数据的存放

组件对象也有一个data属性(也可以有methods等属性,下面我们有用到)

只是这个data属性必须是一个函数

而且这个函数返回一个对象,对象内部保存着数据

image-20201216100902649

子组件不能直接访问父组件

子组件中有自己的data, 而且必须是一个函数.

为什么必须是一个函数.

为什么data在组件中必须是一个函数呢?

首先,如果不是一个函数,Vue直接就会报错。

其次,原因是在于Vue让每个组件对象都返回一个新的对象,因为如果是同一个对象的,组件在多次使用后会相互影响。

image-20201216101148670

6.8. 父子组件的通信

在上一个小节中,我们提到了子组件是不能引用父组件或者Vue实例的数据的。

但是,在开发中,往往一些数据确实需要从上层传递到下层:

比如在一个页面中,我们从服务器请求到了很多的数据。

其中一部分数据,并非是我们整个页面的大组件来展示的,而是需要下面的子组件进行展示。

这个时候,并不会让子组件再次发送一个网络请求,而是直接让大组件(父组件)将数据传递给小组件(子组件)。

如何进行父子组件间的通信呢?Vue官方提到

通过props向子组件传递数据

通过事件向父组件发送消息

image-20201216101435503

在下面的代码中,我直接将Vue实例当做父组件,并且其中包含子组件来简化代码。

真实的开发中,Vue实例和子组件的通信和父组件和子组件的通信过程是一样的。

父传子: props

在组件中,使用选项props来声明需要从父级接收到的数据。

props的值有两种方式:

方式一:字符串数组,数组中的字符串就是传递时的名称。

方式二:对象,对象可以设置传递时的类型,也可以设置默认值等。

我们先来看一个最简单的props传递:

image-20201216103350232

props数据验证

在前面,我们的props选项是使用一个数组。

我们说过,除了数组之外,我们也可以使用对象,当需要对props进行类型等验证时,就需要对象写法了。

验证都支持哪些数据类型呢?

String

Number

Boolean

Array

Object

Date

Function

Symbol

image-20201216103522743

当我们有自定义构造函数时,验证也支持自定义的类型

image-20201216103551053



子传父: $emit

props用于父组件向子组件传递数据,还有一种比较常见的是子组件传递数据或事件到父组件中。

我们应该如何处理呢?这个时候,我们需要使用自定义事件来完成。

什么时候需要自定义事件呢?

当子组件需要向父组件传递数据时,就要用到自定义事件了。

我们之前学习的v-on不仅仅可以用于监听DOM事件,也可以用于组件间的自定义事件。

自定义事件的流程:

在子组件中,通过$emit()来触发事件。

在父组件中,通过v-on来监听子组件事件。

我们来看一个简单的例子:

我们之前做过一个两个按钮+1和-1,点击后修改counter。

我们整个操作的过程还是在子组件中完成,但是之后的展示交给父组件。

这样,我们就需要将子组件中的counter,传给父组件的某个属性,比如total。

自定义事件代码

image-20201216103939589

父子组件通信案例

<!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);
posted @ 2021-01-11 09:55  赵刚、  阅读(274)  评论(0编辑  收藏  举报