Vue2

Vue2

认识

概念

Vue.js 的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 DOM 的系统

1
2
3
<div id="app">
  {{ message }}
</div>
1
2
3
4
5
6
var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!'
  }
})

v- attribute 被称为指令。指令带有前缀 v-,以表示它们是 Vue 提供的特殊 attribute。

它们会在渲染的 DOM 上应用特殊的响应式行为

  • JavaScript 框架
  • 简化 Dom 操作
  • 响应式数据驱动

核心

  1. 数据绑定
  2. 事件绑定
  3. 用户输入获取
  4. 组件定义和使用

引入

CDN

1
2
3
4
5
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

<!-- 生产环境版本,优化了尺寸和速度 -->
<script src="https://cdn.jsdelivr.net/npm/vue"></script>

Vue CLI

安装

1
npm install -g @vue/cli

升级

1
npm update -g @vue/cli

创建项目

1
vue create project-name

安装必要工具

1
npm install

开启本地端口

1
2
npm run serve
npm run serve -- --port 5000

基础

Vue 实例

1
2
3
4
5
6
7
8
var app = new Vue({
    el:"#app",
    data:{
        message:"Hello World",
        array:[],
		obj:{},
    }
})
  • el:挂载点,
    • Vue 会管理 el 选项命中的元素及其内部的后代元素
    • 可以挂载到双标签上,不能使用 HTML 和 BODY
  • data:数据对象
    • Vue 中用到的数据定义在 data 中
    • data 中可以写复杂类型的数据
    • 渲染复杂类型数据时,遵守 js 的语法即可

Vue 指令

Vue 指令指的是,以 v- 开头的一组特殊语法

v-text

设置标签的文本值 (textContent)

支持在内部写表达式,如 v-text="message + '!'"

v-text 指令无论内容是什么,只会解析为文本

1
2
3
4
5
6
var app = new Vue({
    el: "#app",
    data: {
        message: "Hello"
    }
})

默认写法:在文本标签中添加 v-text 属性值,会替换全部内容

1
2
3
<div id="app">
    <h2 v-text="message"></h2>
</div>

差值表达式 {{ }} 可以替换指定内容

1
2
3
<div id='app'>
        <p>{{message + '!'}}</p>
</div>

v-html

设置标签的 innerHTML

若内容中有 html 结构,会被解析为标签,如 content:"<a href='#'>Hello World</a>"

辨析:解析文本使用 v-text,需要解析 html 结构使用 v-html

1
2
3
4
5
6
var app = new Vue({
    el:"#app",
    data:{
        content:"Hello World"
    }
})
1
2
3
<div id="app">
    <p v-html="content"></p>
</div>

v-on

为元素绑定事件

  • 事件名不需要写 on

  • 指令可以简写为 @

  • 绑定的方法定义在 methods 属性中

  • 方法内部通过 this 关键字可以访问定义在 data 中数据

1
2
3
4
5
6
7
8
var app = new Vue({
    el: "#app",
    methods: {
        doIt: function () {
            // ...
        }
    }
})
1
2
3
4
5
6
<div id="app">
    <input type="button" value="bind1" v-on:click="doIt">
    <input type="button" value="bind2" v-on:monseenter="doIt">
    <input type="button" value="bind3" v-on:dblclick="doIt">
    <input type="button" value="bind4" @dblclick="doIt">
</div>
例:计数器
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<div id="app">
    <input type='button' value='-' v-on:click='sub'>
    <span>{{value}}</span>
    <input type='button' value='+' v-on:click='add'>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
    var app = new Vue({
        el: "#app",
        data: {
            value: 1
        },
        methods: {
            sub: function () {
                this.value -= 1;
            },
            add: function () {
                this.value += 1;
            }
        },
    })
</script>
修饰符

