5
头图
Hello, everyone. I’m Lin Sanxin. Vuex state management mode specially developed for the Vue.js application. It uses centralized storage to manage the state of all components of the application, and uses corresponding rules to ensure that the state changes in a predictable manner.

image.png

When should I use Vuex?

Vuex can help us manage shared state and comes with more concepts and frameworks. This requires a balance between short-term and long-term benefits.

If you don't plan to develop large single-page applications, using Vuex may be tedious and redundant. It is true-if your application is simple enough, you'd better not use Vuex. A simple store mode (opens new window) is enough for you. However, if you need to build a medium and large single-page application, you are likely to consider how to better manage state outside the component, Vuex will become a natural choice. To quote Dan Abramov, the author of Redux:

The Flux architecture is like glasses: you know when you need it.

Review the use of Vuex

Install

Yarn
yarn add vuex
NPM
npm install vuex --save
In a modular packaging system, you must explicitly install Vuex via Vue.use():
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

Register store

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  }
})

new Vue({
  el: '#app',
  store // 注册
})

State

  1. Ordinary use

    const Counter = {
      template: `<div>{{ count }}</div>`,
      computed: {
     count () {
       return this.$store.state.count
     }
      }
    }
    Whenever this.$store.state.count changes, the calculated attributes will be re-calculated and the associated DOM will be updated.
  2. Auxiliary function

    When a component needs to obtain multiple states, declaring these states as calculated properties will be somewhat repetitive and redundant. In order to solve this problem, we can use the mapState auxiliary function to help us generate the calculated attributes, so that you can press the key a few times less:
    // 在单独构建的版本中辅助函数为 Vuex.mapState
    import { mapState } from 'vuex'
    
    export default {
      // ...
      computed: mapState({
     // 箭头函数可使代码更简练
     count: state => state.count,
    
     // 传字符串参数 'count' 等同于 `state => state.count`
     countAlias: 'count',
    
     // 为了能够使用 `this` 获取局部状态,必须使用常规函数
     countPlusLocalState (state) {
       return state.count + this.localCount
     }
      })
    }
    When the name of the mapped calculated attribute is the same as the name of state , we can also pass a string array to mapState.
    computed: mapState([
      // 映射 this.count 为 store.state.count
      'count'
    ])
    Object expansion operator
    computed: {
      localComputed () { /* ... */ },
      // 使用对象展开运算符将此对象混入到外部对象中
      ...mapState({
     // ...
      })
    }

Getters

  1. Ordinary use

    Getter accepts state as its first parameter:
    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)
     }
      }
    })
    Getter can also accept other getters as the second parameter:
    getters: {
      // ...
      doneTodosCount: (state, getters) => {
     return getters.doneTodos.length
      }
    }
    We can easily use it in any component:
    computed: {
      doneTodosCount () {
     return this.$store.getters.doneTodosCount
      }
    }
    Note that getter is cached as part of Vue's responsive system when accessed through attributes. (Similarly to computed , I will post an article to talk about it later)
