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 操作
- 响应式数据驱动
核心
- 数据绑定
- 事件绑定
- 用户输入获取
- 组件定义和使用
引入
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
| vue create project-name
|
安装必要工具
开启本地端口
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
为元素绑定事件
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
子组件部分
父组件部分
子传父
通过事件传递数据
子组件部分
父组件部分
一些原理
计算属性 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:
使用 bower:
使用 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) {
})
}
|
工程化目录结构
脚手架
架构
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 应用程序开发的状态管理模式。
核心思想:把组件的共享状态抽取出来,以一个全局单例模式进行管理
五类对象
- 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 一样遵守一些注意事项:
- 最好提前在你的 store 中初始化好所有所需属性。
- 当需要在对象上添加新属性时,你应该
使用 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.state
和 context.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'
])
|