事件的后面跟上 .修饰符 可以对事件进行限制

  • .stop - 调用 event.stopPropagation()
  • .prevent - 调用 event.preventDefault()
  • .capture - 添加事件侦听器时使用 capture 模式。
  • .self - 只当事件是从侦听器绑定的元素本身触发时才触发回调。
  • .{keyCode | keyAlias} - 只当事件是从特定键触发时才触发回调。
  • .native - 监听组件根元素的原生事件。
  • .once - 只触发一次回调。
  • .left - (2.2.0) 只当点击鼠标左键时触发。
  • .right - (2.2.0) 只当点击鼠标右键时触发。
  • .middle - (2.2.0) 只当点击鼠标中键时触发。
  • .passive - (2.3.0) 以 { passive: true } 模式添加侦听器
 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
<!-- 方法处理器 -->
<button v-on:click="doThis"></button>

<!-- 动态事件 (2.6.0+) -->
<button v-on:[event]="doThis"></button>

<!-- 内联语句 -->
<button v-on:click="doThat('hello', $event)"></button>

<!-- 缩写 -->
<button @click="doThis"></button>

<!-- 动态事件缩写 (2.6.0+) -->
<button @[event]="doThis"></button>

<!-- 停止冒泡 -->
<button @click.stop="doThis"></button>

<!-- 阻止默认行为 -->
<button @click.prevent="doThis"></button>

<!-- 阻止默认行为,没有表达式 -->
<form @submit.prevent></form>

<!--  串联修饰符 -->
<button @click.stop.prevent="doThis"></button>

<!-- 键修饰符,键别名 -->
<input @keyup.enter="onEnter">

<!-- 键修饰符,键代码 -->
<input @keyup.13="onEnter">

<!-- 点击回调只会触发一次 -->
<button v-on:click.once="doThis"></button>

<!-- 对象语法 (2.4.0+) -->
<button v-on="{ mousedown: doThis, mouseup: doThat }"></button>

v-show

根据表达值的真假,切换元素的显示和隐藏

  • 原理是修改元素的 display,实现显示隐藏
  • 指令后面的内容,最终都会解析为布尔值
1
2
3
4
5
6
7
var app = new Vue({
    el:"#app",
    data:{
        isShow:false,
        age:16
    }
})
1
2
3
4
5
<div id="app">
    <img src="地址" v-show="true">
    <img src="地址" v-show="isShow">
    <img src="地址" v-show="age>=18">
</div>

v-if

根据表达值的真假,切换元素的显示和隐藏 (操纵 dom 元素)

  • 本质是通过操纵 dom 元素来切换显示状态
  • 表达式的值为 true,元素存在于 dom 树中,为 false,从 dom 树中移除
  • 辨析:频繁的切换使用 v-show,偶尔切换使用 v-if,前者的切换消耗小
1
2
3
4
5
6
var app = new Vue({
    el:"#app",
    data:{
        isShow:false,
    }
})
1
2
3
4
5
    <div id="app">
      <p v-if="true">我是一个p标签</p>
      <p v-if="isShow">我是一个p标签</p>
      <p v-if="表达式">我是一个p标签</p>
    </div>

v-bind

设置元素的属性:v-bind:属性名=表达式

  • 可以用 : 简写
  • 需要动态的增删 class 建议使用对象的方式
1
2
3
4
5
6
7
8
var app = new Vue({
    el:"#app",
    data:{
        imgSrc:"picture-path",
        imgTitle:"text",
        isActive:false
    }
})
1
2
3
4
5
6
<div id="app">
    <img v-bind:src= "imgSrc" >
    <img v-bind:title="imgTitle+'!'">
    <img v-bind:class="isActive?'active':''">
    <img v-bind:class="{active:isActive}">
</div>

v-for

根据数据生成列表结构

  • 语法:(item, index) in array
  • item 和 index 可以结合其他指令一起使用
  • 数组长度的更新会同步到页面上,是响应式的
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var app = new Vue({
    el: "#app",
    data: {
        arr: ['a', 'b', 'c', 'd', 'e'],
        objArr: [
            { name: 'Mike' },
            { name: 'Jack' }
        ]
    }
})
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<div id="app">
    <ul>
        <li v-for="(item,index) in arr" :title='item'>
            {{index}} -> {{ item }}
        </li>

        <li v-for="item in objArr">
            {{ item.name }}
        </li>
    </ul>
