构建 Vuex 模块
在上一篇教程中,我们在 resources/assets/js/api/cafe.js
文件中通过 JavaScript 的 Axios 库构建了一些调用 Laravel 后端 API 路由的方法。在这一篇教程中我们需要将从 API 接口获取的数据保存下来以便在单页面应用中使用,而这正是 Vuex 模块可以大展拳脚的地方。
在 Vuex 文档中将 Vuex 定位成专为 Vue.js 应用程序开发的状态管理模式,它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。听上去有点抽象,翻译过来就是可以在多个组件和页面中使用的单点数据。为什么我们需要这个?因为随着构建的应用越来越大,单页面也会变得越来越复杂,在多个地方使用数据会非常麻烦。例如,假设你有一份登录到应用的用户数据,有了 Vuex 模块之后,你无需通过用户名将用户作为参数传递到不同的组件,只需将其保存到 Vuex 模块中,然后在任何地方都可以访问这个模块来获取数据。
Vuex 在跟踪应用程序中的数据状态方面也非常有用。如果你使用了 Vue 开发工具,就可以查看每个模块包含的数据以及如何访问它。
Vuex 在一开始有点难以理解,因为这是一种全新的保存数据的方式,好在 Vuex 官方文档 非常详尽,你可以通过它们深入学习。不是所有的应用都需要 Vuex 来存储数据,但是如果你在构建一个大型的单页面应用,并且需要以更优雅地方式来处理数据,那么 Vuex 是一个不错的选择。
第一步:配置 resources/assets/js/store.js
我们之前已经创建过一个初始化的 store.js
文件,现在需要打开这个文件,并添加一些代码来实现一个初始化的数据存储器。
首先,需要在 store.js
顶部导入 Vue 和 Vuex:
/**
* Import Vue and Vuex
*/
import Vue from 'vue'
import Vuex from 'vuex'
接下来,要告知 Vue 使用 Vuex 作为数据存储器,这将会扩展 Vue 实例具备使用 Vuex 数据存储器所需要的方法。将下面这行代码放到导入 Vue 和 Vuex 之后的地方:
/**
* Initializes Vuex on Vue.
*/
Vue.use( Vuex )
最后,我们将会从 store.js
文件导出一个新的 Vuex 数据存储器。这样我们就可以将其应用到 Vue 实例并让所有模块在各个组件和路由中都可以访问。将下面这段代码放到 store.js
末尾:
/**
* Export the data store.
*/
export default new Vuex.Store({
modules: {
}
});
这样我们就有了一个最基本的数据存储器配置,我们可以在其基础上轻松实现模块扩展。
第二步:安装 es6-promise 支持 IE 数据存储
上述配置在 IE 11 下不能正常工作,因为 IE 11 不支持 promise,需要通过 NPM 安装 es6-promise
:
npm install es6-promise --save-dev
然后在 resources/assets/js/store.js
文件顶部加入如下这段代码:
/**
* Adds the promise polyfill for IE 11
*/
require('es6-promise').polyfill();
最终版本的 store.js
文件内容如下:
/*
|-------------------------------------------------------------------------------
| VUEX store.js
|-------------------------------------------------------------------------------
| Builds the data store from all of the modules for the Roast app.
*/
/**
* Adds the promise polyfill for IE 11
*/
require('es6-promise').polyfill();
/**
* Import Vue and Vuex
*/
import Vue from 'vue'
import Vuex from 'vuex'
/**
* Initializes Vuex on Vue.
*/
Vue.use( Vuex )
/**
* Export our data store.
*/
export default new Vuex.Store({
modules: {
}
});
第三步:新增数据存储器到 Vue 实例
现在数据存储器已经构建好了,需要将其添加到 Vue 中,Vue 实例位于 resources/assets/js/app.js
,打开该文件,在
import router from './routes.js')
之后添加如下这行代码:
import store from './store.js'
这将会引入我们上几步创建的数据存储器,接下来,我们需要通过数据存储器来扩展 Vue 实例:
new Vue({
router,
store
}).$mount('#app');
现在我们就可以在应用中使用整个 Vue 全家桶了!下面我们将新增一些模块并使其可以正常工作。
第四步:新增 Vuex 模块 cafes.js
首先需要在 resources/assets/js/modules
目录下创建一个名为 cafes.js
的文件,我们将在这个文件中管理所有的咖啡店数据,然后在整个应用中使用这些数据。从这里,也可以看到单页面应用的优点:一次加载页面,将数据存储到 Vuex 模块,到处使用,只有在需要重新加载页面时才重新加载。
现在 cafes.js
文件还是空的,下一步就来配置这个文件。
第五步:配置 Vuex 模块的 state 属性
在 resources/assets/js/modules/cafes.js
文件中,首先从 api
目录下导入咖啡店相关 API,我们将使用其中的 API 请求方法来加载数据:
/*
|-------------------------------------------------------------------------------
| VUEX modules/cafes.js
|-------------------------------------------------------------------------------
| The Vuex data store for the cafes
*/
import CafeAPI from '../api/cafe.js';
现在我们将会导出一个常量作为咖啡店模块,在导入 CafeAPI
的下面添加如下这段代码:
export const cafes = {
}
这就是我们要添加到数据存储器的模块,稍后我们会将其导入到数据存储器。
接下来,我们需要设置上述 Vuex 模块的四个属性(state、actions、mutations、getters)。
首先,添加一个空的 state
对象:
export const cafes = {
state: {
}
}
该状态是所有我们想要跟踪数据的状态,在 cafes
模块中有两个需要跟踪的数据:咖啡店数组,以及存储单个咖啡店的对象。分别对应返回所有咖啡店和单个咖啡店的 API。我们会这样初始化这两个数据:
export const cafes = {
state: {
cafes: [],
cafe: {}
}
}
根据经验,我们经常遇到的一个问题是显示加载状态。在单页面应用中,加载状态至关重要。HTML/CSS 和其他页面功能通常会在向等待数据加载的用户提供不良UX的数据之前加载。对于我们在状态中跟踪的每个数据,我会为跟踪加载状态的数据状态添加相应的变量。这样我就可以读取这个变量来确定是否显示加载状态。 随着 Vue 被激活,数据被加载后,这个变量会更新,使用该变量的组件也会更新,并相应地显示到页面。相应的状态变量定义如下:
export const cafes = {
state: {
cafes: [],
cafesLoadStatus: 0,
cafe: {},
cafeLoadStatus: 0
},
}
我通常定义状态码如下:
-
status = 0
-> 数据尚未加载 -
status = 1
-> 数据开始加载 -
status = 2
-> 数据加载成功 -
status = 3
-> 数据加载失败
这样我们就可以基于数据加载状态在需要的时候相应的提示信息。
第六步:配置 Vuex 模块的 actions 属性
actions
在模块中用于被调用来修改状态。在本教程中,我们会调用一个 action 用于发起 API 请求并提交 mutations
。mutations
我们会在下一步中实现。
在 actions
对象中我们可以添加方法来加载所有咖啡店和单个咖啡店信息:
export const cafes = {
state: {
cafes: [],
cafesLoadStatus: 0,
cafe: {},
cafeLoadStatus: 0
},
actions: {
loadCafes( { commit } ){
},
loadCafe( { commit }, data ){
}
}
};
上述代码 actions
部分有两个需要注意的地方:
- 每个方法都包含一个名为
commit
的析构参数,该参数通过 Vuex 传入,允许我们提交mutations
。你还可以传入其他的析构参数,要了解更多关于参数析构的细节,可以参考 lukehoban/es6features 这个 Github 项目。 -
loadCafe
动作包含了一个名为data
的第二个参数。该参数是一个对象,包含我们想要加载的咖啡店的 ID。
现在,我们来实现这两个方法:
actions: {
loadCafes( { commit } ){
commit( 'setCafesLoadStatus', 1 );
CafeAPI.getCafes()
.then( function( response ){
commit( 'setCafes', response.data );
commit( 'setCafesLoadStatus', 2 );
})
.catch( function(){
commit( 'setCafes', [] );
commit( 'setCafesLoadStatus', 3 );
});
},
loadCafe( { commit }, data ){
commit( 'setCafeLoadStatus', 1 );
CafeAPI.getCafe( data.id )
.then( function( response ){
commit( 'setCafe', response.data );
commit( 'setCafeLoadStatus', 2 );
})
.catch( function(){
commit( 'setCafe', {} );
commit( 'setCafeLoadStatus', 3 );
});
}
},
首先需要注意的是 commit
函数,该函数用于提交一个 mutation,我们会在下一步设置 mutations
。再次重申,state
中的每个数据片段都应该有一个与之对应的 mutation。在上面两个方法中,我们都提交了所使用的状态的加载状态,接下来,调用 API 来加载想要加载的指定信息状态,这些 API 调用定义在 resources/assets/js/api/cafe.js
文件中,之后链式调用 then
和 catch
方法,前者在 API 请求成功后调用,后者在 API 请求失败后调用,response
变量会传递到这两个方法,以便获取响应数据和请求头。
第七步:配置 Vuex 模块的 mutations 属性
mutations
定义了数据的更新方式,每个模块都有 state
,每个 state
都需要对应的 mutation 来更新,完整工作流如下:
- 用户调用一个 action
- 该 action 加载/计算数据
- 该 action 提交一个 mutation
- state 被更新
- getter 将更新后的 state 返回给组件
- 组件被更新
以上工作流可以通过多种方式来实现,不过相较于 jQuery 或 vanilla JS 的实现,使用 Vuex 更加简单。
我们已经定义了 state
和 actions
,现在是时候实现 mutations
了,我们在配置 actions
时已经看到了 mutations
的调用,现在只需实现其功能代码即可:
mutations: {
setCafesLoadStatus( state, status ){
},
setCafes( state, cafes ){
},
setCafeLoadStatus( state, status ){
},
setCafe( state, cafe ){
}
},
所有 mutations
所做的工作都是设置 state
,所以第一个参数是 state
,这里的 state
是局部模块 state 而不是全局 state,所以我们在第六步中配置的 state
可以被访问,第二个参数是 state
更新后的数据,所以最终实现如下:
mutations: {
setCafesLoadStatus( state, status ){
state.cafesLoadStatus = status;
},
setCafes( state, cafes ){
state.cafes = cafes;
},
setCafeLoadStatus( state, status ){
state.cafeLoadStatus = status;
},
setCafe( state, cafe ){
state.cafe = cafe;
}
},
在每个 mutation 中,我们将局部模块的 state
数据设置为传入的更新后数据,这也正是每个 mutation 所要做的操作。接下来,我们将会配置 getters
。
第八步:配置 Vuex 模块的 getters 属性
到目前为止,我们已经有了想要跟踪的 state
数据,从 API 接口获取数据的 actions
,以及用于设置 state
的 mutations
,现在需要定义 getters
从模块中获取数据。
我们的 getters
对象需要像这样添加到 cafes
模块中:
getters: {
}
接下来,需要为每一个 state
数据的获取定义一个方法:
getters: {
getCafesLoadStatus( state ){
return state.cafesLoadStatus;
},
getCafes( state ){
return state.cafes;
},
getCafeLoadStatus( state ){
return state.cafeLoadStatus;
},
getCafe( state ){
return state.cafe;
}
}
每个 getter 方法都会传入一个局部模块 state 作为参数并返回相应的 state 数据,这就是 getters
所做的全部工作了!现在我们可以在组件中使用所有这些数据了。
至此,我们的 Vuex 模块已经全部定义好了:
/*
|-------------------------------------------------------------------------------
| VUEX modules/cafes.js
|-------------------------------------------------------------------------------
| The Vuex data store for the cafes
*/
import CafeAPI from '../api/cafe.js';
export const cafes = {
/**
* Defines the state being monitored for the module.
*/
state: {
cafes: [],
cafesLoadStatus: 0,
cafe: {},
cafeLoadStatus: 0
},
/**
* Defines the actions used to retrieve the data.
*/
actions: {
loadCafes( { commit } ){
commit( 'setCafesLoadStatus', 1 );
CafeAPI.getCafes()
.then( function( response ){
commit( 'setCafes', response.data );
commit( 'setCafesLoadStatus', 2 );
})
.catch( function(){
commit( 'setCafes', [] );
commit( 'setCafesLoadStatus', 3 );
});
},
loadCafe( { commit }, data ){
commit( 'setCafeLoadStatus', 1 );
CafeAPI.getCafe( data.id )
.then( function( response ){
commit( 'setCafe', response.data );
commit( 'setCafeLoadStatus', 2 );
})
.catch( function(){
commit( 'setCafe', {} );
commit( 'setCafeLoadStatus', 3 );
});
}
},
/**
* Defines the mutations used
*/
mutations: {
setCafesLoadStatus( state, status ){
state.cafesLoadStatus = status;
},
setCafes( state, cafes ){
state.cafes = cafes;
},
setCafeLoadStatus( state, status ){
state.cafeLoadStatus = status;
},
setCafe( state, cafe ){
state.cafe = cafe;
}
},
/**
* Defines the getters used by the module
*/
getters: {
getCafesLoadStatus( state ){
return state.cafesLoadStatus;
},
getCafes( state ){
return state.cafes;
},
getCafeLoadStatus( state ){
return state.cafeLoadStatus;
},
getCafe( state ){
return state.cafe;
}
}
};
第九步:将 Vuex 模块添加到数据存储器
最后,我们还要告诉 Vuex 数据存储器使用 cafes
模块,打开 resources/assets/js/store.js
文件,紧随
Vue.use( Vuex )
之后添加如下这段代码:
/**
* Imports all of the modules used in the application to build the data store.
*/
import { cafes } from './modules/cafes.js'
并且修改默认导出数据存储器代码如下:
export default new Vuex.Store({
modules: {
cafes
}
});
小结
在这篇教程中,我们创建了一个 Vuex 存储器并为咖啡店配置了一个 Vuex 模块。要想看到对应的效果,编译前端资源后(npm run dev
),可以在开发环境访问 http://roast.test
并打开开发者工具,切换到 Vue 标签页,在 Vuex 部分就可以看到 state
和 getters
属性:
下一篇教程中我们演示如何在 Vue 组件中使用 Vuex 模块。
项目源码位于 Github 上:nonfu/roastapp。
9 Comments
app.js:56072 Uncaught ReferenceError: vue is not defined ,点进去看报错的地方
/**
这里报的错,不懂vue,对着你的代码,还是没找出是什么错
应该是实例化的是Vue,我有个地方小写,显示difined,但是找不到这个小写的在哪。。
感觉重复的代码好多呀 !!!!!!.........
请问为什么不在开头统一引入?而是选择了模块式的引入?仅是为了学习者更好的理解吗?
对 一步一步 演示整个流程
第三步:新增数据存储器到 Vue 实例
多了一个反引号和右括号
http://roast.test这个域名怎么配置的,访问不了
这个超厉害,尤其是js+vue的一步步配置
改成你自己的域名