目录
Vuex
vuex基础
阶段小结(vuex的四种对象)
vuex响应式原理
vuex.js的实现
index.js
Module
局部状态
命名空间
跨模块访问数据
小结
目标
- Vuex相关基本概念和使用方式
- vuex响应式原理过程
- vuex中如何进行模块式开发
Vue2.0是单向数据流的
Vuex
vuex基础
Vuex使用单一的Store状态树,包含全部层级状态
const store = new Vuex.Store({
state: { // 状态值
count: 0
},
getter: {// 衍生值, 计算属性,变化后会再计算
doubleCount: state => state.count * 2
},
mutations: { // 变更state属性,同步函数
addCount (state,payload=1) {
state.count += payload
}
},
actions: {// 异步操作
asyncAddCount({commit}) { // 结构获得commit方法
setTimeout(() => {
commit('addCount');// 通知状态变更 count++
},1500);
}
}
})
cmd指令 :vue add vuex
快速在vue工程中添加vuex依赖
// 父组件简写
{{count}}{{count2}}{{doubleCount}}
import {mapState, mapGetters, mapMutations, mapActions} from "vuex"
// map方法,快速开发
export default {
name: "App",
components: {
HelloWord
},
computed: {
// ...mapState(["count"])// 映射下面
// count() {
// return this.$store.state.count;
// },
// ---------------------------------
...mapGetters(["doubleCount"])// 映射下面
// doubleCount() { // 派生属性
// return this.$store.getters.doubleCount;
// },
// ---------------------------------
...mapState({ // 映射
count: state => state.count, // 自定义变量
count2: state => state.count // 别名修改
}),
},
methods: {
...mapMutations(["addCount"]),// 对象扩展语法 @click="addcount(2)"
//addCount() {
// this.$store.commit("addCount", 3) // mutations
//},
// ---------------------------------
...mapActions(["asyncAddCount"]),// 对象扩展语法
//asyncAddCount() {
// // dispatch("【actions里面约定的函数】asyncAddCount") 异步
// this.$store.dispatch("asyncAddCount"); // actions 1.5s后操作变更
}
}
}
vuex大大简化了组件通信的过程
// 子组件简写
<button @click="addCount(2)">addCount</button>
<button @click="asyncAddCount">asyncAddCount</button>
import {mapMutations, mapActions} from "vuex"
methods: {
...mapMutations(["addCount"]),// 对象扩展语法 @click="addcount(2)"
//addCount() {
// this.$store.commit("addCount", 3) // mutations
//},
// ---------------------------------
...mapActions(["asyncAddCount"]),// 对象扩展语法
//asyncAddCount() {
// // dispatch("【actions里面约定的函数】asyncAddCount") 异步
// this.$store.dispatch("asyncAddCount"); // actions 1.5s后操作变更
}
}
阶段小结(vuex的四种对象)
- State【单一Store、响应式】
- Getter【由state派生出的状态、store的计算属性】
- Mutations【由commit触发、必须是同步函数】
- Actions【由dispatch触发、可以包含任意异步操作、不直接变更状态,可以通过mutation变化】
Mutations—修改—>State—响应式—>Vue Component【响应式变化,进行渲染】
vuex响应式和vue响应式串联,使vuex里的变化同步到vue组件中
vuex响应式原理
vuex.js的实现
import { reactive } from "./index.js"; // 响应式劫持
export class Store { // export 暴露Store
// 构造器
constructor(options = {}) {
let { state, mutations, plugins, actions, getter } = options;// plugins插件
this._vm = reactive(state);// reactive响应式劫持
this._mutations = mutations;
this._subscribe = [] // 订阅函数队列
// 初始化之后
// 遍历,
plugins.forEach(plugin => {
plugin(this) // 每个插件执行一下,把Store传进去
})
}
get state() {
return this._vm;
}
commit(type, payload) {// 执行_muations的type函数
const entry = this._muations[type];// 通过枚举取出 this._muations[type]
if(!entry) {
return;
}
entry(this.state, payload);
this._subscribe.forEach(sub => sub({type, payload}, this.state));
}
subscribe(fn) {
if(!this._subscribe.includes(fn)) {// 判断fn不在订阅队列里
this._subscribe.push(fn);
}
}
}
index.js
let active;
let watch = function(cb) {
active = cb;
active();
active = null;
};
let queue = [];
let nextTick = cb => Promise.resolve().then(cb);
let queueJob = job => {
if (!queue.includes(job)) {
queue.push(job);
nextTick(flushJobs);
}
};
let flushJobs = () => {
let job;
while ((job = queue.shift()) !== undefined) {
job();
}
};
class Dep {
constructor() {
this.deps = new Set();
}
depend() {
if (active) {
this.deps.add(active);
}
}
notify() {
this.deps.forEach(dep => queueJob(dep));
}
}
let ref = initValue => {
let value = initValue;
let dep = new Dep();
return Object.defineProperty({}, "value", {
get() {
dep.depend();
return value;
},
set(newValue) {
value = newValue;
dep.notify();
}
});
};
let createReactive = (target, prop, value) => {
let dep = new Dep();
// return new Proxy(target, {
// get(target, prop) {
// dep.depend();
// return Reflect.get(target, prop);
// },
// set(target, prop, value) {
// Reflect.set(target, prop, value);
// dep.notify();
// },
// });
return Object.defineProperty(target, prop, {
get() {
dep.depend();
return value;
},
set(newValue) {
value = newValue;
dep.notify();
}
});
};
export let reacitve = obj => {
let dep = new Dep();
Object.keys(obj).forEach(key => {
let value = obj[key];
createReactive(obj, key, value);
});
return obj;
};
// let data = reacitve({
// count: 0
// });
import { Store } from "./vuex";
let store = new Store({
state: {
count: 0
},
mutations: {
addCount(state, payload = 1) {
state.count += payload;
}
},
plugins: [// 定义一个插件
function(store) {
// 订阅mutation, state
store.subscribe((mutation, state) => {
console.log(mutation);// 监听mutation变化,如日志上报
})
},
store =>
store.subscribe((mutation, state) => {
console.log(mutation);
})
]
});
document.getElementById("add").addEventListener("click", function() {
// data.count++;
store.commit("addCount", 1);
});
let str;
watch(() => {
str = `hello ${store.state.count}`;
document.getElementById("app").innerText = str;
});
Module
因为使用了单一对象树,应用的所有状态都会集中在一个比较大的对象当中,应用变的复杂时,Store对象就会变的非常臃肿
为了解决上面的问题,Vuex允许我们将Store分割成模块
const moduleA = {
state: {},
mutations: {},
actions: {},
getters: {}
}
const moduleB = {
state: {},
mutations: {},
actions: {}
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
局部状态
const moduleA = {
state: { count: 0 },
mutations: {
increment (state) {
//这里的`state`对象时模块的局部状态
state.count++
}
},
actions: {
doubleCount (state, getters) {
return state.count * 2
}
},
getters: {
incrementIfOddOnRootSum ({state, commit}) {
if(state.count%2 === 1) {
commit('increment')
}
}
}
}
命名空间
默认情况下,模块内部action、mutation和getter是注册在全局命名空间的,这就导致不同的module中间出现命名冲突问题const
const store = new Vuex.Store({
modules: {
moduleA: {
namespaced: true, // 命名空间开启
state: { count: 0 },
mutations: {
increment (state) {// 使用时->mutations['moduleA/increment']
//这里的`state`对象时模块的局部状态
state.count++
}
},
actions: {
doubleCount (state, getters) {// actions['moduleA/doubleCount']
return state.count * 2
}
},
getters: {
incrementWhenOdd ({state, commit}) {// getters['moduleA/incrementWhenOdd']
if(state.count%2 === 1) {
commit('increment')
}
}
}
}
}
})
跨模块访问数据
modules: {
foo: {
namespaced: true,
getters: {
// 在中国模块的getter中,`getters`被局部化了
// 你可以使用getter的第4个参数来调用`rootGetters`
someGetter(state, getters, rootState, rootGetters) {
getters.someOtherGetter// -> 'foo/someOtherGetter' foo命名空间下的方法
rootGetters.someOtherGetter// -> 'someOtherGetter' 根模块的方法
},
someOtherGetter: state => {...}
},
actions: {
// 在这个模块中,dispatch和commit也被局部化了
// 它们可以接受`root`属性以访问根dispatch或commit
someAction({dispatch,commit,getters,rootGetters}) {
getters.someGetter // -> 'foo/someGetter' foo命名空间下的方法
rooGetters.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) {...}
}
// Mutations 在设计时候,不会暴露rootState,rootGetters,Mutation都是易于测试的同步纯函数,
// 所以获取rootState这种具有副作用的参数,统一需要大家放在action里面操作
// 在action里去拼接rootState和rootGetters和当前的state
// 在通过commit的纯函数去修改我们的state
}
}
小结
- vuex响应式源码分析
- 如何去通过vuex的插件机制去订阅mutation的改变
- 在vuex当中模块化设计module