</div>

v-model

获取和设置表单元素的值 (双向数据绑定)

  • 绑定的数据会和表单元素值相关联
1
2
3
<div id="app">
    <input type="text" v-model="message" />
</div>

组件间的数据传递

父子组件之间的数据传递可以使用 props 或者 $emit 等方式

父传子

使用 props

子组件部分
image-20210411160809257
父组件部分
image-20210411160816904

子传父

通过事件传递数据

子组件部分
image-20210411160915513
父组件部分
image-20210411161044777

一些原理

计算属性 computed 中的 getter

在 Vue 中,computed 的属性可以被视为是 data 一样,可以读取和设值,因此在 computed 中可以分成 getter(读取) 和 setter(设值),一般情况下是没有 setter 的,computed 预设只有 getter ,也就是只能读取,不能改变设值。

vue.js 计算属性默认只有 getter,因为是默认值所以我们也常常省略不写,如下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
var vm = new Vue({
  el: '#demo',
  data: {
    firstName: 'Foo',
    lastName: 'Bar'

  },
  computed: {
    fullName: function () {
      return this.firstName + ' ' + this.lastName
    }
  }
})

其实 computed 里的代码完整的写法应该是:

1
2
3
4
5
6
7
computed: {
    fullName: {
      get(){
         return this.firstName + ' ' + this.lastName
      }
    }
  }

axios

介绍

Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。

引入

使用 npm:

1
$ npm install axios

使用 bower:

1
$ bower install axios

使用 cdn:

1
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

样例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
axios({
    method: 'GET',
    url: url,
})
.then(res => {console.log(res)})
.catch(err => {console.log(err)})

axios.post('/user', {
    firstName: 'Mike',
    lastName: 'Allen'
}).then( res => {
    console.info(res)
}).catch( e => {
    console.info(e)
})

使用

axios(config)

1
2
3
4
5
6
7
8
9
// 发送 POST 请求
axios({
  method: 'post',
  url: 'www.google.com',
  data: {
    firstName: 'Fred',
    lastName: 'Flintstone'
  }
});

axios(url[, config])

1
2
// 发送 GET 请求(默认的方法)
axios('/user/12345');

别名

axios.get(url[, config])

axios.post(url[, data[, config]])

配置项

  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
{
  // `url` 是用于请求的服务器 URL
  url: '/user',

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

  // `baseURL` 将自动加在 `url` 前面,除非 `url` 是一个绝对 URL。
  // 它可以通过设置一个 `baseURL` 便于为 axios 实例的方法传递相对 URL
  baseURL: 'https://some-domain.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 对象
  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) {
  })
}

工程化目录结构

脚手架

http://markdown-1303167219.cos.ap-shanghai.myqcloud.com/image.png

架构

main.js

程序入口

App.vue

主视图

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<template>
  <div id="app">
    <div>
      <router-link to="/">Home</router-link> |
      <router-link to="/about">About</router-link>
    </div>
    <router-view />
  </div>
</template>

<script>
export default {};
</script>
<style lang="scss">
</style>

结构:<template>

脚本: <script>

样式:<style>

页面中放入一个路由视图容器:

1
<router-view></router-view>

引用路由链接:

1
2
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>

router

路由配置例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    component: () => import('../views/About.vue')
  }
]

用 Vue.js + Vue Router 创建单页应用

使用 Vue.js ,我们已经可以通过组合组件来组成应用程序;当你要把 Vue Router 添加进来,我们需要做的是,将组件 (components) 映射到路由 (routes),然后告诉 Vue Router 在哪里渲染它们。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>

<div id="app">
  <h1>Hello App!</h1>
  <p>
    <!-- 使用 router-link 组件来导航. -->
    <!-- 通过传入 `to` 属性指定链接. -->
    <!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
    <router-link to="/foo">Go to Foo</router-link>
    <router-link to="/bar">Go to Bar</router-link>
  </p>
  <!-- 路由出口 -->
  <!-- 路由匹配到的组件将渲染在这里 -->
  <router-view></router-view>
