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 在节点  里去操作。