Skip to content

Latest commit

 

History

History
252 lines (212 loc) · 5.97 KB

README.md

File metadata and controls

252 lines (212 loc) · 5.97 KB

vuex-actions

Action utilities for Vuex, supports promise-based async actions, inspired by redux-actions.

Travis Codecov npm npm JavaScript Style Guide npm

Well tested with vuex@1.0.0-rc.2 and vuex@2.0.0-rc.3, for other versions, use at your own risk 🔴.

npm install --save vuex-actions
import { createAction, handleAction, handleMutations, $inject } from 'vuex-actions'

createAction(type, payloadCreator = Identity)

Wraps a Vuex action so that it has the ability to handle both normal actions and promise-based async actions, commit mutations with the resolved payload created by payloadCreator. If no payload creator is passed, or if it's not a function, the identity function is used. The parameter type is considered as a mutation's name, it will be automatically triggered in the action.

Example:

let increment = createAction('INCREMENT', amount => amount)
// same as
increment = createAction('INCREMENT')

expect(increment).to.be.a('function')

handleAction(handlers)

Wraps a mutation handler so that it can handle async actions created by createAction.

If a single handler is passed, it is used to handle both normal actions and success actions. (A success action is analogous to a resolved promise)

Otherwise, you can specify separate handlers for pending(), success() and error(). It's useful for tracking async action's status.

Example:

const store = new Vuex.Store({
  state: {
    obj: null
  },
  mutations: {
	SINGLE: handleAction((state, mutation) => {
	  state.obj = mutation
	}),
    CHANGE: handleAction({
      pending (state, mutation) {
	    state.obj = mutation
	  },
	  success (state, mutation) {
	    state.obj = mutation
	  },
	  error (state, mutation) {
	    state.obj = mutation
	  }
    })
  }
})

handleMutations(mutations)

Wraps a set of mutations with handleAction. The example above is the same as below

mutations: handleMutations({
  SINGLE: (state, mutation) => {
    state.obj = mutation
  },
  CHANGE: {
    pending(state, mutation) {
      state.obj = mutation
    },
    success(state, mutation) {
      state.obj = mutation
    },
    error(state, mutation) {
      state.obj = mutation
    }
  }
})

Normal actions

const vm = new Vue({
  store,
  vuex: {
    actions: {
      single: createAction('SINGLE')
    }
  }
})

vm.single(1)
expect(store.state.obj).to.equal(1)

vm.single(null)
expect(store.state.obj).to.be.null

vm.single({a: 1})
expect(store.state.obj).to.be.an('object')

// for vuex 2.x, the usage is similar
store.dispatch('single', 1)
expect(store.state.obj).to.equal(1)

Async actions

const vm = new Vue({
  store,
  vuex: {
    actions: {
      change: createAction('CHANGE')
    }
  }
})

Give a promise as payload

vm.change(Promise.resolve(1)).then(() => {
  expect(store.state.obj).to.equal(1)
})

vm.change(Promise.reject(new Error('wow, it\'s rejected'))).then(() => {
  expect(store.state.obj).to.be.an.instanceof(Error)
  expect(store.state.obj.message).to.equal('wow, it\'s rejected')
})

Handle parallel promises in payload

const p1 = new Promise((resolve) => setTimeout(() => resolve(1), 300))
const p2 = new Promise((resolve) => setTimeout(() => resolve(2), 300))

vm.change({
  p1,
  p2,
  other: 3
}).then(() => {
  expect(store.state.obj).to.eql({
    p1: 1,
    p2: 2,
    other: 3
  })
})

Handle rejected promise in payload

const p1 = new Promise((resolve) => setTimeout(() => resolve(1), 100))
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => reject(new Error('Something went wrong')), 100)
})

vm.change({
  p1,
  p2,
  other: 3
}).then(() => {
  expect(store.state.obj).to.be.an('error')
  expect(store.state.obj.message).to.equal('Something went wrong')
})

Using $inject to handle promises (has denpendencies) in sequence

const p1 = Promise.resolve(1)
const p2 = Promise.resolve(2)
const getP3 = p2 => Promise.resolve(p2 + 1)
const getP4 = p3 => Promise.resolve(p3 + 1)
const getP5 = (p3, p4) => Promise.resolve(p3 + p4)
const getP6 = (p4, p5) => Promise.resolve(p4 + p5)

store.dispatch(CHANGE, {
  p1,
  p2,
  p3: $inject(getP3)('p2'),
  p4: $inject(getP4)('p3'),
  p5: $inject(getP5)('p3', 'p4'),
  p6: $inject(getP6)('p4', 'p5'),
  other: 'other'
}).then(() => {
  expect(store.state.obj).to.eql({
    p1: 1,
    p2: 2,
    p3: 3,
    p4: 4,
    p5: 7,
    p6: 11,
    other: 'other'
  })
})

Access origin args in the dependent function

const testArgs = createAction('CHANGE', options => ({
  p1: new Promise((resolve) => setTimeout(() => resolve(1), 10)),
  p2: new Promise((resolve) => setTimeout(() => resolve(2), 20)),
  p3: $inject((p1, p2, options) => {
    expect(p1).to.equal(1)
    expect(p2).to.equal(2)
    expect(options).to.be.an('object')
    expect(options.opt1).to.equal('opt1')
    expect(options.opt2).to.equal('opt2')
    return Promise.resolve(p1 + p2)
  })('p1', 'p2')
}))

testArgs(vm.$store, {
  opt1: 'opt1',
  opt2: 'opt2'
})

Usage with plugin

const store = new Vuex.Store({
  state: {
    obj: null,
    status: ''
  },
  plugins: [
    store => {
      store.subscribe((mutation, state) => {
		// vuex 1.x
        state.status = mutation.payload[0].__status__
		// vuex 2.x
		state.status = mutation.payload.__status__
		
		// status can be one of ['pending', 'success', 'error']
      })
    }
  ]
})