vue框架状态管理之vuex

作为一个全沾攻城狮,需要会的东西实在太多了。虽然思路都大同小异,但是对于不同的框架,架构和使用的api都有着不小的区别,还是需要花费点时间学习一下,今天要记录的是便是管理vue状态的框架vuex。

vuex是什么

vuex 是一个状态管理框架,也可以叫做数据共享框架。它能够创建一个独立于组件之外的一个共享数据仓储,将需要的数据放在store当中,当需要用到数据的组件通过this.$store.state.xxx获取 。

不使用vuex的现状

父向子传值: v-bind,子组件通过props接收。

子向父传值: $emit()发射自定义事件, 父组件用v-on监听事件。

不相干组件传值,eventbus实现。 接收方用$on,传递方用$emit()

此种方式只能在小型项目中使用,当需要大量组件需要传值时,会通过其他不相干的组件层层传递,导致组件之间耦合严重,维护相当吃力。

使用vuex有什么好处

vuex创建的state是独立于组件的,能够解藕组件。

集中管理数据,易于维护。

共享数据,存储和获取都非常便捷。

state中的数据都是响应式的,引用state的组件能够实时同步UI

存储原则:一般来说将需要共享的数据放在state中,私有的数据放在组件的data中。但是也可以将所有数据都放在state中,这要看个人和项目组的的开发习惯。

vuex组成部分

解构注意事项: state和getter本质上是属性,所以解构的时候是放在computed中,actions和mutactions是方法,所以解构是要放在methods中。

state(公共数据源,需要共享的数据都放在这里面)

使用的时候: this.$store.state.count (this可以省略)

或者使用mapState解构为computed属性,使用哪种方法都可以

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<template>
	<div>
	{{count}}
	</div>
</template>

<script>
import { mapState } from 'vuex'
export default {

	computed: {
	...mapState([
	'count'
	])
}

}
</script>

mutations(同步的修改state,mutations中不能使用异步操作)

用setTimeoutt等异步操作会出错 ,异步操作需要放在action中

好处:在统一的地方对应,方便管理和维护

1
2
3
4
5
6
add (state) {
state.count++
},
addN (state, n) {
state.count += n
}

使用时

第一种

this.$store.commit('add')

如果想传递参数

this.$store.commit('addN',3)

第二种mapMutations (注意要放在methods中)

1
2
3
import { mapMutations } from 'vuex'  
/// 省略其他内容
methods: {  ...mapMutations([  'add', 'addN'  ])  }  

actions(异步的修改state,它不能直接操作state,需要调用mucations中的方法来操作)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
mutations: {
	add (state) {
	state.count++
	},
	addN (state, n) {
	state.count += n
	}
},
actions: {
addAsync (context) {
	setTimeout(() => {
	context.commit('add')
		}, 1000)
	},
addNAsync (context,n) {
	setTimeout(() => {
	context.commit('addN',n)
		}, 1000)
	}

},

使用时:方式一

写一个方法,通过$store.dispatch这个action,想要传参数和mucation中写法一样

1
2
3
4
5
6
7
8
9
methods: {
	handleAsyncAdd () {
		this.$store.dispatch('addAsync')
	},
	handleAsyncAddN () {
		this.$store.dispatch('addNAsync',4)
	}
	
}

第二种使用方法,解构出来直接使用(注意要放在methods中)

1
2
3
4
5
6
7
8
methods:{
	...mapActions([
	'addAsync',
	'addNAsync'
	]),
}

<div @click="addAsync">

getters(用于对store中的数据进行加工形成新的数据,但不会影响store中的数据)

store的数据变化之后,getter中的数据也会变化

1
2
3
4
5
getters: {
	showNum (state) {
	return `当前最新的数量 ${state.count}`
	}
},

使用时

1
2
3
<div @click="addAsync">
	{{$store.getters.showNum}}
</div>

第二种使用方法(注意要放在computed中)

