1 vuex
$ npm i vuex -S
// store.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
updateCount(state, num) {
state.count = num;
}
}
});
export default store;
<template>
<div id="root">
<span>{{count}}</span>
</div>
</template>
<script>
export default {
components: {
Header,
Footer
},
mounted () {
let i = 1
setInterval(() => {
this.$store.commit('updateCount', i++) // 通过组件内部调用方法修改mutations
}, 1000)
},
computed: {
count() {
return this.$store.state.count
}
}
}
</script>
vue 组件是树形结构的,store 只有放在最外层,它内部的子节点才能拿到 store 对象
import Vuex from 'vuex';
export default () => {
return new Vuex.Store({
state: {
count: 0
},
mutations: {
updateCount(state, num) {
state.count = num;
}
}
});
};
// return新的对象,防止栈溢出
1.1 vuex-state
每个数据都要给默认值,不然会导致后续增加这个值的时候,它的数据不是响应式的,不会更新视图。用到的字段一次性声明好。
import Vuex from 'vuex';
import defaultState from './state/state';
import mutations from './mutations/mutations';
export default () => {
return new Vuex.Store({
state: defaultState,
mutations
});
};
这里命名 defaultState 是因为后续服务端渲染有一部分数据直接渲染到客户端,会覆盖 default 数据,default 数据没有跟业务相关的内容,只是默认值。
export default {
count: 0
};
export default {
updateCount(state, num) {
state.count = num;
}
};
1.2 vuex-getter
可以理解为 computed,方便我们生成在应用里可以用的数据,在与后端开发联调的时候,后端数据有时并不适合在 view 层去显示,需要重新组装,这个组装数据需要在多个页面用到的时候用 getter。
import Vuex from 'vuex';
import defaultState from './state/state';
import mutations from './mutations/mutations';
import getters from './getters/getters';
export default () => {
return new Vuex.Store({
state: defaultState,
mutations,
getters
});
};
// state.js
export default {
count: 0,
firstName: 'liu',
lastName: 'sixin'
};
// getters.js
export default {
fullName(state) {
return `${state.firstName} ${state.lastName}`;
}
};
// app.vue
<template>
<span>{{fullName}}</span>
</template>
<script>
export default {
computed: {
fullName(){
return this.$store.getters.fullName
}
}
}
</script>
在组件内这种写法太麻烦,vuex 给我们提供了更简便的写法
// app.vue
<template>
<span>{{fullName}} {{count}}</span>
</template>
<script>
import {
mapState,
mapGetters
} from 'vuex'
export default {
computed: {
...mapState(['count']),
fullName(){
return this.$store.getters.fullName
}
}
}
</script>
但是在 babel 里 env 是不支持…这种语法,
// 可以使用es8或者未定稿语法
npm i babel-preset-stage-1 -D
{
"presets": [
"env",
"stage-1"
],
"plugins": [
"transform-vue-jsx",
"syntax-dynamic-import"
]
}
如果想要不同名的方式
export default {
computed: {
...mapState({
counter: 'count'
}),
}
}
// 以函数的方式
export default {
computed: {
...mapState({
counter: (state) => state.count
}),
}
}
1.3 vuex-mutation
专门用来修改 state 数据,默认只能传 2 个参数,要传多个值,第二个参数设为对象
// app.vue
computed: {
let i = 1;
setInterval(() => {
this.$store.commit('updateCount', {
num: i++,
num2: 2
});
}, 1000);
}
// mutations.js
export default {
updateCount(state, { num, num2 }) {
state.count = num;
}
};
vue 官方推荐所有 state 修改都放在 mutation 方法里,但是不强制,如果要强制的话,设置 strict 为 true(只在开发环境使用)
import Vuex from 'vuex';
import defaultState from './state/state';
import mutations from './mutations/mutations';
import getters from './getters/getters';
const isDev = process.env.NODE_ENV === 'development';
export default () => {
return new Vuex.Store({
strict: isDev,
state: defaultState,
mutations,
getters
});
};
1.4 vuex-action
action 和 mutation 差不多,但是 action 用于异步代码,而 mutation 用于同步代码
// store.js
import Vuex from 'vuex'
import defaultState from './state/state'
import mutations from './mutations/mutations'
import getters from './getters/getters'
import actions from './actions/actions'
export default () => {
return new Vuex.Store({
state: defaultState,
mutations,
getters,
actions
})
}
// actions.js
export default {
updateCountAsync (store, data) {
setTimeout(() => {
store.commit('updateCount', {
num: data.num
})
}, data.time)
}
}
// app.vue
mounted () {
this.$store.dispatch('updateCountAsync', {
num: 5,
time: 2000
})
}
更简洁的写法,建立方法对应关系
// app.vue
import {
mapActions,
mapMutations
} from 'vuex'
methods: {
...mapActions(['updateCountAsync']),
...mapMutations(['updateCount'])
},
mounted () {
let i = 1
this.updateCountAsync({
num: 5,
time: 2000
})
setTimeout(() => {
this.updateCount({
num: i++,
num2: 2
})
}, 1000)
}
1.5 vuex 模块
// store.js
import Vuex from 'vuex'
import defaultState from './state/state'
import mutations from './mutations/mutations'
import getters from './getters/getters'
import actions from './actions/actions'
export default () => {
return new Vuex.Store({
state: defaultState,
mutations,
getters,
actions,
modules: {
a: {
state: {
text: 1
}
},
b: {
state: {
text: 2
}
}
}
})
}
// app.vue
<template>
<div id="root">
<span>{{fullName}} {{counter}} - {{textA}} {{textB}}</span>
</div>
</template>
<script>
computed: {
textA () {
return this.$store.state.a.text
},
textB () {
return this.$store.state.b.text
}
}
</script>
更简洁写法
computed: {
...mapState({
counter: (state) => state.count,
textA: state => state.a.text
})
},
加入 mutation
// store.js
export default () => {
return new Vuex.Store({
modules: {
a: {
state: {
text: 1
},
mutations: {
updateText(state, text){
console.log('a.state', state)
state.text = text
}
}
},
b: {
state: {
text: 2
}
}
}
})
}
// app.vue
<template>
<div id="root">
<span>{{fullName}} {{counter}} - {{textA}} {{textB}}</span>
</div>
</template>
<script>
mounted () {
this.updateText('12345')
},
methods: {
...mapActions(['updateCountAsync']),
...mapMutations(['updateCount', 'updateText'])
},
computed: {
...mapState({
counter: (state) => state.count,
textA: state => state.a.text
})
}
</script>
可以看到 updateText 调用没有像之前在模块内调用,这是因为 vuex 默认会把所有 mutation 放到全局命名当中,如果要加命名空间,加入 namespaced:true
getter 和 mutation 一样
// store.js
export default () => {
return new Vuex.Store({
modules: {
a: {
namespaced: true,
state: {
text: 1
},
mutations: {
updateText(state, text){
console.log('a.state', state)
state.text = text
}
},
getters: {
textPlus (state, getters, rootState) {
// getters 所有的getter方法集合,rootState 全局的state
return state.text + rootState.count
}
}
},
b: {
state: {
text: 2
}
}
}
})
}
// app.vue
<template>
<div id="root">
<span>{{fullName}} {{counter}} - {{textA}} {{textB}}</span>
</div>
</template>
<script>
mounted () {
this.['a/updateText']('12345')
},
methods: {
...mapActions(['updateCountAsync']),
...mapMutations(['updateCount', 'a/updateText'])
},
computed: {
...mapState({
counter: (state) => state.count,
textA: state => state.a.text
})
...mapGetters({
textPlus: 'a/textPlus'
})
}
</script>
在模块内获取全局的 rootState 方法
// store.js
export default () => {
return new Vuex.Store({
modules: {
a: {
namespaced: true,
state: {
text: 1
},
mutations: {
updateText(state, text) {
console.log('a.state', state);
state.text = text;
}
},
getters: {
textPlus(state, getters, rootState) {
// getters 所有的getter方法集合,rootState 全局的state
return state.text + rootState.count;
}
},
actions: {
add(ctx) {
//这里ctx在之前全局声明里拿到的是store对象,在这里是这一个模块的ctx,包含模块方法和rootState
},
// 简洁方法
add({ state, commit, rootState }) {
// 这里commit默认去模块里的mutation找
commit('updateText', rootState.count);
// 要想在全局去找
commit('updateText', { num: 55667 }, { root: true });
}
}
},
b: {
state: {
text: 2
},
actions: {
// 在b模块调用a模块方法,如果加了namespaced,则必须要加 root: true 才能调用
testAction({ commit }) {
commit('a/updateText', 'test test', { root: true });
}
}
}
}
});
};
注意:模块里还可以声明模块,可以无限嵌套
1.6 vuex 动态注册模块
//index.js
Vue.use(Vuex)
const store = createStore()
store.registerModule('c', {
state: {
text: 3
}
})
// app.vue
computed: {
...mapState({
textC: state => state.c.text
})
}
1.7 vuex 热更器
页面在不刷新的情况下更新数据
// store.js
import Vuex from 'vuex';
import defaultState from './state/state';
import mutations from './mutations/mutations';
import getters from './getters/getters';
import actions from './actions/actions';
export default () => {
const store = new Vuex.Store({
state: defaultState,
mutations,
getters,
actions
});
if (module.hot) {
module.hot.accept(
[
'./state/state',
'./getters/getters',
'./mutations/mutations',
'./actions/actions'
],
() => {
const newState = require('./state/state').default;
const newGetters = require('./getters/getters').default;
const newMutations = require('./mutations/mutations').default;
const newActions = require('./actions/actions').default;
store.hotUpdate({
state: newState,
getters: newGetters,
mutations: newMutations,
actions: newActions
});
}
);
}
return store;
};
1.8 vuex 其它 API
//index.js
Vue.use(Vuex);
const store = createStore();
store.registerModule('c', {
state: {
text: 3
}
});
//unregisterModule 解绑动态注册模块
store.unregisterModule('c');
// store.watch
// 第一个参数为state,监听得到state的返回值,第二个参数返回一个方法,当第一个参数有变化的时候调用第二个方法作为回调。相当于store里的getter方法。
store.watch(state => state.count + 1, () => {});
// store.subscribe
// 拿到所有mutation状态,有一个mutation变化,就会被调用
store.subscribe((mutation, state) => {
console.log(mutation.type);
console.log(mutation.payload);
});
// store.subscribeAction
// 拿到所有action状态,有一个action变化,就会被调用
store.subscribeAction((action, state) => {
console.log(action.type);
console.log(action.payload);
});
这两个方法一般用于 vuex 插件
1.9 vuex 插件
import Vuex from 'vuex';
import defaultState from './state/state';
import mutations from './mutations/mutations';
import getters from './getters/getters';
import actions from './actions/actions';
export default () => {
const store = new Vuex.Store({
state: defaultState,
mutations,
getters,
actions,
plugins: [
store => {
// store.subscribe
}
]
});
};
1.10 vuex 总结
整个应用加入了 vuex 之后:
- index.js 顶部声明 root -> new Vue()
- 通过 vue 实例把整个应用都挂载 到 root 节点上,这个应用渲染的是 App 这个节点
new Vue({
router,
store,
render: h => h(App)
}).$mount('#root');
- 在 App 里承担了一个路由操作,有多个路由子节点,每个子节点分别渲染不同的 vue 组件。整个 vue 应用就是节点的树形,通过一层层向下渲染,最终 渲染成 html 节点。
- 有一个独立于 vue 节点树的 store,类似于一个数据库,store 通过在声明 vue 对象的时候 注入到整个 组件树,注入之后 App,及 所有节点都可以通过 $store 对象调用 store 内容。
- 在节点内就可以通过 dispatch 或 commit 分别调用 mutation 或 action,以这种方式让我们在节点里可以修改 store 数据。但是要注意,真正的修改是在 store 里去做,而不是 dispatch 在节点 里去操作。