You can also pass parameters to the getter by letting the getter return a function. It is very useful when you are querying the array in the store.
getters: {
  // ...
  getTodoById: (state) => (id) => {
    return state.todos.find(todo => todo.id === id)
  }
}
store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }
  1. Auxiliary function

    The mapGetters helper function just maps the getters in the store to the local calculated properties:
    import { mapGetters } from 'vuex'
    
    export default {
      // ...
      computed: {
      // 使用对象展开运算符将 getter 混入 computed 对象中
     ...mapGetters([
       'doneTodosCount',
       'anotherGetter',
       // ...
     ])
      }
    }
    If you want to give another name to a getter property, use the object form:
    ...mapGetters({
      // 把 `this.doneCount` 映射为 `this.$store.getters.doneTodosCount`
      doneCount: 'doneTodosCount'
    })

    Muations

  2. Ordinary use

    Mutations in Vuex are very similar to events: each mutation has a string event type (type) and a callback function (handler)
    const store = new Vuex.Store({
      state: {
     count: 1
      },
      mutations: {
     increment (state, n) { // n为参数,可设置,可不设置,此参数也称为“载荷”
       // 变更状态
       state.count++
     }
      }
    })
    // 使用
    this.$store.commit('increment', 10)
  3. Auxiliary function

    import { mapMutations } from 'vuex'
    
    export default {
      // ...
      methods: {
     ...mapMutations([
       'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`
    
       // `mapMutations` 也支持载荷:
       'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`
     ]),
     ...mapMutations({
       add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`
     })
      }
    }
    Mixing asynchronous calls in mutations can make your program difficult to debug. For example, when you call two mutations that include asynchronous callbacks to change the state, how do you know when to call back and which one to call back first? This is why we have to distinguish between these two concepts. In Vuex, mutations are all synchronous transactions

    Action

    Action is similar to mutation, except that:

    • Action submits a mutation instead of directly changing the state.
    • Action can contain any asynchronous operation.
    const store = new Vuex.Store({
      state: {
     count: 0
      },
      mutations: {
     increment (state) {
       state.count++
     }
      },
      actions: {
       // Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象
      incrementAsync (context , n) { // 可传“载荷” n
        setTimeout(() => {
          context.commit('increment') 
        }, 1000)
       }
      }
    })
    // 执行
    // 以载荷形式分发
    store.dispatch('incrementAsync', {
      amount: 10
    })
    
    // 以对象形式分发
    store.dispatch({
      type: 'incrementAsync',
      amount: 10
    })
  4. Auxiliary function

    import { mapActions } from 'vuex'
    
    export default {
      // ...
      methods: {
     ...mapActions([
       'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`
    
       // `mapActions` 也支持载荷:
       'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
     ]),
     ...mapActions({
       add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
     })
      }
    }
  5. Combination Action

    // 假设 getData() 和 getOtherData() 返回的是 Promise
    actions: {
      async actionA ({ commit }) {
     commit('gotData', await getData())
      },
      async actionB ({ dispatch, commit }) {
     await dispatch('actionA') // 等待 actionA 完成
     commit('gotOtherData', await getOtherData())
      }
    }

Module

Due to the use of a single state tree, all the states of the application will be concentrated into a relatively large object. When the application becomes very complex, the store object may become quite bloated.

In order to solve the above problems, Vuex allows us to divide the store into modules. Each module has its own state, mutation, action, getter, and even nested submodules-split in the same way from top to bottom:

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 的状态
For the mutation and getter inside the module, the first parameter received is the local state object of the module.
const moduleA = {
  state: () => ({
    count: 0
  }),
  mutations: {
    increment (state) {
      // 这里的 `state` 对象是模块的局部状态
      state.count++
    }
  },

  getters: {
    doubleCount (state) {
      // 这里的 `state` 对象是模块的局部状态
      return state.count * 2
    }
  }
}
Similarly, for actions inside the module, the local state is exposed through context.state, and the root node state is context.rootState:
const moduleA = {
  // ...
  actions: {
    incrementIfOddOnRootSum ({ state, commit, rootState }) {
      if ((state.count + rootState.count) % 2 === 1) {
        commit('increment')
      }
    }
  }
}
For the getter inside the module, the root node state will be exposed as the third parameter:
const moduleA = {
  // ...
  getters: {
    sumWithRootCount (state, getters, rootState) {
      return state.count + rootState.count
    }
  }
}
The namespace part of the module, I will talk about it later if I have a chance

Simple principle realization

explain

I looked at the Vuex source code files and found that there are indeed a lot of them. Here I will talk about the source code of some of our most commonly used functions.

In fact, students who have used Vuex know that we this.$store.xxx in the page or component. In fact, we only need to store $store variable in the page or component.

Vuex , the principle of is: use 161cd12ab1a331 global mixin Mixin store object you created into each Vue instance, then what is global mixin? for example:

import Vue from 'vue'
// 全局混入
Vue.mixin({
  created () {
      console.log('我是林三心')
  }
})

