vuex笔记
675635708
8年前
<p>前言:在使用vue开发的时候数据一版通过事件分发的方式进行,在vue1.x中组件中数据交互主要包含有:</p> <ol> <li> <p>子组件事件派发:$emit(向父级),$dispatch(沿着父级向上冒泡)</p> </li> <li> <p>父组件通过$on监听到之后进行相应的操作</p> </li> <li> <p>当有兄弟组件需要监听事件,父组件通过$broadcast向下广播。</p> </li> </ol> <p>vue2.x中取消了$dispatch,$broadcast,要实现组件之前的交互就非常蛋疼了,首先要不停的通过$emit派发到需要获取到这个事件的父级,然后父级通过$ref来调用相应的子组件的方法,单想想就容易心态爆炸。解决办法有2:</p> <ul> <li> <p>在根目录下data里新建一个vue对象作为eventHander,所有的事件通过这个新建的vue对象进行监听派发,具体就不进行描述了送上关键字:this.$root.eventHander.$on、this.$root.eventHander.$emit;</p> </li> <li> <p>全局状态管理工具vuex,什么是vuex?借助官网的一句话:采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。;</p> </li> </ul> <h2>vuex简介</h2> <pre> <code class="language-javascript">借助一张图片,简短的对vuex的状态管理进行一个描述</code></pre> <p style="text-align:center"><img src="https://simg.open-open.com/show/e1da1afa1bca6269e8d223393bb0e753.png"></p> <ul> <li> <p>从上图可以看出vuex主要包含actions、mutations、state</p> </li> <li> <p>交互过程中,渲染:vue components根据state进行渲染,响应:通过触发actions相关事件调用mutations相关函数,mutations对state进行改变,state的改变引发vue components的重绘</p> </li> </ul> <p>状态的统一管理对大型项目提供了相当多的便利,数据的操作都可以通过vuex来进行,小型项目组件并不复杂,数据的交互层级不高,因此并不建议使用vuex进行数据交互</p> <p>在对vuex进行描述之前送上vuex官网 飞机票 一张</p> <h2>核心概念之state</h2> <p>state里存储所有应用层级的信息。是作为唯一的一个数据源存在.</p> <h3>state定义</h3> <pre> <code class="language-javascript">const store = new Vuex.Store({ state: { count: 0 } }) //上面的例子就定义了一个state,state存有一个变量count</code></pre> <h3>state获取</h3> <pre> <code class="language-javascript">//网页直接通过script标签引入的方式不需要进行注册,模块化构建一定要进行Vue.use(Vuex)组件注册 //...state定义 //ps:注意,state相关数据是写在computed内,不是写在data内 new Vue({ el: '#app', template: `<div class="app">{{count}}</div>` computed: { count() { return this.$store.state.count } }, store }) //首先将store注入到组件内部,调用可直接通过this.$store.state获取相应的值 //当数组需要获取多个状态值时this.$store.state前缀就需要写的很多,容易冗余,利用mapState辅助函数可以很好的解决这个问题 import { mapState } from 'vuex' //在直接标签引入的情况下用mapState = vuex.mapState; computed: mapState({ count: state => state.count, countAlias: 'count', // 为了能够使用 `this` 获取局部状态,必须使用常规函数,因为箭头函数会进行this绑定 countPlusLocalState (state) { return state.count + this.localCount } }) //当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组 computed: mapState([ // 映射 this.count 为 store.state.count 'count' ]) //当需要与当前组件属性共同使用的时候,可采用es6/7的对象展开符 computed: { localComputed () { /* ... */ }, // 使用对象展开运算符将此对象混入到外部对象中 ...mapState({ // ... }) }</code></pre> <h2>核心概念之mutations</h2> <p>更改store中的state的唯一方法就是通过mutation,每个mutation都像是一个事件监听,等待调用的观察者。其由一个事件类型和一个回调函数构成,回调函数对state进行修改,事件类型名称作为事件调用的名称;</p> <pre> <code class="language-javascript">//声明 const store = new Vuex.Store({ //... mutations: { increment (state) { // 变更状态 state.count++ } } }) //唤起 store.commit('increment')</code></pre> <h3>提交荷载</h3> <p>提交荷载可以理解为store.commit传递的额外参数</p> <pre> <code class="language-javascript">// ... mutations: { increment (state, n) { state.count += n } } store.commit('increment', 10) //在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读: // ... mutations: { increment (state, payload) { state.count += payload.amount } } store.commit('increment', { amount: 10 }) //对象风格的提交方式 store.commit({ type: 'increment', amount: 10 })</code></pre> <h3>相应规则</h3> <ol> <li> <p>最好提前在你的 store 中初始化好所有所需属性。</p> </li> <li> <p>当需要在对象上添加新属性时,你应该</p> <ul> <li> <p>使用Vue.set(state.obj,'newProp','xxx')</p> </li> <li> <p>state.obj = { ...state.obj, newProp: 123 }</p> </li> </ul> </li> </ol> <h3>使用常量代替mutation事件类型</h3> <p>这么做的好处是对state的操作接口一目了然,全部展现在mutation-types文件夹中,便于大项目的协作。当然这不是必须的。</p> <pre> <code class="language-javascript">// mutation-types.js export const SOME_MUTATION = 'SOME_MUTATION' // store.js import Vuex from 'vuex' import { SOME_MUTATION } from './mutation-types' const store = new Vuex.Store({ state: { ... }, mutations: { // 我们可以使用 ES2015 风格的计算属性命名功能来使用一个常量作为函数名 [SOME_MUTATION] (state) { // mutate state } } })</code></pre> <h3>mutation必须是同步函数</h3> <p>因为异步函数会导致状态的不确定性,造成运行和调试的出路,这里就不进行展开了。</p> <h3>组件的mapMutations</h3> <pre> <code class="language-javascript">import { mapMutations } from 'vuex' export default { // ... methods: { ...mapMutations([ //type1 'increment' // 映射 this.increment() 为 this.$store.commit('increment') ]), //type2 ...mapMutations({ add: 'increment' // 映射 this.add() 为 this.$store.commit('increment') }) } }</code></pre> <h2>核心概念之actions</h2> <p>上一章我们有提到mutation是没有异步操作的,因此异步操作需要用到action,注意action提交的mutation而不是直接处理state</p> <pre> <code class="language-javascript">const store = new Vuex.Store({ //... actions: { //context与store对象具有相同的方法和参数,但。。不是store本身 increment (context) { context.commit('increment') } }, //也可以写成 actions: { increment ({ commit }) { commit('increment') } } })</code></pre> <h3>分发 Action</h3> <pre> <code class="language-javascript">store.dispatch('increment'); //action同样支持荷载方式,和mutation差不多 // 以载荷形式分发 store.dispatch('incrementAsync', { amount: 10 }) // 以对象形式分发 store.dispatch({ type: 'incrementAsync', amount: 10 })</code></pre> <h3>组件中分发 Action</h3> <pre> <code class="language-javascript">import { mapActions } from 'vuex' export default { // ... methods: { ...mapActions([ 'increment' // 映射 this.increment() 为 this.$store.dispatch('increment') ]), ...mapActions({ add: 'increment' // 映射 this.add() 为 this.$store.dispatch('increment') }) } }</code></pre> <h3>组合 Actions</h3> <p>因为action是异步的,因此组合多个action将是一个大问题,要解决这个问题这里引用了promise对象。通过dispath返回的promise对象进行操作,达成顺序执行</p> <pre> <code class="language-javascript">//A进行promise对象返回 actions: { actionA ({ commit }) { return new Promise((resolve, reject) => { setTimeout(() => { commit('someMutation') resolve() }, 1000) }) } } //通过dispatch后的返回值执行异步操作 store.dispatch('actionA').then(() => { // ... }) //actions之间的互相调用 actions: { // ... actionB ({ dispatch, commit }) { return dispatch('actionA').then(() => { commit('someOtherMutation') }) } } //也可以通过async/await属性组合(await只能在async函数内部运行)action: actions: { async actionA ({ commit }) { commit('gotData', await getData()) }, async actionB ({ dispatch, commit }) { await dispatch('actionA') // 等待 actionA 完成 commit('gotOtherData', await getOtherData()) } }</code></pre> <h2>核心概念之getter</h2> <p>有时候我们需要从 store 中的 state 中派生出一些状态(即对state进行一些操作过后得到的状态),例如对列表进行过滤并计数:</p> <pre> <code class="language-javascript">//常规做法,当其他component需要用到时代码就会出现冗余 computed: { doneTodosCount () { return this.$store.state.todos.filter(todo => todo.done).length } } //Vuex 允许我们在 store 中定义『getters』(可以认为是 store 的计算属性)。Getters 接受 state 作为其第一个参数 const store = new Vuex.Store({ state: { todos: [ { id: 1, text: '...', done: true }, { id: 2, text: '...', done: false } ] }, getters: { doneTodos: state => { return state.todos.filter(todo => todo.done) } } }) //Getters 也可以接受其他 getters 作为第二个参数 getters: { // ... doneTodosCount: (state, getters) => { return getters.doneTodos.length } } store.getters.doneTodosCount // -> 1 //组件中使用 computed: { doneTodosCount () { return this.$store.getters.doneTodosCount } }</code></pre> <h3>mapGetters 辅助函数</h3> <p>和之前一样的辅助映射函数一毛一样</p> <pre> <code class="language-javascript">import { mapGetters } from 'vuex' export default { // ... computed: { // 使用对象展开运算符将 getters 混入 computed 对象中 ...mapGetters([ 'doneTodosCount', 'anotherGetter', // ... ]) mapGetters({ // 映射 this.doneCount 为 store.getters.doneTodosCount doneCount: 'doneTodosCount' }) } }</code></pre> <h2>核心概念之module</h2> <p>使用单一状态树,导致应用的所有状态集中到一个很大的对象。但是,当应用变得很大时,store 对象会变得臃肿不堪。</p> <p>Vuex 允许我们将 store 分割到模块(module)。每个模块拥有自己的 state、mutation、action、getters、甚至是嵌套子模块。</p> <h3>module定义</h3> <pre> <code class="language-javascript">//状态篇 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 的状态</code></pre> <h3>module局部状态</h3> <p>对于模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态。</p> <pre> <code class="language-javascript">//即此状态为moduleA的state const moduleA = { state: { count: 0 }, mutations: { increment (state) { // state 模块的局部状态 state.count++ } }, //对于模块内部的 getter,根节点状态会作为第三个参数: getters: { doubleCount (state, getters, rootState) { return state.count * 2 } } }, // 同样,对于模块内部的 action,context.state 是局部状态,根节点的状态是 context.rootState这就是之前action的context不等于当前store对象的原因 actions: { incrementIfOddOnRootSum ({ state, commit, rootState }) { if ((state.count + rootState.count) % 2 === 1) { commit('increment') } } }</code></pre> <h3>命名空间</h3> <p>模块内部的 action、mutation、和 getter 现在仍然注册在全局命名空间——这样保证了多个模块能够响应同一 mutation 或 action。你可以通过添加前缀或后缀的方式隔离各模块,以避免名称冲突。你也可能希望写出一个可复用的模块,其使用环境不可控。例如,我们想创建一个 todos 模块:</p> <pre> <code class="language-javascript">// types.js // 定义 getter、action、和 mutation 的名称为常量,以模块名 `todos` 为前缀 export const DONE_COUNT = 'todos/DONE_COUNT' export const FETCH_ALL = 'todos/FETCH_ALL' export const TOGGLE_DONE = 'todos/TOGGLE_DONE' // modules/todos.js import * as types from '../types' // 使用添加了前缀的名称定义 getter、action 和 mutation const todosModule = { state: { todos: [] }, getters: { [types.DONE_COUNT] (state) { // ... } }, actions: { [types.FETCH_ALL] (context, payload) { // ... } }, mutations: { [types.TOGGLE_DONE] (state, payload) { // ... } } }</code></pre> <p> </p> <p>来自:https://segmentfault.com/a/1190000009108259</p> <p> </p>