</div> 

单文件组件

Vuex

认识

vuex 是一个专门为 vue.js 应用程序开发的状态管理模式。

核心思想:把组件的共享状态抽取出来,以一个全局单例模式进行管理

http://markdown-1303167219.cos.ap-shanghai.myqcloud.com/vuex.png

五类对象

  • state:存储状态(变量)在组件中使用 $store.state.foo
  • getters:对数据获取之前的再次编译,可以理解为 state 的 computed 属性。在组件中使用 $store.getters.fun()
  • mutations:修改状态,并且是同步的。在组件中使用 $store.commit('funcName',params) 。它和组件中的自定义事件类似。
  • actions:异步操作。在组件中使用是 $store.dispath('funcName',params)
  • modules:store 的子模块,为了开发大型项目,方便状态管理而使用的。

简单配置

main.js

Vuex 提供了一个从根组件向所有子组件,以 store 选项的方式 “注入” 该 store 的机制

1
2
3
4
5
6
7
8
9
//main.js内部对store.js的配置
import store from '"@/store/store.js' 
//具体地址具体路径
new Vue({
    el: '#app',
    store, //将store暴露出来
    template: '<App></App>',
    components: { App }
});

store/index.js

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import Vue from 'vue'; //首先引入vue
import Vuex from 'vuex'; //引入vuex
Vue.use(Vuex) 

export default new Vuex.Store({
    state: { 
        // state 类似 data
        //这里面写入数据
    },
    getters:{ 
        // getters 类似 computed 
        // 在这里面写个方法
    },
    mutations:{ 
        // mutations 类似methods
        // 写方法对数据做出更改(同步操作)
    },
    actions:{
        // actions 类似methods
        // 写方法对数据做出更改(异步操作)
    }
})

或者

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
mport Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const state = {}
const getters = {}
const mutations = {}
const actions = {}

const store = new Vuex.Store({
  state,
  getters,
  mutations,
  actions
})

export default store;

但更建议的做法是:在单独的文件里写好每个组件的状态,最后统一在 index.js 中引入

项目结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
├── index.html
├── main.js
├── api
│   └── ... # 抽取出API请求
├── components
│   ├── App.vue
│   └── ...
└── store
    ├── index.js          # 我们组装模块并导出 store 的地方
    ├── actions.js        # 根级别的 action
    ├── mutations.js      # 根级别的 mutation
    └── modules
        ├── cart.js       # 购物车模块
        └── products.js   # 产品模块

对象

state

state 中存放状态对象

1
2
3
4
5
6
const state = {
    todos: [
        { id: 1, title: "Wake up", done: false },
        { id: 2, title: "Learn", done: false },
    ]
}

以形似 $store.state.todos$store.state.todo.todos (如果此状态分模块单独存放的话)调用

getters

getters 里存放着从 state 中派生出来的一些状态,类似于 computed,所以 getters 里的状态都具有相应的依赖值,而且以函数的形式进行定义

1
2
3
4
5
6
7
8
const getters = {
    doneTodos: state => {
        return state.tasks.filter(task => task.done)
    },
    doneTodosNum: (state, getters) => {
        return getters.doneTodos.length
    }
}

注意,getter 也可以接受其他 getter 作为第二个参数

通过属性访问

getter 会暴露为 store.getters 对象,可以以属性的形式访问这些值,如 $store.getters.doneTodoNum不分组件

通过方法访问

也可以通过让 getter 返回一个函数,来实现给 getter 传参。在对 store 里的数组进行查询时非常有用

1
2
3
4
5
6
getTodoById: state => id => {
        return state.todos.find(todo => todo.id === id)
    }

// 调用时
$store.getters.getTodoById(2)

注意,getter 在通过方法访问时,每次都会去进行调用,而不会缓存结果

mutations

