FrankKai.github.io
FrankKai.github.io copied to clipboard
Vuex那些事儿
用vuex也有一段时间了,但仅仅是很基础的用,没有学习高级特性,更没有深入过源码 由于手上负责的聊天项目较为复杂,深入学习vuex更显得很有必要,从而优化聊天部分的前端实现
基于以上目的,我将在这里记录自己在实践过程中的一些总结。
- mapState与mapGetter的区别是什么?
- Vuex的plugins有什么用?
- this.$store.commit()和mapMutation()哪种更好?
- mutation与action区别是什么?
- Modules的namespacing是什么操作?
- 一次vuex的状态变更经历哪些步骤?
- Vuex是如何与Vue框架做双向数据绑定的?
mapState与mapGetter的区别是什么?
我们都知道,这是我们从vuex查询数据的两种方式,那这两者到底有什么区别呢? 直接看二者的定义:
// https://vuex.vuejs.org/api/#mapstate
mapState(namespace?: string, map: Array<string> | Object<string | function>): Object
// https://vuex.vuejs.org/api/#mapgetters
mapGetters(namespace?: string, map: Array<string> | Object<string>): Object
如果看定义看不懂的话,可以移步TypeScript入门,或者到TypeScript官网自己学习,了解TypeScript中的Optional Property和strictNullChecks。 二者的第一个参数都是可选的命名空间,第二个是数组或者对象的map。
-
mapState
一方面获取全局状态,一方面辅助我们生成计算属性。 -
mapGetters
仅仅是获取全局状态,通过将store中的getter映射过来。
mapState:
mapState(['bar','baz']) // 相当于state.bar
mapState({
foo: 'foo',
bar: state => state.bar,
baz(state) {
return state.baz + 'hhh'; // 用做计算
}
})
mapState('foo',['bar','baz']) // 这种形式不常用
mapGetters:
mapGetters(['bar', 'baz']) // 直接映射
mapGetters({
barBuddy: 'bar', // 重命名映射
bazBuddy: 'baz',
})
其实mapState和mapGetters的真正区别在于:
- mapState在组件层,整合组件资源,进行个性化操作,也就是某个.vue。适用于获取纯状态树上的原始数据,可能在每个页面都要再进行计算。
- mapGetters在store层,整合store层资源,进行个性化操作,也就是getters.js。适于获取状态树上的处理后的数据,不需要在每个页面进行计算,store层直接算好了。
Vuex的plugins有什么用?
暴露每个mutation的hooks,只接收一个store(初始化好的store)为参数。
- Plugin内部提交mutation
- 获取状态快照
- Built-in Logger插件
最简单的开发和注入:
const myPlugin = store => {
store.subscribe((mutation, state) => { })
}
const store = new Vuex.Store({
plugins: [myPlugin]
})
又见subscribe,这仍然是基于事件的"发布订阅者"编程模型,关于发布订阅者模型,可以参考webhook到底是个啥?
Plugin内部提交mutation
Plugins不能直接修改状态--这和我们的组件一样,可以通过commit mutation触发更改。 通过commit mutation,插件可以同步数据到store。例如sync一个websocket数据源到store,这个contrived example里, 函数里可以执行很多更加复杂的任务:
export default function createWebSocketPlugin (socket) {
return store => {
socket.on('data', data => {
store.commit('receiveData', data)
})
store.subscribe(mutation => {
if (mutation.type === 'UPDATE_DATA') {
socket.emit('update', mutation.payload)
}
})
}
}
const plugin = createWebSocketPlugin(socket)
const store = new Vuex.Store({
state,
mutations,
plugins: [plugin]
})
获取状态快照
state的"snapshots"指的是,对mutation的状态做保留,例如当前的post-mutation state和pre-mutation state做对比。可以通过深拷贝实现:
const myPluginWithSnapshot = store => {
let prevState = _.cloneDeep(store.state)
store.subscribe((mutation, state) => {
let nextState = _.cloneDeep(state)
// compare `prevState` and `nextState`...
// save state for next mutation
prevState = nextState
})
}
snapshot适合在development阶段使用。当结合webpack或者Browserify,我们可以让构建工具处理:
const store = new Vuex.Store({
plugins: process.env.NODE_ENV !=='production' ? [myPluginWithSnapshot] : []
})
此处的环境变量的获取,需要用到webpack的DefinePlugin 这个webpack内置插件。关于DefinePlugin,可以参考Webpack Plugin那些事儿
Built-in Logger插件
用于在控制台输出store的mutation信息,其实也是一种state snapshot。 信息内容包括:
- mutation信息
- mutation 前的state
- mutation 后的state
相比vue-devtools,有以下优势:
- 可以直接在console查看
- 可以查看特定的mutation,在filter中
- 可以输出特定的state,在transformer函数中
- 可以选择性输出matation的type,payload,在mutationTransformer中
/store/logger.js
import createLogger from 'vuex/dist/logger';
const subscribedMutations = ['USER_INIT', 'DEVICE_EXPEND_STATUS'];
const logger = createLogger({
collapsed: false,
filter: (mutation) => subscribedMutations.includes(mutation.type),
transformer: (state) => state,
mutationTransformer: (mutation) => mutation,
logger: console,
});
export default logger;
/store/index.js
import logger from './logger';
export default new Vuex.Store({
plugins: [logger],
})
日志输出就像下面这样:
注意:logger plugin仅仅用于development环境,因为它也本身属于state snapshot。
this.$store.commit()和mapMutation()哪种更好?
当然是mapMutation,可以统一查看当前组件有多少mutation类型,但是需要注意命名不能和组件方法冲突。
import { mapMutations } from 'vuex'
export default {
// ...
methods: {
...mapMutations([
'increment', // 不带payload映射,map `this.increment()` to `this.$store.commit('increment')`
'incrementBy' // 带payload映射,map `this.incrementBy(amount)` to `this.$store.commit('incrementBy', amount)`
]),
...mapMutations({
add: 'increment' // 重命名: map `this.add()` to `this.$store.commit('increment')`
})
}
}
直接使用this.$store.commit():
methods: {
foo() {
this.$store.commit('USER_INIT');
}
...
...
bar() {
this.$store.commit('USER_UPDATE');
}
}
当业务逐渐变复杂时,foo和bar之间会相隔很远,很难一目了然看清当前组件用到了哪些mutation。 使用mapMutatinos:
methods: {
...mapMutations([
'USER_INIT',
'USER_UPDATE',
]),
foo() {
this['USER_INIT']();
}
...
bar() {
this['USER_UPDATE']();
}
}
很明显,这样很清晰。
mutation与action区别是什么?
- mutation必须同步进行,action可以异步进行。
- action可以调用异步api,可以分发多重mutation。
- action可以返回promise,保证异步请求的顺序,一个action执行完再触发第二个action。 2019.10.4更新
- action是为了异步请求后在action内部调用其他mutation。
mutation可以结构多个入参数吗?
当然,加个判断就好了。
Modules的namespacing是什么操作?
- 默认情况下,actions,mutations和getters是注册在global namespace的,这样设计的目的是多个module可以对同一mutation或者action做出响应。(据我使用两年vuex的经验,基本不会有这种操作,都是各模块管各模块的。)
- namespaced: true可以使得模块更加自容器化和可重用,开启namespaced后,所有的getters,actions,mutations都会自动添加路径。
- namespaced开启后,可以使用本地化的getters, dispatch和commit,单个module中的资源不需要增加前缀就可以访问。
modules: {
account: {
namespaced: true,
// module assets
state: { ... }, // module state is already nested and not affected by namespace option
getters: {
isAdmin () { ... } // -> getters['account/isAdmin']
},
actions: {
login () { ... } // -> dispatch('account/login')
},
mutations: {
login () { ... } // -> commit('account/login')
},
},
}
- 获取全局的state,getter,mutation,action。getters可以通过rootState, rootGetters获得;mutation和action可以通过dispatch,commit的
{root: true}
去获取全局的mutation和action。
modules: {
foo: {
namespaced: true,
getters: {
// `getters` is localized to this module's getters
// you can use rootGetters via 4th argument of getters
someGetter (state, getters, rootState, rootGetters) {
getters.someOtherGetter // -> 'foo/someOtherGetter'
rootGetters.someOtherGetter // -> 'someOtherGetter'
},
someOtherGetter: state => { ... }
},
actions: {
// dispatch and commit are also localized for this module
// they will accept `root` option for the root dispatch/commit
someAction ({ dispatch, commit, getters, rootGetters }) {
getters.someGetter // -> 'foo/someGetter'
rootGetters.someGetter // -> 'someGetter'
dispatch('someOtherAction') // -> 'foo/someOtherAction'
dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction'
commit('someMutation') // -> 'foo/someMutation'
commit('someMutation', null, { root: true }) // -> 'someMutation'
},
someOtherAction (ctx, payload) { ... }
}
}
}
- 可以在module内部,通过
{root: true}
注册global action。(这种骚操作应用场景少吧。) - 使用namespace绑定helpers(mapState, mapGetters, mapActions和mapMutations),更高级的是用createNamespacedHelpers。
import { createNamespacedHelpers } from 'vuex'
const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')
export default {
computed: {
// look up in `some/nested/module`
...mapState({
a: state => state.a,
b: state => state.b
})
},
methods: {
// look up in `some/nested/module`
...mapActions([
'foo',
'bar'
])
}
}
一个组件可能需要多个module的state,mutation和action,可以使用多个mapState么?亲测可以。
...mapState({
user: 'user',
}),
...mapState({
Foo: 'device',
}),
上述内容完全可以满足需求,其他内容暂时不用涉猎。
一次vuex的状态变更经历哪些步骤?
组件内的方法 commit (1.判断是否当前类型的mutation2.若有触发mutation)
Vuex是如何与Vue框架做双向数据绑定的?
使用了Vue实例作为全局状态树_vm($$state) deep watch所有属性