Vue

TIP

最后更新时间:2019年9月20日

字数:62087

Vue2都没学好,3.0都要开始开发了,😭😭😭

主要内容

  • 学习资源

  • JavaScript部分基础

    • let和const
    • ES6箭头函数
    • this指向问题
    • 数组方法
    • 字符串方法
  • Vuejs基础

    • 基本指令
    • 修饰符
    • 过滤器
    • 计算属性
    • 属性侦听
    • 生命周期
    • 指令
    • 组件
    • 网络请求
  • Vue-Router

    • SPA介绍
    • 路由配置
    • 监听路由
    • 路由钩子函数
  • Vuex

    • 核心概念
    • 使用demo
  • 模块化和webpack请参考这里

学习资源

JavaScript

TIP

这里的JavaScript能够帮助快速开发Vuejs项目,所以贴了出来

let、const和var

  • JS没有块级作用域:
    • 在JS函数中的var声明,其作用域是函数体的全部
    • var分为两种:局部作用域和函数作用域
    • var定义的变量可以修改,如果不初始化会输出undefined,不会报错
  • let是更完美的var:
    • let声明的变量拥有块级作用域
    • let声明的全局变量不是全局对象的属性
    • let不能重复声明,会报错
  • 形如for (let x...)的循环在每次迭代时都为x创建新的绑定:
    • 因为形成了闭包
    • 面试题经常遇到
  • const定义的变量:
    • 不可以修改,而且必须初始化
    • 如果const定义的对象,对象属性是可以修改的

ES6中的箭头函数

  • 不绑定this:
    • 箭头函数的this其实就是在定义的时候就确定好的,以后不管怎么调用这个箭头函数,箭头函数的this始终为定义时的this
  • 不绑定arguments:
    • 如果你在箭头函数中使用arguments参数不能得到想要的内容
  • 什么时候不建议用箭头函数:
    • 为对象的方法
    • 不能作为构造函数
    • 定义原型方法
    • 动态的修改this,最好还是不要使用箭头函数了

普通函数this指向问题

  • this总是代表它的直接调用者(js的this是执行上下文), 例如 obj.func ,那么func中的this就是obj
  • 在默认情况(非严格模式下,未使用 'use strict'),没找到直接调用者,则this指的是 window (约定俗成)
  • 在严格模式下,没有直接调用者的函数中的this是 undefined
  • 使用call,apply,bind(ES5新增)绑定的,this指的是 绑定的对象
  • 具体规则:
    • 函数调用模式:window
    • 方法调用模式(对象的方法):指向对象
    • 对象的方法:指向新创建出来的对象
    • call,apply,bind

数组⽅法

  • forEach:为每个元素执行对应的⽅法
  • map:返回一个由原数组中的每个元素调⽤一个指定方法后的返回值组成的新数组
  • some:用于测试数组中是否⾄少有一项元素通过了指定函数的测试
  • every:用于测试数组中所有元素是否都通过了指定函数的测试
  • filter:使⽤指定的函数测试所有元素,并创建一个包含所有通过测试的元素的新数组
  • reduce:接收一个函数作为累加器(accumulator),数组中的每个值(从左到右)开始合并,最终为一个值
  • indexof:返回在该数组中第一个找到的元素位置,如果它不存在则返回-1

展开运算符

  • 函数调用
  • 数组字面量展开
  • 解构赋值
  • 类数组对象变成数组

字符串方法

  • str.charAt(index):
    • 返回子字符串,index为字符串下标,index取值范围[0,str.length-1]
  • str.indexOf(searchString,startIndex):
    • 返回⼦字符串第一次出现的位置,从startIndex开始查找,找不不到时返回-1
  • str.lastIndexOf(searchString,startIndex):
    • 从右往左找子字符串,找不到时返回-1
  • str.substring(start,end):
    • 两个参数都为正数,返回值:[start,end) 也就是说返回从start到end-1的字符
  • str.slice(start,end):
    • 两个参数可正可负,负值代表从右截取,返回值:[start,end) 也就是说返回从start到end-1的字符
  • str.split(separator,limit):
    • 字符串拆分,参数1指定字符串或正则,参照2指定数组的最⼤长度
  • str.join():
    • 字符串拼接
  • str.replace(rgExp/substr,replaceText):
    • 返回替换后的字符串串
  • str.match(rgExp):
    • 正则匹配

认识Vue

Vue介绍

  • 作者大大:尤雨溪

  • Vue.js(读音 /vjuː/, 类似于 view) 是一套构建用户界面的渐进式框架。

  • Vue 只关注视图层, 采用自底向上增量开发的设计。

  • Vue 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。

MVC和MVVM

MVC

  • MVC:模型(model) — 视图(view) — 控制器器(controller)
  • 用户操作 ->View(负责接收用户的输入操作)->Controller(业务逻辑处理)->Model(数据持久化)->View(将结果反馈给View)

MVVM

  • MVVM:模型(model) — 视图(view) — 视图模型(ViewModel)

  • MVVM是将”数据模型数据双向绑定”的思想作为核心

  • 在View和Model之间没有联系,通过ViewModel进行交互,而且Model和ViewModel之间的交互是双向的

  • 因此视图的数据的变化会同时修改数据源,而数据源数据的变化也会立即反应到View上。

  • MVVM主要目的是分离视图(View)和模型(Model),优点:
    • 低耦合
    • 可重用性
    • 独立开发
    • 可测试

MVC VS MVVM

TIP

有人坚持Vue不是双向绑定的,有人坚持是,如果你听到有人说Vue不是双向绑定的,不要奇怪

Hello Vue

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Hello Vue</title>
</head>

<body>
  <!-- 指定Vue管理的区域 #app -->  
  <div id="app">
    <!-- 插值表达式 -->
    <h1>{{ msg }}</h1>
  </div>

  <!-- 1 引入vue.js文件 -->
  <!--  引入本地文件  -->
  <!-- <script src="./vue.js"></script> -->
  <!--  vue cnd  -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
  <script>
    // 2 创建Vue的实例对象
    // 参数是一个配置对象
    new Vue({
      // el 是 element的简写
      // 作用:指定了Vue管理的范围
      el: '#app',
      // 使用 data 属性,指定vue中使用的数据
      data: {
        msg: 'Hello Vue!'
      },
      methods: {
        fn: function () {
          console.log('方法fn')
        }
      }
    })
  </script>
</body>

</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

基本语法

插值表达式

  • Mustache语法
  • 双括号包裹需要渲染的值:{{}}
  • 注意事项:
    • 可以使用任意的JS表达式
    • 不能使用语句!!! if语句、for语句 等
<!-- JS表达式:能计算出一个值来的,就是表达式 -->
<h1>{{ isTrue ? '真的' : '假的' }}</h1>
<p>{{ 998 }}</p>
<p>{{ 9 + '数字加字符串' }}</p>
<p>{{ 'string' }}</p>

<!-- 注意:不能使用语句!!! if语句、for语句 等 -->
<p>{{ if (true) }}</p>
<!-- 这里会报错 -->
1
2
3
4
5
6
7
8
9

el

  • el:element
  • 指定了Vue管理理的范围

data

  • 指定了Vue中的数据
  • Vue 将会递归将 data 的属性转换为 getter/setter,从而让 data 的属性能够响应数据变化
  • 注意:
    • vue中的响应式数据是指定的时候就确定好的
    • 后期动态添加的数据是不能做到双向绑定的
  • 新添加和修改响应式数据:
    • Vue.set( target, key, value )或者this.$set()
    • target:要更改的数据源(可以是对象或者数组)
    • key:要更改的具体数据
    • value :重新赋的值

TIP

  • 这里很多同学都遇到了一个问题,莫名其妙的数据就不是双向绑定的了,这里有个坑
  • 我们请求回来的数据res.data,直接赋值给this.data中的数据,那么这个数据就固定了,后期添加的属性是无法,双向绑定的
  • 建议:先格式化数据,数据组装好了,在赋值给this.data
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>数据双向绑定</title>
</head>

<body>
  <div id="app">

    <div>请输入姓名:<input type="text" v-model="list.name"></div>
    <div>请输入年龄:<input type="text" v-model="list.age"></div>
    <div><button @click="add">点击添加新的属性</button></div>

    <br>
    <div>
      <h1>输入的数据是:</h1>
      <h1>姓名:{{ list.name }}</h1>
      <h1>年龄:{{ list.age }}</h1>
      <h1>新添加的属性是:{{list.height}}</h1>
    </div>
  </div>

  <!-- 1 引入vue.js文件 -->
  <!--  vue cnd  -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
  <script>
    new Vue({
      el: '#app',
      data: {
        list:{}
      },
      mounted(){
        this.fn()
      },
      methods: {
        fn: function () {
          
          // 这里res.data 是网络请求回来的数据
          let res = {
            status:200,
            data:{
              name:'zs',
              age:18
            }
          }
          // 赋值,这里的name和liss是双向绑定
          this.list = res.data           

        },
        add:function(){
          // 这里在list赋值完毕后,再添加给list的新属性,不是双向绑定的
          this.list.number = '110'

          console.log(this.list)
        }
      }
    })
  </script>