更改 Vuex 的 store 中的状态的唯一方法是提交 mutation

Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
const mutations = {
    addTask(state, newTaskTitle) {
        console.log('add: ' + newTaskTitle);
        let newTask = {
            id: Date.now(),
            title: newTaskTitle,
            done: false,
        };
        state.tasks.push(newTask);
        newTaskTitle = "";
    },
}

要唤醒一个 mutation handler,需要以相应的 type 调用 store.commit 方法,如 $store.commit('mutations','Sleep')不分组件

载荷

传入的额外参数称为载荷(payload)

在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
mutations: {
  increment (state, payload) {
    state.count += payload.amount
  }
}

// 调用时
store.commit('increment', {
  amount: 10
})

type 属性

提交 mutation 的另一种方式是直接使用包含 type 属性的对象

1
2
3
4
store.commit({
  type: 'increment',
  amount: 10
})

Mutation 需遵守 Vue 的响应规则

既然 Vuex 的 store 中的状态是响应式的,那么当我们变更状态时,监视状态的 Vue 组件也会自动更新。这也意味着 Vuex 中的 mutation 也需要与使用 Vue 一样遵守一些注意事项:

  1. 最好提前在你的 store 中初始化好所有所需属性。
  2. 当需要在对象上添加新属性时,你应该
  • 使用 Vue.set(obj, 'newProp', 123), 或者

  • 以新对象替换老对象。例如,利用对象展开运算符 (opens new window) 我们可以这样写:

    1
    
    state.obj = { ...state.obj, newProp: 123 }
    

actions

Action 类似于 mutation,不同在于:

  • Action 提交的是 mutation,而不是直接变更状态。
  • Action 可以包含任意异步操作。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment (context) {
      context.commit('increment')
    }
  }
})

Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.statecontext.getters 来获取 state 和 getters

Action 通过 store.dispatch 方法触发,如 $store.dispatch('increment')

Actions 支持同样的载荷方式和对象方式进行分发

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 以载荷形式分发
store.dispatch('incrementAsync', {
  amount: 10
})

// 以对象形式分发
store.dispatch({
  type: 'incrementAsync',
  amount: 10
})

使用参数结构定义

实践中,我们会经常用到 ES2015 的 参数解构 (opens new window) 来简化代码(特别是我们需要调用 commit 很多次的时候):

1
2
3
4
5
actions: {
  increment ({ commit }) {
    commit('increment')
  }
}

异步触发

可以在 action 内部执行异步操作

1
2
3
4
5
6
7
actions: {
  incrementAsync ({ commit }) {
    setTimeout(() => {
      commit('increment')
    }, 1000)
  }
}

来看一个更加实际的购物车示例,涉及到调用异步 API分发多重 mutation

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
actions: {
  checkout ({ commit, state }, products) {
    // 把当前购物车的物品备份起来
    const savedCartItems = [...state.cart.added]
    // 发出结账请求,然后乐观地清空购物车
    commit(types.CHECKOUT_REQUEST)
    // 购物 API 接受一个成功回调和一个失败回调
    shop.buyProducts(
      products,
      // 成功操作
      () => commit(types.CHECKOUT_SUCCESS),
      // 失败操作
      () => commit(types.CHECKOUT_FAILURE, savedCartItems)
    )
  }
}

modules

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。

为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割

默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应。

如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。例如:

mapState、mapGetters、mapActions

mapState

当一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性,让你少按几次键:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// 在单独构建的版本中辅助函数为 Vuex.mapState
import { mapState } from 'vuex'

export default {
  // ...
  computed: mapState({
    // 箭头函数可使代码更简练
    count: state => state.count,

    // 传字符串参数 'count' 等同于 `state => state.count`
    countAlias: 'count',

    // 为了能够使用 `this` 获取局部状态,必须使用常规函数
    countPlusLocalState (state) {
      return state.count + this.localCount
    }
  })
}

当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组。

1
2
3
4
computed: mapState([
  // 映射 this.count 为 store.state.count
  'count'
])