// 之后创建的Vue实例,都会输出'我是林三心'
const a = new Vue({
  // 这里什么都没有,却能实现输出'我是林三心'
})
// => "我是林三心"
const b = new Vue({
  // 这里什么都没有,却能实现输出'我是林三心'
})
// => "我是林三心"
Anyone who understands the above example will know it. Similarly, replace the console.log('I am Lin Sanxin') with a piece of code that can do this: assign store to the instance's $store attribute , It is achieved:

image.png

Code

content

image.png

  1. vuex.js

    // vuex.js
    let Vue;
    
    // install方法设置,是因为Vue.use(xxx)会执行xxx的install方法
    const install = (v) => { // 参数v负责接收vue实例
     Vue = v;
     // 全局混入
     Vue.mixin({
         beforeCreate() {
             if (this.$options && this.$options.store) {
                 // 根页面,直接将身上的store赋值给自己的$store,
                 这也解释了为什么使用vuex要先把store放到入口文件main.js里的根Vue实例里
                 this.$store = this.$options.store;
             } else {
                 // 除了根页面以外,将上级的$store赋值给自己的$store
                 this.$store = this.$parent && this.$parent.$store;
             }
         },
     })
    }
    
    // 创建类Store
    class Store {
     constructor(options) { // options接收传入的store对象
         this.vm = new Vue({
             // 确保state是响应式
             data: {
                 state: options.state
             }
         });
         // getter
         let getters = options.getters || {};
         this.getters = {};
         console.log(Object.keys(this.getters))
         Object.keys(getters).forEach(getterName => {
             Object.defineProperty(this.getters, getterName, {
                 get: () => {
                     return getters[getterName](this.state);
                 }
             })
         })
         // mutation
         let mutations = options.mutations || {};
         this.mutations = {};
         Object.keys(mutations).forEach(mutationName => {
             this.mutations[mutationName] = payload => {
                 mutations[mutationName](this.state, payload);
             }
         })
         // action
         let actions = options.actions || {};
         this.actions = {};
         Object.keys(actions).forEach(actionName => {
             this.actions[actionName] = payload => {
                 actions[actionName](this.state, payload);
             }
         })
     }
     // 获取state时,直接返回
     get state() {
         return this.vm.state;
     }
     // commit方法,执行mutations的'name'方法
     commit(name, payload) {
         this.mutations[name](payload);
     }
     // dispatch方法,执行actions的'name'方法
     dispatch(name, payload) {
         this.actions[name](payload);
     }
    }
    
    // 把install方法和类Store暴露出去
    export default {
     install,
     Store
    }
  2. index.js

    // index.js
    import Vue from 'vue';
    import vuex from './vuex'; // 引入vuex.js暴露出来的对象
    Vue.use(vuex); // 会执行vuex对象里的install方法,也就是全局混入mixin
    
    // 实例一个Store类,并暴露出去
    export default new vuex.Store({
     state: {
         num: 1
     },
     getters: {
         getNum(state) {
             return state.num * 2;
         }
     },
     mutations: { in (state, payload) {
             state.num += payload;
         },
         de(state, payload) {
             state.num -= payload;
         }
     },
     actions: { in (state, payload) {
             setTimeout(() => {
                 state.num += payload;
             }, 2000)
         }
     }
    })
  3. main.js

    // main.js
    import Vue from 'vue';
    import App from './App.vue'
    
    import store from './store/index'; // 引入刚刚的index.js
    
    
    new Vue({
     store, // 把store挂在根实例上
     el: '#app',
     components: {
         App
     },
     template: '<App/>',
    })
    So far, the state, mutations, getters, and actions of vuex have been simply implemented. In the future, I will have the opportunity to write a special article to realize mudule
Vue does not advocate mixing into mixin globally, even mixin does not advocate using it, don't use it indiscriminately!

Concluding remarks

I am Lin Sanxin, an enthusiastic front-end rookie programmer. If you are motivated, like the front-end, and want to learn the front-end, then we can make friends, fish together haha, fish school, add me, please note [think]

image.png


Sunshine_Lin
2.1k 声望7.1k 粉丝