1
2
3
4
5
computed: {
	...mapGetters([
		'showNum'
	])
}

使用时

1
2
3
<div @click="addAsync">
	{{showNum}}
</div>

modules(对store进行模块划分)

使用场景:当项目组中有多个人同时开发时,如果放在一个文件里面则会出现修改冲突,这里需要使用modules进行文件切分再组合。

目录结构:

image-20201028145155073
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'

import users from './users'
import todos from './todos'

Vue.use(Vuex)

export default new Vuex.Store({
  modules: {
    users,
    todos
  }
})
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// /store/todos/index.js
import todoStates from './todoStates'
import todoActions from './todoActions'
import todoMutations from './todoMutations'
import todoGetters from './todoGetters'

export default {
  namespaced: true,
  state: todoStates,
  mutations: todoMutations,
  actions: todoActions,
  getters: todoGetters
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
//  /store/users.js
import userStates from './userStates'
import userActions from './userActions'
import userMutations from './userMutations'
import userGetters from './userGetters'

export default {
  namespaced: true,
  state: userStates,
  mutations: userMutations,
  actions: userActions,
  getters: userGetters
}

使用的时候,解构的时候写法有区别。commit的时候需要加上模块名

 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
<template>
    <div>
        <button @click="addTodo">add</button>
        <li v-for="todo in todoList" :key="todo.name">
            {{todo.name}}
         </li>
    </div>
</template>
<script>
import { mapState, mapMutations } from 'vuex'

export default {
  computed: {
    ...mapState({
      todoList: state => state.todos.todoList
    })
  },
  methods: {
    ...mapMutations({
      addTodo (commit) {
        commit('todos/addTodo', 'test')
      }
    })
  }
}
</script>

使用注解方式

只有使用ts的环境才能使用注解方式,通过调研有以下两种方案。至于怎么选择,看实际项目的情况下,有的时候不一定有发言权,leader定哪个就用哪个吧。

vuex-module-decorators 用法

yarn add vuex-module-decorators

 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
import { Module, VuexModule, Mutation, Action } from 'vuex-module-decorators'
 
@Module
export default class Counter2 extends VuexModule {
  count = 0
 
  @Mutation
  increment(delta: number) {
    this.count += delta
  }
  @Mutation
  decrement(delta: number) {
    this.count -= delta
  }
 
  // action 'incr' commits mutation 'increment' when done with return value as payload
  @Action({ commit: 'increment' })
  incr() {
    return 5
  }
  // action 'decr' commits mutation 'decrement' when done with return value as payload
  @Action({ commit: 'decrement' })
  decr() {
    return 5
  }
}

vuex-class用法

yarn add vuex-class

 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
import Vue from 'vue'
import Component from 'vue-class-component'
import {
  State,
  Getter,
  Action,
  Mutation,
  namespace
} from 'vuex-class'

const someModule = namespace('path/to/module')

@Component
export class MyComp extends Vue {
  @State('foo') stateFoo
  @State(state => state.bar) stateBar
  @Getter('foo') getterFoo
  @Action('foo') actionFoo
  @Mutation('foo') mutationFoo
  @someModule.Getter('foo') moduleGetterFoo

  // If the argument is omitted, use the property name
  // for each state/getter/action/mutation type
  @State foo
  @Getter bar
  @Action baz
  @Mutation qux

  created () {
    this.stateFoo // -> store.state.foo
    this.stateBar // -> store.state.bar
    this.getterFoo // -> store.getters.foo
    this.actionFoo({ value: true }) // -> store.dispatch('foo', { value: true })
    this.mutationFoo({ value: true }) // -> store.commit('foo', { value: true })
    this.moduleGetterFoo // -> store.getters['path/to/module/foo']
  }
}

参考文章:

Vue & TypeScript 初体验 - 使用Vuex (vuex-module-decorators)

署名 - 非商业性使用 - 禁止演绎 4.0