</body>

</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65

TIP

上面的例子可以看到打印出来的add方法this是包含number属性的,但是number属性却不是双向绑定的

记住一句话:data中的数据赋值后,新添加的结构或者属性都不是双向绑定的

data注意事项

使用的时候可以不加data

  data(){
    return:{
      name:"zhangsan"
    }
  },
  create(){
		this.name = "lisi"
  }
1
2
3
4
5
6
7
8

data可以返回一个对象(仅限于实例实例)

对象必须是纯粹的对象

new Vue({
  el:"#app",
  data:{
   	name:"zhangsan1"
  },
  data(){
  	return {
  		name:"zhangsan2"
  	}
  },
  method:{
    fn1(){
      console.log("fn1")
    },
    fn2(){
      console.log("fn2")
    },
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

TIP

  • vuejs中有2个data,一个是实例的data,一个是组件的data
  • 实例的data 不仅可以返回一个对象,也可以返回一个函数

组件的data只能返回函数

  • 在vuejs组件中,我们要求data必须返回一个函数,不能是一个对象
  • 原因:
    • Object是引用数据类型,如果不用function 返回,每个组件的data 都是内存的同一个地址,一个数据改变了其他也改变了
    • 解释:
      • javascipt只有函数构成作用域(注意理解作用域,只有函数的{}构成作用域,对象的{}以及 if(){}都不构成作用域),
      • data是一个函数时,每个组件实例都有自己的作用域,每个实例相互独立,不会相互影响

TIP

组件data报错

The "data" option should be a function that returns a per-instance value in component definitions
1

vuejs源码

vue/src/core/util/options.js文件中,对应版本:v2.5.17

strats.data = function (
  parentVal: any,
  childVal: any,
  vm?: Component
): ?Function {
  if (!vm) {
    if (childVal && typeof childVal !== 'function') {
      process.env.NODE_ENV !== 'production' && warn(
        'The "data" option should be a function ' +
        'that returns a per-instance value in component ' +
        'definitions.',
        vm
      )

      return parentVal
    }
    return mergeDataOrFn(parentVal, childVal)
  }

  return mergeDataOrFn(parentVal, childVal, vm)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

methods

  • Vue中所有的方法都是写在methods中的
  • 方法中的this自动绑定为 Vue 实例
  • 不应该使用箭头函数来定义 methods 函数

Vuejs开发须知

Object.defineProperty

Vue异步更新DOM

  • 只要观察到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。
  • 如果同一个 watcher 被多次触发,只会被推入到队列中一次。
  • 这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作上非常重要。
  • 然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。

TIP

如果多次连续改变数据,其实Vue只会更新最后一次数据,因为它有一个队列,记录改变的数据,短时间内可能会被覆盖掉变化

节省资源开销

数据绑定

data

  • data属性是Vue实例的数据对象,可以绑定的是对象或者是函数
  • 当数据对象一旦被data绑定就会发生变化,数据对象中的属性会拥有get和set属性,用来监听数据变化,实时响应
  • 如果data中的数据被绑定了,那么这个数据新添加的属性是不能双向绑定的

vue.set

指令

  • 指令是带有 v- 前缀的特殊属性。
  • 指令用于在表达式的值改变时,将某些行为应用到 DOM 上。

Vue基本指令

v-text

  • 设置⽂文本内容
  • 有相同作用
<span v-text="msg"></span>
<!-- 效果一样 -->
<span>{{msg}}</span>
1
2
3

TIP

v-text相当于原⽣DOM操作的innerText 或者 textContent

v-model

  • 在表单控件或者组件上创建双向绑定
  • v-model 其实是一个语法糖,这背后其实做了两个操作
    1. v-bind 绑定一个 value 属性
    2. v-on 指令给当前元素绑定 input 事件
  • 使用场景
    • HTML元素的v-model
    • 组件上的v-model两种

HTML元素

  • 包含:
    • input
    • select
    • textarea
<input v-model='name'>
<!-- 相当于 -->
<input v-bind:value="name" v-on:input="name = $event.target.value">
<!--当input接收到新的输入,就会触发input事件,将事件目标的value 值赋给绑定的元素 -->
1
2
3
4
  • textarea和input用法基本一致
<select v-model="selected">
    <option disabled value="">请选择</option>
    <option>西瓜</option>
    <option>橘子</option>
    <option>香蕉</option>
</select>
<span>Selected: {{ selected }}</span>
1
2
3
4
5
6
7

自定义组件

<my-component v-model='name'></my-componment>

<!-- 相当于 -->
<my-component v-bind:value='name' v-on:input='name = arguments[0]'></my-component>

// 触发事件绑定,这个字符串input就是v-on:冒号后面的方法名
this.$emit('input', value)
1
2
3
4
5
6
7
  • name接受的值就是input是事件的回掉函数的第一个参数
  • 所以在自定义的组件当中,要实现数据绑定,还需要使用 $emit 去触发input的事件。

v-html

  • 更新元素的innerHTML
  • 内容按普通 HTML 插入
    • 不会作为 Vue 模板进行编译
    • 也就是在这个指令下数据绑定会被忽略,而被当成HTML
  • 使用v-html动态渲染HTML是非常危险的,因为容易导致XSS攻击
  • 所以只能在可信的内容上使用 v-html

TIP

永远不要在用户提交和可操作的网页上使用 v-html

本质:永远不要一厢情愿的相信用户的输入,一定要对用户的输入进行限制和过滤

v-bind

  • 动态地绑定一个或多个特性,或一个组件 prop 到表达式
  • 在绑定class 或 style 特性时,支持其它类型的值,如数组或对象
  • 在绑定 prop 时,prop 必须在子组件中声明
  • 简写:冒号
<!-- 绑定一个属性 -->
<img v-bind:src="imageSrc">
<!-- 等同于 -->
<img :src="imageSrc">

<!-- 绑定动态calss 如果dataA="classA"变成了"classB",那么这个类名也是会变化的 -->
<div :class="dataA"></div>
<!-- :class也支持JavaScript表达式,比如:三元表达式 -->
<div :class="isClass ? 'classA' : 'classB' "></div>
<!-- 动态绑定class也是支持数组和对象的 -->

<!-- 动态style绑定 -->
<div :style="{ fontSize: size + 'px' }"></div>
1
2
3
4
5
6
7
8
9
10
11
12
13

TIP

项目中我们最常用的就是:动态绑定class或者是动态绑定style

v-if(v-else,v-else-if)

  • 根据表达式的值的真假条件渲染元素

  • 如果是隐藏,DOM中是没有这个元素的

<h1 v-if="isShow">你好!</h1>
<h1 v-else>小峰哥!</h1>
1
2

v-if使用注意事项

  • v-if 控制的隐藏元素是不显示在DOM中的

  • v-if 一定不要v-for同时使用在一个元素上

    • v-for比v-if优先,如果每一次都需要遍历整个数组,将会影响速度,尤其是当之需要渲染很小一部分的时候

    • <ul>
          <li
              v-for="item in list"
              v-if="item.isShow"
              :key="item.id"
              >
              {{ item.A }}
          </li>
      </ul>
      <!-- 即使100个item中之需要使用一个数据,也会循环整个数组 -->
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
    • 正确的做法:可以使用计算属性(computed)来操作

    • <template>
          <ul v-if="isShowStatus">
            <li
              v-for="item in list"
              :key="item.id"
            >
              {{ item.A }}
            </li>
          </ul>
      </template>
      <script>
          computed: {
            isShowStatus: function () {
              return this.list.filter(function (user) {
                return item.isShow
              })
            }
          }
      </script>
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19

v-show

  • 根据表达式之真假值,切换元素的display属性
  • 如果不显示元素,其实是display:none
  • 和v-if的区别就是这个时候DOM中是存在这个元素的
<div v-show="true">你好 !</div>
1

v-if VS v-show

  • v-if:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块
  • v-show:不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换

TIP

  • 一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销
  • 如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好

v-pre

  • 跳过这个元素和它的子元素的编译过程
  • 可以用来显示原始 Mustache 标签
  • 跳过大量没有指令的节点会加快编译
<!-- 这里就会显示: {{ username }}  不会被解析-->
<div v-pre>{{ username }}</div>

<!-- 当前元素和子元素中的表达式等都不会被解析  -->
<div v-pre>
    {{age}}
    <div>
        111
        <span>{{name}}</span>
    </div>
</div>

<!--

显示的是:
{{name}}
{{age}}
111 {{name}}

-->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

v-once

  • 只渲染元素和组件一次
  • 随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过
  • 这可以用于优化更新性能
 <div v-once>
  <h1>h1</h1>
  <p>{{ name }}</p>
</div>  
1
2
3
4

v-cloak

  • 这个指令保持在元素上直到关联实例结束编译

  • 和 CSS 规则如 [v-cloak] { display: none } 一起用时,这个指令可以隐藏未编译的 Mustache 标签直到实例准备完毕。

  • 结合CSS规则,解决插值表达式‘闪烁’问题

// 带有v-cloak的标签隐藏
[v-cloak] {
  display: none;
}

// 标签
<div v-cloak>
  {{ message }}
</div>
1
2
3
4
5
6
7
8
9

v-for

  • 基于源数据多次渲染元素或模板块,也就是循环渲染
<ul>
	<li v-for="(item,index) in items" :key="item.id">
      <span>{{ item.text }}</span>
	</li>
</ul>
1
2
3
4
5
  • items:我们要遍历的数据,可以是对象,也可以是数组
  • in:固定格式,你也可以用 of 替代 in 作为分隔符
  • item:在items每次遍历出来的结果item
  • index:为当前项的索引
  • key:和元素复用性能有关,有相同父元素的子元素必须有独特的 key,一般可以用id
<div v-for="(val, key) in object"></div>
<div v-for="(val, key, index) in object"></div>
1
2

TIP

  • v-for可以循环数组,对象和字符串以及number,Iterable (2.6 新增)
  • v-for 采用了复用机制,关键字key,参考资料

v-on

  • 监听 DOM 事件,并在触发时运行一些 JavaScript 代码
  • 可以接收一个需要调用的方法名称
  • 也可以在内联 JavaScript 语句中调用方法,其实就是时间调用,可以传参数
  • 处理DOM事件的时候,可以传递事件对象$event
  • 可以添加事件修饰符
<!-- 方法处理器 -->
<button v-on:click="clickFn"></button>

<!-- 简写 -->
<button @click="clickFn('name',$event)"></button>
1
2
3
4
5

修饰符

事件修饰符

  • 修饰符是由点开头的指令后缀来表示的
  • .stop:阻止冒泡修饰符
  • .prevent:阻止元素发生默认行为
  • .capture:捕获
  • .self:只点自己身上才运行
  • .once:只执行一次
  • .passive
<!-- 阻止单击事件继续传播 -->
<a v-on:click.stop="doThis"></a>

<!-- 提交事件不再重载页面 -->
<form v-on:submit.prevent="onSubmit"></form>

<!-- 修饰符可以串联 -->
<a v-on:click.stop.prevent="doThat"></a>

<!-- 只有修饰符 -->
<form v-on:submit.prevent></form>

<!-- 添加事件监听器时使用事件捕获模式 -->
<!-- 即元素自身触发的事件先在此处处理,然后才交由内部元素进行处理 -->
<div v-on:click.capture="doThis">...</div>

<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<div v-on:click.self="doThat">...</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

按键修饰符

  • 监听键盘事件时添加按键修饰符
  • .enter
  • .tab
  • .delete (捕获 “删除” 和 “退格” 键)
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right
  • ⾃定义按键修饰符:
    • Vue.config.keyCodes.f1 = 112

系统修饰符

  • .exact 修饰符
  • 鼠标按钮修饰符

@click

  • @click = 'fn' 相当于 v-on:click='fn'

  • 绑定点击事件

  • 可以传递参数和事件对象$event

过滤器filter

作用

  • vuejs中过滤器可以进行一些常见的文本格式化
  • 可以用在差值表达式和v-bind中
  • VueJs允许你链式调用过滤器,简单的来说,就是一个过滤器的输出成为下一个过滤器的输入,然后再次过滤

TIP

过滤器本质上是一个只带单一输入参数的函数

全局过滤器

  • Vue.filter
// capitalize 过滤器名称
Vue.filter('capitalize', function (value) {
  if (!value) return ''
  value = value.toString()
  return value.charAt(0).toUpperCase() + value.slice(1)
})

new Vue({
  // ...
})
1
2
3
4
5
6
7
8
9
10

局部过滤器

  • filters
// capitalize 过滤器名称
filters: {
  capitalize: function (value) {
    if (!value) return ''
    value = value.toString()
    return value.charAt(0).toUpperCase() + value.slice(1)
  }
}
1
2
3
4
5
6
7
8

过滤器使用

<!-- 在双花括号中 -->
{{ message | capitalize }}

<!--`v-bind`-->
<div v-bind:id="rawId | formatId"></div>
1
2
3
4
5
  • 竖线 ‘ | ’:管道,操作系统中也有管道这个概念

TIP

  • 在所有的过滤器中是没有this引用的,过滤器内的this是一个undefined的值

  • 禁止在过滤器内尝试引用组件示例内的变量或者方法,否则会发生空值引用的异常

计算属性computed

  • 对于一些复杂逻辑的数据处理,你都应当使用计算属性
  • 关键字:computed
  • 计算属性是有缓存的,只有当他的依赖项发生变化时,才会被重新计算
    • 对比与methods中的方法,没得调用都会被计算,所以性能消耗比computed大
computed: {
  sum: function () {
    return data.a + data.b
  }
}
1
2
3
4
5
  • 注意:计算属性不能与data中提供的数据重名, 否则会报错

属性侦听watch

  • 用于观察Vue实例上的数据变动,watch是一个对象,一定要当成对象来用
  • 对象的键
    • 就是你要监控的那个家伙,比如说$route,这个就是要监控路由的变化,或者是data中的某个变量
  • 值可以是函数:
    • 就是当你监控的家伙变化时,需要执行的函数,这个函数有两个形参,第一个是当前值,第二个是变化后的值。
  • 值也可以是函数名:
    • 不过这个函数名要用单引号来包裹。
  • 值是包括选项的对象:
    • 包含三个选项
    • handler:
      • 其值是一个回调函数。即监听到变化时应该执行的函数。
    • deep:
      • 其值是true或false;确认是否深入监听。(一般监听时是不能监听到对象属性值的变化的,数组的值变化可以听到。)
    • immediate:
      • 其值是true或false;确认是否以当前的初始值执行handler的函数。
var vm = new Vue({
  data: {
    a: 1,
    b: 2
  },
  watch: {
    // 函数
    a: function (val, oldVal) {
      console.log('new: %s, old: %s', val, oldVal)
    },
    // 方法名
    b: 'someMethod',
    // 选项的对象
    c: {
      handler: function (val, oldVal) { /* ... */ },
      deep: true,
      immediate: true
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

debounce

  • 一个通过 Lodash 限制操作频率的函数

  • debounce用于延迟调⽤用,延迟触发

  • 让一个方法在一定时间内只能执行一次

  • 使用场景:

    • 监听用户的输入,单位时间内用户不在进行输入,自动发送请求
    • 如果用户连续输入,间隔小于我们设置的时间,是不会出发请求的
    • 比如某个按钮,用户连续点击,可以只相应一次

Vue生命周期

生命周期图示

Vue生命周期三个阶段

  • 初始化阶段:
    • 进入页面
  • 更新阶段:
    • 当数据发生变化
  • 卸载阶段:
    • 实例卸载

生命周期函数

beforeCreate

  • 可以在这加个loading效果

created

  • 这时DOM还未生成,$el 属性还不存在
  • 发送ajax请求,获取数据,在这结束loading效果

beforeMount

  • 模块编译/挂在之前

mounted

  • 模块编译/挂在之后
  • 可以获取到DOM,然后,就可以进行DOM操作了

beforeUpdate:

  • 更更新前的DOM内容

updated

beforeDestroy

  • 你确认删除XX吗?

destroyed

  • 当前组件已被删除,清空相关内容

Vue.nextTick

  • nextTick由来:
    • 由于Vue的数据驱动视图更新,是异步的,即修改数据的当下,视图不会立刻更新,而是等同一事件循环中的所有数据变化完成之后,再统一进行视图更新
  • 异步执行的运行机制:
    • 所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)
    • 主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
    • 一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
    • 主线程不断重复上面的第三步
    • 简单来说,Vue 在修改数据后,视图不会立刻更新,而是等同一事件循环中的所有数据变化完成之后,再统一进行视图更新
  • nextTick的触发时机:
    • 在同一事件循环中的数据变化后,DOM完成更新,立即执行nextTick(callback)内的回调
  • 应用场景:
    • 需要在视图更新之后,基于新的视图进行操作
  • 举例:
// 场景
<div id="app">
    <p ref="myWidth" v-if="showMe">{{ message }}</p>
    <button @click="getMyWidth">获取p元素宽度</button>
</div>

getMyWidth() {
    this.showMe = true;
    //this.message = this.$refs.myWidth.offsetWidth;
    //报错 TypeError: this.$refs.myWidth is undefined
    this.$nextTick(()=>{
        //dom元素更新后执行,此时能拿到p元素的属性
        this.message = this.$refs.myWidth.offsetWidth;
  })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

TIP

相信你肯定有修改数据后,再获取DOM元素属性的场景,nextTick就是你最好的选中

指令directive

由来

  • 在 Vue2.0 中,代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。

  • 使用场景:

    • 操作DOM元素,可以结合jQuery

全局指令和局部指令

// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
  // 当被绑定的元素插入到 DOM 中时……
  inserted: function (el) {
    // 聚焦元素
    el.focus()
  }
})

// 使用
<input v-focus>

// 注册一个局部自定义指令 `v-focus`
directives: {
  focus: {
    // 指令的定义
    inserted: function (el) {
      el.focus()
    }
  }
}
// 使用
<input v-focus>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

自定义指令钩子函数

  • 目前我们知道有钩子函数的知识点:

    • ajax
    • Vuejs生命周期
    • VueRouter路由
    • Vuejs自定义指令
  • bind:

  • inserted:

  • update:

    • 所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
  • componentUpdated:

    • 指令所在组件的 VNode 及其子 VNode 全部更新后调用。
  • unbind:

    • 只调用一次,指令与元素解绑时调用
Vue.directive('hello',{
	// 指令第一次绑定到元素的时候执行,且只执行一次,一般用于执行初始化操作
    bind(el,binding,vnode,oldVnode){
      	// 这里的el就是我们想要的DOM对象,可以直接操作
      	// binding能传递我们需要的值
    	console.log('bind')
    },
    // 被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)
    inserted(el,binding,vnode,oldVnode){ 
    	console.log('inserted')
    },
    // 当被绑定的元素所在模版更新时执行
    update(el,binding,vnode,oldVnode){ 
    	console.log('update')
    },
    // 表示被绑定元素所在模板完成一次更新周期时调用
    componentUpdated(el,binding,vnode,oldVnode){ 
    	console.log('componentUpdated')
    },
    // 指令与被绑定的元素解绑时使用,只调用一次
    unbind(el,binding,vnode,oldVnode){ 
    	console.log('unbind')
    }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  • 指令钩子函数会被传入el,binding,vnode,oldVnode这四个参数
  • el:
    • 指令所绑定的元素,可以用来直接操作 DOM
  • binding:一个对象,包含以下属性:
    • name:指令名,不包括 v- 前缀。
    • value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2
    • oldValue:指令绑定的前一个值,仅在 updatecomponentUpdated 钩子中可用。无论值是否改变都可用。
    • expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"
    • arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"
    • modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }
  • vnode:Vue 编译生成的虚拟节点。
  • oldVnode:上一个虚拟节点,仅在 updatecomponentUpdated 钩子中可用。

refs

  • 类似于 jQuery 操作 DOM 结构
  • ref 被用来给元素或子组件注册引用信息
  • 引用信息将会注册在父组件的 $refs 对象上。
  • 如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例
  • 面试时如果被问到操作DOM:
    • 自定义指令
    • refs
  • 使用:
    • $refs是⼀一个对象,⻚页⾯面中所有的ref都会作为$refs对象中的键
    • DOM元素:$refs.xxx
// 使用
<div ref="box"></div>

// 获取
let box = this.$trfs.box
1
2
3
4
5

网络请求

axios

import axios from 'axios'
// 有些如果是预检请求之类的,可能会用到qs
import qs from 'qs'
// baseURL:基础url,可以根据环境进行切换
let interfaceUrl = ""
const basicRequest = params => {
    return new Promise(function (resolve, reject) {
        if (params.method && params.method.toLowerCase() === 'get') {
        axios.get(params.url,
        {

            baseURL: interfaceUrl,
            headers: {
                'Cache-Control': 'no-cache',
                'Content-Type': 'application/x-www-form-urlencoded',
                'access_token': params.token ? params.token : localStorage.getItem("access_token"),
                ...params.headers
            }
        }
        ).then((result) => {
            resolve(result.data);
        }).catch(err => {
            reject(err);
        });
        } else {
            axios.post(
            params.url,
            qs.stringify(params.ajaxData),
            {
                baseURL: interfaceUrl,
                headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
                'access_token': params.token ? params.token : localStorage.getItem("access_token"),
                ...params.headers
            }
        }
        ).then((result) => {
            resolve(result.data);
        }).catch(err => {
            reject(err);
        });
        }
        });
    }
		// 导出
    export default {
        request: basicRequest
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// main.js 安装到vuejs下面
import request from './utils/ajax'
Vue.prototype.$http = request
1
2
3
this.$http.request({
  url:'/boslogin',
  methods: 'post',
  ajaxData:{
    user: 'admin',
    password: '111111'
  }
}).then((res)=>{
  console.log('res')
})
1
2
3
4
5
6
7
8
9
10

axios特点

  • axios是Vue官方推荐
  • 不能use,因为不是Vue插件
  • 从 node.js 创建 http 请求
  • 支持 Promise
  • 客户端支持防止CSRF:
    • 就是让你的每个请求都带一个从cookie中拿到的key, 根据浏览器同源策略,假冒的网站是拿不到你cookie中得key的
    • 后台可以轻松辨别出这个请求是否是用户在假冒网站上的误导输入,从而采取正确的策略。
  • Axios中文文档

安装

# 使用npm
npm install axios

# 使用cnd
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

# 导入axios
import axios from 'axios'

# 装载全局,因为不是vue插件,只能挂载到vue实例下面
Vue.prototype.$http = axios
1
2
3
4
5
6
7
8
9
10
11

get请求

// 示例一
// 参数可以直接拼接到url中
axios.get('/user?ID=12345')
  .then(function (response) {
  	// 成功
    console.log(response);
  })
  .catch(function (error) {
  	// 失败
    console.log(error);
  });

// 示例二
// 参数可以是对象
axios.get('/user', {
    params: {
      ID: 12345
    }
  })
  .then(function (response) {
  	// 成功
    console.log(response);
  })
  .catch(function (error) {
    // 失败
    console.log(error);
  });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

post请求

// 需要安装qs
// npm install qs --save
import qs from 'qs'

let data = {
  username:"admin",
  password:"123456"
}
axios.post('/user', qs.stringify(data)
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

qs说明

  • 不使用qs序列化:

    • 请求类型为:application/json
  • 使用qs

    请求类型为:application/x-www-form-urlencoded;charset=UTF-8(默认)

TIP

  • 我们使用axios有时候会出现400错误(400错误是由于不正确的请求造成的,说明正在搜索的网页可能已经删除、更名或暂时不可用)
  • 这个时候必须使用qs转换请求参数

post请求后台收不到参数的可能原因(扩展)

  • 没有对要发送的数据进行序列化
data: qs.stringify(data)
1
  • 没有设置对应的post的请求头
headers: {
  'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
}
1
2
3
  • 解决axios发请求不带cookies的问题
axios.defaults.withCredentials = true // 请求会带cookies
1

发post请求时,1,2项设置必不可少

别名请求

axios.request(config)
axios.get(url[, config])
axios.delete(url[, config])
axios.head(url[, config])
axios.post(url[, data[, config]])
axios.put(url[, data[, config]])
axios.patch(url[, data[, config]])
1
2
3
4
5
6
7

TIP

在使用别名方法时, urlmethoddata 这些属性都不必在配置中指定

配置项

// axios默认请求,通过配置设置
axios(config)
1
2

配置项详解

参考来源

{
  // `url` 是用于请求的服务器 URL,必须参数
  url: '/user',

  // `method` 是创建请求时使用的方法
  method: 'get', // 默认是 get

  // `baseURL` 将自动加在 `url` 前面,除非 `url` 是一个绝对 URL。
  // 它可以通过设置一个 `baseURL` 便于为 axios 实例的方法传递相对 URL
  baseURL: 'http://xuefeng666.com/api/',

  // `transformRequest` 允许在向服务器发送前,修改请求数据
  // 只能用在 'PUT', 'POST' 和 'PATCH' 这几个请求方法
  // 后面数组中的函数必须返回一个字符串,或 ArrayBuffer,或 Stream
  transformRequest: [function (data) {
    // 对 data 进行任意转换处理
    return data;
  }],

  // `transformResponse` 在传递给 then/catch 前,允许修改响应数据
  transformResponse: [function (data) {
    // 对 data 进行任意转换处理

    return data;
  }],

  // `headers` 是即将被发送的自定义请求头
  headers: {'X-Requested-With': 'XMLHttpRequest'},

  // `params` 是即将与请求一起发送的 URL 参数
  // 必须是一个无格式对象(plain object)或 URLSearchParams 对象
  // 一般我们使用qs,方便快捷
  params: {
    ID: 12345
  },

  // `paramsSerializer` 是一个负责 `params` 序列化的函数
  // (e.g. https://www.npmjs.com/package/qs, http://api.jquery.com/jquery.param/)
  paramsSerializer: function(params) {
    return Qs.stringify(params, {arrayFormat: 'brackets'})
  },

  // `data` 是作为请求主体被发送的数据
  // 只适用于这些请求方法 'PUT', 'POST', 和 'PATCH'
  // 在没有设置 `transformRequest` 时,必须是以下类型之一:
  // - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams
  // - 浏览器专属:FormData, File, Blob
  // - Node 专属: Stream
  data: {
    firstName: 'Fred'
  },

  // `timeout` 指定请求超时的毫秒数(0 表示无超时时间)
  // 如果请求话费了超过 `timeout` 的时间,请求将被中断
  timeout: 1000,

  // `withCredentials` 表示跨域请求时是否需要使用凭证
  withCredentials: false, // 默认的

  // `adapter` 允许自定义处理请求,以使测试更轻松
  // 返回一个 promise 并应用一个有效的响应 (查阅 [response docs](#response-api)).
  adapter: function (config) {
    /* ... */
  },

  // `auth` 表示应该使用 HTTP 基础验证,并提供凭据
  // 这将设置一个 `Authorization` 头,覆写掉现有的任意使用 `headers` 设置的自定义 `Authorization`头
  auth: {
    username: 'janedoe',
    password: 's00pers3cret'
  },

  // `responseType` 表示服务器响应的数据类型,可以是 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream'
  responseType: 'json', // 默认的

  // `xsrfCookieName` 是用作 xsrf token 的值的cookie的名称
  xsrfCookieName: 'XSRF-TOKEN', // default

  // `xsrfHeaderName` 是承载 xsrf token 的值的 HTTP 头的名称
  xsrfHeaderName: 'X-XSRF-TOKEN', // 默认的

  // `onUploadProgress` 允许为上传处理进度事件
  onUploadProgress: function (progressEvent) {
    // 对原生进度事件的处理
  },

  // `onDownloadProgress` 允许为下载处理进度事件
  onDownloadProgress: function (progressEvent) {
    // 对原生进度事件的处理
  },

  // `maxContentLength` 定义允许的响应内容的最大尺寸
  maxContentLength: 2000,

  // `validateStatus` 定义对于给定的HTTP 响应状态码是 resolve 或 reject  promise 。如果 `validateStatus` 返回 `true` (或者设置为 `null` 或 `undefined`),promise 将被 resolve; 否则,promise 将被 rejecte
  validateStatus: function (status) {
    return status >= 200 && status < 300; // 默认的
  },

  // `maxRedirects` 定义在 node.js 中 follow 的最大重定向数目
  // 如果设置为0,将不会 follow 任何重定向
  maxRedirects: 5, // 默认的

  // `httpAgent` 和 `httpsAgent` 分别在 node.js 中用于定义在执行 http 和 https 时使用的自定义代理。允许像这样配置选项:
  // `keepAlive` 默认没有启用
  httpAgent: new http.Agent({ keepAlive: true }),
  httpsAgent: new https.Agent({ keepAlive: true }),

  // 'proxy' 定义代理服务器的主机名称和端口
  // `auth` 表示 HTTP 基础验证应当用于连接代理,并提供凭据
  // 这将会设置一个 `Proxy-Authorization` 头,覆写掉已有的通过使用 `header` 设置的自定义 `Proxy-Authorization` 头。
  proxy: {
    host: '127.0.0.1',
    port: 9000,
    auth: : {
      username: 'mikeymike',
      password: 'rapunz3l'
    }
  },

  // `cancelToken` 指定用于取消请求的 cancel token
  // (查看后面的 Cancellation 这节了解更多)
  cancelToken: new CancelToken(function (cancel) {
  })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125

响应要求

{
  // `data` 由服务器提供的响应
  data: {},

  // `status` 来自服务器响应的 HTTP 状态码
  status: 200,

  // `statusText` 来自服务器响应的 HTTP 状态信息
  statusText: 'OK',

  // `headers` 服务器响应的头
  headers: {},

  // `config` 是为请求提供的配置信息
  config: {}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

拦截器

在请求或响应被 thencatch 处理前拦截它们

// 导入axios
import axios from 'axios'
// 设置 axios 的接口的公共路径
axios.defaults.baseURL = 'api'

// axios 的拦截器
// 请求拦截器
axios.interceptors.request.use(function (config) {
  // 所有请求之前都要执行的操作
  if (config.method === 'get') {
    // 加载提示
  }
  return config;
});

// 响应拦截器
axios.interceptors.response.use(function (response) {
  // 所有请求完成后都要执行的操作
  setTimeout(() => {
    // 关闭提示
  }, 400);

  return response;
});

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

并发多个请求

// 同时执行多个请求
axios.all([

    axios.get('https://api.xuefeng666.com/xxx/1'),

    axios.get('https://api.xuefeng666.com/xxx/2')

  ])

  .then(axios.spread(function (userResp, reposResp) {

    // 上面两个请求都完成后,才执行这个回调方法

    console.log('User', userResp.data);

    console.log('Repositories', reposResp.data);

  }));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  • 只有两个请求都完成才会成功,否则会被catch捕获
  • 当所有的请求都完成后,会收到一个数组,包含着响应对象,其中的顺序和请求发送的顺序相同,可以使用 axios.spread 分割成多个单独的响应对象

TIP

axios.all()的目的是批量发送请求,等所有请求都有返回时,再执行统一的回调,并不是把所有的请求合并成同一个请求发送

ajax

  • 对原生XHR的封装,支持JSONP
  • 优点不用多说,缺点:
    • 不符合MVVM,因为是MVC
    • 给予XHR,XHR有问题
    • 太大,必须引入jQuery

fetch

  • 号称是AJAX的替代品
  • 优点:
    • API丰富
    • ES语法
    • 基于Promise,更友好,更简单
  • 缺点:
    • fetch默认不会带cookie,需要添加配置项
    • fetch没有办法原生监测请求的进度,而XHR可以
    • fetch只对网络请求报错,对400,500都当做成功的请求,需要封装去处理

组件component

  • 组件(Component)是 Vue.js 最强大的功能之一。
  • 组件可以扩展 HTML 元素,封装可重用的代码。
  • 组件系统让我们可以用独立可复用的小组件来构建大型应用,几乎任意类型的应用的界面都可以抽象为一个组件树

组件属性

  • 组件是可复用的 Vue 实例,所以它们与 new Vue 接收相同的选项
  • 例如 datacomputedwatchmethods 以及生命周期钩子等。仅有的例外是像 el这样根实例特有的选项

data

  • 组件的data必须是返回一个函数,不能是对象,详情看本章data部分

分类

  • 组件可以分为全局组件和局部组件

全局组件

  • Vue.component(tagName, options)
  • tagName:组件名
  • options:组件配置对象
  • 使用组件:<tagName></tagName>
<div id="app">
  <h1>app</h1>
  <!-- 这里的my-component不会生效,因为组件必须在实例创建之前注册才行 -->
  <!-- 
报错:Unknown custom element: <my-component> - did you register the component correctly? For recursive components, make sure to provide the "name" option.(found in <Root>) 
-->
  <my-component></my-component>
</div>
<div id="app1">
  <h1>app1</h1>
  <my-component></my-component>
</div>
<div id="app2">
  <h1>app2</h1>
  <my-component></my-component>
</div>
<!-- vuejs cdn -->
<script src="https://cdn.bootcss.com/vue/2.6.9/vue.js"></script>
<script type="text/javascript">

  new Vue({
    el:"#app",
  })

  // 1. 创建一个组件构造器
  var myComponent = Vue.extend({
    template: '<div>我是一个div,我是在组件构造器中创建的,我是全局组件</div>'
  })
  // 2. 注册组件,并且指定组件的标签,组件的html标签为<my-component>
  Vue.component('my-component',myComponent)

  // 3. 上面的1和2可以简写为:
  Vue.component('my-component',{
    template: '<div>我是一个div,我是在组件构造器中创建的,我是全局组件</div>'
  })
  new Vue({
    el:"#app1",
  })
  new Vue({
    el:"#app2",
  })
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

注意事项

  • 全局组件必须写在Vue实例创建之前,才在该根元素下面生效
  • 模板里面第一级(跟元素)只能有一个标签,不能并行,如果出现会报错,并且之渲染第一个元素
  • 组件调用时可以直接使用单标签形式和双标签都是可以的

说明理解

  • Vue.extend()是Vue构造器的扩展,调用Vue.extend()创建的是一个组件构造器,而不是一个具体的组件实例
  • Vue.extend()构造器有一个选项对象,选项对象的template属性用于定义组件要旋绕的HTML
  • 使用Vue.component()注册组件时,需要提供两个参数,第一个是组件的标签名,第二个是组件构造器
  • Vue.component()方法内部会调用组件构造器,创建一个组件实例
  • 组件应该挂载到某个Vue实例下,否则不会生效

局部组件

  • 注册:components
  • 组件配置对象:

    • template:html样式
    • data:组件的数据
    • methods:组件对应的方法
  • 组件嵌套:

    • 组件是可以嵌套的
<div id="app">
  <h1>app</h1>
  <!-- 3. my-component 使用(只能在#app下使用) -->
  <my-component></my-component>
</div>
<div id="app2">
  <h1>app2</h1>
  <!-- 这里会报错:因为局部组件没有在#app2实例中注册
Unknown custom element:
<my-component> - did you register the component correctly? 
For recursive components, make sure to provide the "name" option.
-->
  <my-component></my-component>
</div>

<!-- vuejs cdn -->
<script src="https://cdn.bootcss.com/vue/2.6.9/vue.js"></script>
<script type="text/javascript">

  // 1. 创建一个构造器
  var myComponent = Vue.extend({
    template:"<div>我是一个div,我是在组件构造器中创建的,我是局部组件</div>"
  })
  new Vue({
    el:"#app",
    components:{
      // 2. 将myComponent注册到vue实例下
      "my-component": myComponent
    }
  })

  new Vue({
    el:"#app2"
  })

</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

也可以简写

<script type="text/javascript">
  new Vue({
    el:"#app",
    components:{
      "my-component":{
        template:"<div>我是一个div,我是在组件构造器中创建的,我是局部组件</div>"
      }
    }
  })
</script>
1
2
3
4
5
6
7
8
9
10

注意事项

  • 全局组件用到的是 Vue.component(tagName,option)
  • 局部组件用到的是components:{tagName:option},tagname是自定义的组件名称,option是组件构造器
  • 局部组件只能在它注册的实例下使用,没有注册的实例是无法使用的

函数式组件

  • functional:无状态,也就是没有响应式数据
  • React中也有这个概念

什么是函数式组件

  • 我们可以把函数式组件想像成组件里的一个函数,入参是渲染上下文(render context),返回值是渲染好的HTML

函数组件特点

  • 无状态(Stateless)
    • 组件自身是没有状态的
  • 无实例(Instanceless)
    • 组件自身没有实例,也就是没有this

TIP

  • 由于函数式组件拥有的这两个特性,我们就可以把它用作高阶组件(High order components)
  • 所谓高阶,就是可以生成其它组件的组件

render

  • 函数化的组件中的 Render 函数,提供了第二个参数 context 作为上下文
  • data、props、slots、children 以及 parent 都可以通过 context 来访问
export default {
    name: 'functional-button',
    functional: true,
    render(createElement, context) {
      	// 函数式组件没有this,参数就是靠context来传递的
        return createElement('button', 'click me')
    }
}
1
2
3
4
5
6
7
8

content

content包含的属性如下:

  • props
  • children
  • slots (a slots object)
  • parent
  • listeners
  • injections
  • data
export default {
  functional: true,
  // 这里ES6参数的解构content
  render(createElement, { props, listeners, children }) {
    return createElement(
      'button',
      {
        attrs: props,
        on: {
          click: listeners.click
        }
      },
      children
    );
  }
};

// 这是比较复杂的,因为需要多次传递,后期尤大大就把许多参数都封装到data中了
export default {
  functional: true,
  // 这个data就是封装之后的,简单明了
  render(createElement, { data, children }) {
    return createElement( 'button', data, children );
  }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

参考demo

单文件组件

  • 文件扩展名vue
  • template:
    • 编写html结构
    • 只能一个根组件
  • script:
    • 编写Vue和js逻辑代码
    • 主要代码区域
  • style:
    • 编写样式
    • lang:支持less,sass,scss
    • scoped:限制作⽤用范围
  • Vue中所有的钩子函数在组件中都是可以用的
<template>
    <div class="root">
    </div>
</template>

<script>
    export default {
    };
</script>
<style lang="less" scoped>
</style>
1
2
3
4
5
6
7
8
9
10
11

name(官方解释)

  • 只有作为组件选项时起作用
  • 允许组件模板递归地调用自身
    • 注意,组件在全局用 Vue.component() 注册时,全局 ID 自动作为组件的 name
  • 指定 name 选项的另一个好处是便于调试
    • 有名字的组件有更友好的警告信息。另外,当在有 vue-devtools,未命名组件将显示成 <AnonymousComponent>,这很没有语义
    • 通过提供 name 选项,可以获得更有语义信息的组件树

组件嵌套

组件中也可以使用components选项来注册组件,使组件可以嵌套

<div id="app">
  <h1>app</h1>
  <my-component></my-component>
</div>

<!-- vuejs cdn -->
<script src="https://cdn.bootcss.com/vue/2.6.9/vue.js"></script>
<script type="text/javascript">

  new Vue({
    el:"#app",
    components:{
      "my-component":{
        // 这里引用组件内的子组件
        template:"<div>我是一个div,我是在组件构造器中创建的<h1><inner-Template></inner-Template></h1>我是局部组件</div>",
        components:{
          "inner-Template":{
            template:"<div>我是组件内部的组件</div>"
          }
        }
      }
    }
  })
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

模块化和组件化(简单理解)

  • 组件:

    • 最初的目的是代码重用,功能相对单一或者独立。
    • 在整个系统的代码层次上位于最底层,被其他代码所依赖,所以说组件化是纵向分层。
  • 模块:

    • 最初的目的是将同一类型的代码整合在一起,所以模块的功能相对复杂,但都同属于一个业务。
    • 不同模块之间也会存在依赖关系,但大部分都是业务性的互相跳转,从地位上来说它们都是平级的。
    • 模块化开发是横向分块
  • 组件化:重⽤用、解耦,⾼高重⽤用、松耦合,基础组件

  • 模块化:隔离/封装,⾼高内聚、松耦合,业务模块

  • 举例:发表评论功能是一个模块,但是其中添加图片就可以是一个组件

组件通讯

  • 因为组件是独⽴立的个体,需要组件通讯
  • 父组件到子组件:
    • props:props写在子组件中
  • 子组件到父组件:
    • ⽗组件提供一个⽅法,用来接受子组件中传递过来的数据
    • 将这个方法传递给子组件 @fn='方法名'
    • 由⼦组件触发这个方法,将要传递的数据作为⽅法的参数:this.$emit('fn', 参数)
  • 兄弟组件通讯:
    • Bus事件总线是解决兄弟间通信,祖⽗父祖孙间通信的最佳⽅方法、
    • 设置Bus,其实就是一个Vue实例
    • 注册时间:bus.$on('fn',function(data){})
    • 调⽤用⽅法传值:bus.$emit('fn', this.data);

props

从父组件向子组件传递数据的方式称为props

TIP

后续代码实例,我们到采用单文件组件的形式去演示

props介绍

  • props 可以是数组或对象,用于接收来自父组件的数据
  • props 可以是简单的数组,或者使用对象作为替代
  • 对象允许配置高级选项,如类型检测、自定义校验和设置默认值
// props 作为简单的数组
props: ['name', 'name1']

// props 传递复杂数据
props: {
  // name限定为字符串
  name: "String",
  // name1 更复杂
  name1: {
      type: 'String',
      // 默认值可以是固定的
      default:'zhangsan',
      // 默认值也可以是:对象或数组且一定会从一个工厂函数返回默认值
      default: function () {
        return { message: 'zhangsan' }
      }
      required: true,
      // 自定义验证函数
      validator: function (value) {
        // 实例:这个值必须匹配下列字符串中的一个
        return value === 'zhangsan' || t === 'lisi'
      }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

组件传值(父组件 => 子组件,利用props)

父组件

<template>
  <div>
    <Apple
      :n="name"
      n1="lisi"
      :user-name="userName">
    </Apple>
  </div>
</template>
<script>
// 导入组件
import Apple from './Apple'
export default {
  name: 'HelloWorld',
  // 注册组件
  components: {
    Apple
  },
  data () {
    return {
      name: '苹果',
      color: 'red',
      userName: 'zhangsan'
    }
  }
}
</script>
<style scoped>
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
  • Apple 是组件名称
  • n1是静态props数据
  • :n 是动态props数据,需要用v-bind绑定,可以传递我们父组件的动态数据

子组件

<template>
  <div>
    我是子组件,父组件传递过来的数据是:
    <ul>
      <li>n:{{ n }}</li>
      <li>n1:{{ n1 }}</li>
      <li>userName:{{ userName }}</li>
    </ul>
  </div>
</template>
<script>
export default {
  name: 'Apple',
  // 接受父组件传递过来的数据
 	// 并且,对数据进行限定(类型,是否必须)
  props: {
    n: {
      type: String,
      require: false
    },
    n1: {
      type: String,
      require: true
    },
    userName: {
      type: String
    }
  }
}
</script>
<style>
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

props大小写问题

官方推荐格式

  • 问题产生的原因

    • 由于 HTML 是大小写不敏感的,在 DOM 模板中必须仍使用 kebab-case
  • 组件名称的写法

<!-- 在单文件组件和字符串模板中 -->
<MyComponent/>

<!-- 在 DOM 模板中 -->
<my-component></my-component>

<!-- 或者:在所有地方 -->
<my-component></my-component>
1
2
3
4
5
6
7
8
  • props的写法
props: {
  userName: {
  type: String
  }
}
1
2
3
4
5
<Apple :user-name="userName"></Apple>
1

TIP

也就是在props中,传递的时候用kebab-case,子组件prosp中使用驼峰命名

props数据修改问题

props传递的数据不允许在子组件内修改,会发生如下报错:

vue.esm.js?efeb:628 [Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "userName"
1

原因:

  • 在vue2中,直接修改prop是被视作反模式的
  • 由于在新的渲染机制中,每当父组件重新渲染时,子组件都会被覆盖,所以应该把props看做是不可变对象

.sync说明:

在一些情况下,我们可能会需要对一个 prop 进行『双向绑定』。

事实上,这正是 Vue 1.x 中的 .sync修饰符所提供的功能。当一个子组件改变了一个 prop 的值时,这个变化也会同步到父组件中所绑定的值。

这很方便,但也会导致问题,因为它破坏了『单向数据流』的假设。由于子组件改变 prop 的代码和普通的状态改动代码毫无区别,当光看子组件的代码时,你完全不知道它何时悄悄地改变了父组件的状态。这在 debug 复杂结构的应用时会带来很高的维护成本。 

上面所说的正是我们在 2.0 中移除 .sync 的理由。

但是在 2.0 发布之后的实际应用中,我们发现 .sync 还是有其适用之处,比如在开发可复用的组件库时。我们需要做的只是让子组件改变父组件状态的代码更容易被区分。 

从 2.3.0 起我们重新引入了 .sync 修饰符,但是这次它只是作为一个编译时的语法糖存在。它会被扩展为一个自动更新父组件属性的 v-on 侦听器
1
2
3
4
5
6
7
8
9
10
11

解决方案

  • 让应该这个组件提交个事件给父组件,可以 watch 变量,如果变量发生改变就emit事件

$emit

  • 父组件可以使用 props 把数据传给子组件
  • 子组件可以使用 $emit 触发父组件的自定义事件

父组件

<template>
  <div>
    <!-- 传递事件给子组件,供子组件调用 -->
    <Apple @showFn="showFn"></Apple>
    <div>子组件传递过来的数据是:{{name}}</div>
  </div>
</template>
<script>
import Apple from './Apple'
export default {
  name: 'HelloWorld',
  components: {
    Apple
  },
  data () {
    return {
      name: ''
    }
  },
  methods: {
    showFn (val) {
      console.log('111')
      this.name = val
    }
  }
}
</script>
<style scoped>
</style>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

子组件

<template>
  <div>
    我是子组件,我要给父组件发送的数据是:{{name}}

    <button @click="clickFn">点击发送数据</button>
  </div>
</template>
<script>
export default {
  name: 'Apple',
  data () {
    return {
      name: 'zhangsan'
    }
  },
  methods: {
    clickFn () {
      console.log(this.name)
      // 调用传递过来的方法,也就是调用父组件提供的方法,并且传值
      this.$emit('showFn', this.name)
    }
  }
}
</script>
<style>
</style>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

组件模板

  • 类似arttemplate模板引擎
  • <script type="text/x-template" id="tpl"></script>
  • template:'#tpl'
    • 通过模板id来指定模板内容
  • components:
    • 定义局部组件
    • 后⾯跟⼀个配置对象,其中对象的键表示组件名称,对象的值表示组件的配置对象

内容分发

  • ⽤来获取组件标签中的⼦节点
  • slot:
    • 具名插槽
    • 获取到组件标签中的子节点
  • 作⽤域插槽:
    • slot-scope

JSX

  • JSX:JavaScript XML
    • React中常⻅见的写法
    • Vue也⽀支持
  • 节点:
    • 每个元素都是一个节点
    • 文档节点,属性节点,⽂本节点
  • 树:
    • 将HTML⽂文档视作树结构
    • 这种结构被称为节点树
  • 虚拟DOM:
    • 用代码对DOM对象进行描述追踪变化

SPA单页Web应用

介绍

  • 单页Web应用(single page web application)

  • 只有一张Web页面的应用

  • 单页应用程序 (SPA) 是加载单个HTML 页面并在用户与应用程序交互时动态更新该页面的Web应用程序

  • 优点:

    • 减少了请求体积,加快页⾯响应速度,降低了对服务器的压⼒
    • 更好的用户体验,让用户在web app感受native app的速度和流畅
    • 经典MVVM开发模式,前后端各负其责
    • 在URL中采用#号来作为当前视图的地址,改变#号后的参数,页面并不会重载

相关概念

  • hash:
    • 通过hash(锚点)变化切换显示的数据
  • 路由:
    • 浏览器URL中的哈希值(# hash)与展示视图内容(template)之间的对应规则
    • Vue路由中hash 和 component的对应关系是一个哈希值对应一个组件
  • vue-router:
    • Vue核⼼插件,官方推荐

Vue-Router

  • Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌

包含功能

  • 嵌套的路由/视图表
  • 模块化的、基于组件的路由配置
  • 路由参数、查询、通配符
  • 基于 Vue.js 过渡系统的视图过渡效果
  • 细粒度的导航控制
  • 带有自动激活的 CSS class 的链接
  • HTML5 历史模式或 hash 模式,在 IE9 中自动降级
  • 自定义的滚动条行为

Vue-Router配置

  • 创建:
    • new VueRoute({配置对象})
  • routes:
    • 路由规则
    • 数组
    • 内部一个对象是一个规则
  • path:
    • 路由的hash
  • component:
    • 路由对应的组件
  • redirect:
    • 重定向
  • linkActiveClass:
    • :默认高亮类名
    • 可以我们自己指定修改
  • router-link to:
    • 路由⼊⼝
  • router-view:
    • 路由出口

示例代码

// 导入
import Vue from 'vue'
import VueRouter from 'vue-router'

// 安装插件
Vue.use(VueRouter)

// 导入组件,打包写法
const App = () => import(/* webpackChunkName: "Home" */ '../App.vue')

const router = new VueRouter({
  routes: [
    { 
      path: '/', 
      redirect: '/App' 
    },
    { 
      path: '/App', 
      component: App, 
    },
  ],
  // 修改默认高亮类名
  linkActiveClass: 'active'
})

// 将 router 导出
export default router
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
  • 路由与实例关联
    • router

动态路径参数

  • 其实就是路由参数
  • 一个“路径参数”使用冒号 : 标记, /stu/:id?
  • 模板中获取路由参数:
    • $route.params.id
  • JS中获取路由参数:
    • this.$route.params.id
  • 嵌套路由:
    • children

监听路由

  • 复用组件时,对路由参数的变化作出响应
  • watch (监测变化) $route 对象
const User = {
  template: '...',
  watch: {
    '$route' (to, from) {
      // 对路由变化作出响应...
    }
  }
}
1
2
3
4
5
6
7
8

路由导航钩子函数

  • 路由导航钩子函数:
    • 拦截导航,让它完成跳转或取消
  • 分类:
    • 全局导航钩子
    • 路由独享的钩子
    • 组件内的导航钩子
  • 示例代码:
// 导入
import Vue from 'vue'
import VueRouter from 'vue-router'
// 安装插件
Vue.use(VueRouter)

// 导入组件
// 这个代码的作用:用来告诉webpack这个js文件你要从 app 中切出来,作为一个代码块(chunk)
// 第二个参数(vip)的作用:用于指定webpack在代码分割后生成js文件的名称
// const Home = r => require.ensure([], () => r(require('../components/Home.vue')), 'Home')

// 注意:注释是有意义的,用来指定代码分割后生成文件的名称
const App = () => import(/* webpackChunkName: "Home" */ '../App.vue')

const router = new VueRouter({
  routes: [
    { 
      path: '/', 
      redirect: '/App' 
    },
    { 
      path: '/App', 
      component: App, 
      // 路由独享的钩子函数
      beforeEnter: (to, from, next) => {
        console.log('home组件路由钩子函数:beforeEnter')
      },
      // 路由独享的钩子函数
      beforeLeave: (to, from, next) => {
        // ...
        console.log('home组件路由钩子函数:beforeLeave')
      }
    },
  ],
  // 修改默认高亮类名
  linkActiveClass: 'active'
})


//全局钩子函数
router.beforeEach((to, from, next) => {
    
    // 权限控制
    // 部分组件的一些兼容设置
    console.log('全局beforeEach',to)
    console.log('全局beforeEach',from)
    next()

})
router.afterEach((to, from) => {

    console.log('全局afterEach',to)
    console.log('全局afterEach',from)

})

// 因为 main.js 中需要使用当前模块中创建的路由实例,所以,此处需要将 router 导出
export default router


// 组件内的钩子函数,写在.vue组件内
// 组件路由钩子函数
    beforeRouteLeave(to, from, next) {
        // ....
      	// 在渲染该组件的对应路由被 confirm 前调用
        console.log('组件内路由钩子函数:beforeRouteLeave',to)
        console.log('组件内路由钩子函数:beforeRouteLeave',from)
        next()
    },
    beforeRouteEnter(to, from, next) {
        // ....
      	// 在当前路由改变,但是依然渲染该组件是调用
        console.log('组件内路由钩子函数:beforeRouteEnter',to)
        console.log('组件内路由钩子函数:beforeRouteEnter',from)
        next()
    },
    beforeRouteUpdate(to, from, next) {
        // ....
      	// 导航离开该组件的对应路由时被调用
        console.log('组件内路由钩子函数:beforeRouteUpdate',to)
        console.log('组件内路由钩子函数:beforeRouteUpdate',from)
        next()
    },

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84

全局导航钩子

  • 分类:
    • 前置守卫
    • 后置钩子
  • 前置守卫:
    • router.beforeEach((to, from, next) => { // ... })
    • to:
      • 类型:Route
      • 即将要进⼊入的⽬目标 路路由对象
    • from:
      • 类型:Route
      • 当前导航正要离开的路路由
    • next:
      • 类型:Function
      • 这是一个必须需要调用的方法,而具体的执行效果则依赖 next 方法调用的参数
        • next():进入管道中的下一个钩子,如果全部的钩子执行完了,则导航的状态就是 confirmed(确认的)
        • next(false):这代表中断掉当前的导航,即 to 代表的路由对象不会进入,被中断,此时该表 URL 地址会被重置到 from 路由对应的地址
        • next(‘/’) 和 next({path: ‘/’}):在中断掉当前导航的同时,跳转到一个不同的地址
        • next(error):如果传入参数是一个 Error 实例,那么导航被终止的同时会将错误传递给 router.onError() 注册过的回调
    • 注意:next 方法必须要调用,否则钩子函数无法 resolved

单个路由独享的钩⼦函数

  • 顾名思义,即单个路由独享的导航钩子
  • 上面示例代码中有的,可以仔细查看
  • 分类:
    • beforeEnter:进入
    • beforeLeave:离开

组件内的导航钩子

  • 直接在路由组件内部直接进行定义
  • 上面示例代码中有的,可以仔细查看
  • 分类:
    • beforeRouteEnter
      • 在渲染该组件的对应路由被 confirm 前调用
    • beforeRouteUpdate
      • 在当前路由改变,但是依然渲染该组件是调用
    • beforeRouteLeave
      • 导航离开该组件的对应路由时被调用
  • 注意:
    • beforeRouteEnter 不能获取组件实例 this
    • 因为当守卫执行前,组件实例被没有被创建出来
    • 剩下两个钩子则可以正常获取组件实例 this
  • this解决方案:
    • 我们可以通过给 next 传入一个回调来访问组件实例
    • 在导航被确认是,会执行这个回调,这时就可以访问组件实例了
beforeRouteEnter(to, from, next) {
    next (vm => {
        // 这里通过 vm 来访问组件实例解决了没有 this 的问题
    })
}
1
2
3
4
5
  • 注意:
    • beforeRouteEnter 不能获取组件实例 this
    • 因为当守卫执行前,组件实例没有被创建出来,剩下两个钩⼦则可以正常获取组件实例 this
  • 解决方案:
    • 我们可以通过给 next 传入一个回调来访问组件实例例。
    • 在导航被确认时,会执行这个回调,这时就可以访问组件实例了
next (vm => { 
	// 这⾥里里通过 vm 来访问组件实例例解决了了没有 this 的问题
})
1
2
3

完整的导航解析流程

  • 导航被触发
  • 在失活的组件⾥里里调⽤用离开守卫
  • 调⽤用全局的 beforeEach 守卫
  • 在重⽤用的组件⾥里里调⽤用 beforeRouteUpdate 守卫
  • 在路路由配置⾥里里调⽤用 beforEnter
  • 解析异步路路由组件
  • 在被激活的组件⾥里里调⽤用 beforeRouteEnter
  • 调⽤用全局的 beforeResolve 守卫
  • 导航被确认
  • 调⽤用全局的 afterEach 钩⼦子
  • 触发 DOM 更更新
  • 在创建好的实例例调⽤用 beforeRouteEnter 守卫中传给 next 的回调函数

路由缓存

  • keep-alive
  • meta设置参数
<keep-alive> 
  <router-view :key="key">
  </router-view> 
</keep-alive>
1
2
3
4
  • 如果想要单个子路由不刷新,只需要控制key,key值不变,缓存一直存在
  • 想要路由刷新,将key值改为前面没有的即可
  • 也可以利⽤在route中配置
meta:{
	title: 'xx⻚页',
	keep_alive: true # true 表示需要使⽤用缓存
}
1
2
3
4
  • 然后router-view中添加v-if:router-view(v-if="$route.meta.keep_alive"),但是有一定的局限性

Vuex介绍

  • 官方文档
  • 安装:npm install vuex —save
  • 介绍:
    • Vuex是专门为Vuejs设计的状态管理工具
    • 它不像Redux那样,是可以跨语言跨平台的
    • Vuex是专门来解决Vuejs中数据管理过程中出现的各种问题
    • 他采取数据集中管理的模式,来解决Vuejs中数据各种情况下出现的痛点。
  • 可以理解为一句话:数据就是状态,状态管理其实就是数据管理

核心仓库store

  • Vuex使用的是单一状态树
  • 核心就是一个对象管理所有的状态(数据)
  • 一个项目建议也应该只有一个数据仓库
// 引入
import vuex from 'vuex'
// 使用
Vue.use(vuex);
// 创建仓库对象store
var store = new vuex.Store({//store对象
    state:{
        show:false
    }
})
1
2
3
4
5
6
7
8
9
10

核心概念State

  • state是提供状态管理中数据的

TIP

  • js代码是基于内存运行的,一些数据刷新之后就么有了
  • 我们需要做数据存储,一般都是websql,localstorage和sessionstorage来保存数据

核心概念mutations

  • mutations提供操作数据的方法
  • 要求所有操作数据的方法都必须写在这个属性里面
  • 注意mutations必须是同步的,异步的有对应的action属性
{
    state:{
        data:{
          name:'zs',
          age:'18',
          hobby:['玩游戏','看电影']
        }
    },
    mutations:{
    	// 这里的state对应着上面这个state
    	// 每个方法,都会自动传递state
    	// 这个name使我们调用方法,传递进来的数据,专业数据叫做:载荷(payload)
        reName(state,name){
            state.data.name = name;
            //你还可以在这里执行其他的操作改变state
        }
    }
}

// 调用方法
$store.commit('reName', 'ls')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

核心概念getters

  • Vuex要求操作数据必须的实在mutations中进行
  • 如果我们需要读取数据的一些派生属性和状态,我们可以使用getters
  • 这一点其实类似于Vuejs中的计算属性computed
{
    state:{
        data:{
          name:'zs',
          age:'18',
          hobby:['玩游戏','看电影']
        }
    },
    getters:{
    	// 返回值就是我们要获取的数据
        hobbyCounts(state){
            return !state.data.hobby.length;
        }
    },
}

// 获取派生属性
// 注意派生仅仅是获取数据状态,无法修改数据,修改数据必须在mutations中
$store.getters.hobbyCounts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

异步操作数据actions

  • 在mutations中我们操作数据必须是同步的,但是有些情况下我们需要异步操作数据
  • 这个时候就可以用actions了,在actions中我们是可以异步操作数据的,就比如在定时器中调用actions的方法
{
    state:{
        data:{
          name:'zs',
          age:'18',
          hobby:['玩游戏','看电影']
        }
    },
    mutations:{
    	// 这里的state对应着上面这个state
    	// 每个方法,都会自动传递state
    	// 这个name使我们调用方法,传递进来的数据,专业数据叫做:载荷(payload)
        reName(state,name){
            state.data.name = name;
            //你还可以在这里执行其他的操作改变state
        }
    },
    actions: {
    	reName (context,name) {
      		context.commit('reName',name)
    	}
  }
}

// 调用
$store.dispatch('reName','ls')

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

模块化module

  • 由于使用单一状态树,应用的所有状态会集中到一个比较大的对象
  • 当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
  • 为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)
  • 每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割
// 官方实例
const moduleA = {
  state: { ... },
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

const moduleB = {
  state: { ... },
  mutations: { ... },
  actions: { ... }
}

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

map方法

  • mapState
  • mapGetters
  • mapActions

map其实就是一个在store文件中的映射而已,就是不用让你要调用一个值需要敲这么多代码:this.$state.count而只需要用count而已

示例:

mapState 辅助函数:

  • 当⼀个组件需要获取多个状态时候,将这些状态都声明为计算属性会有些重复和冗余。
  • 为了解决这个问题,我们可以使用mapState辅助函数帮助我们⽣成计算属性
  • mapState 函数返回的是⼀个对象
  • 通常我们需要使⽤一个⼯具函数将多个对象合并为⼀个,以使我们可以将最终对象传给computed属性。

Vuex注意点

Vue.js中axios请求代码应该写在组件的methods中还是vuex的actions中:

  • 如果请求来的数据不是要被其他组件公用,仅仅在请求的组件内使用,就不需要放⼊vuex 的state里。

  • 如果被其他地⽅复用,这个很大⼏率上是需要的,如果需要,请将请求放⼊action⾥,方便复⽤,并包装成promise返回,

  • 在调用处⽤async await处理返回的数据。如果不要复用这个请求,那么直接写在vue⽂件⾥很⽅便

Vue-cli

创建项目

npm init webpack 项目名称
1

脚手架使用less

vue-cli默认不支持less,需要我们安装less-loader

# 安装完毕后,即可使用less:lang="less"
npm install less less-loader --sava-dev
1
2