Multiple Vue instances (components) needs single source of truth.
const sourceOfTruth = {}
const vmA = new Vue({
data: sourceOfTruth
})
const vmB = new Vue({
data: sourceOfTruth
})
source of truth is the raw 'data' object - Vue instance proxies access to it
if multiple Vue instances (multiple components) shares the same state object, can achieve single source of truth
however, debugging nightware - any piece of data could be changed by any part of app with no trace
an store object
all action that mute the store's state are put in store - as methods
so mutation is controlled and can be logged / checked
var store = {
debug: true,
state: {
message: 'Hello!'
},
setMessageAction (newValue) {
if (this.debug) console.log('setMessageAction triggered with', newValue)
this.state.message = newValue
},
clearMessageAction () {
if (this.debug) console.log('clearMessageAction triggered')
this.state.message = ''
}
}
Sharing state
var vmA = new Vue({
data: {
privateState: {},
sharedState: store.state
}
})
var vmB = new Vue({
data: {
privateState: {},
sharedState: store.state
}
})
vuex - like redux with knowledge of Vue
Interactions: see title diagram
Global singleton, centralized store for all components, with rules ensuring predicable mutation, and work with tools for advanced features. Component tree becomes big view. Reactive, in same way as Vue instance, also subject to same restriction. Cannot directly change store's state. Need to explicitly commit mutations.
Trade off: more concepts and boilerplate (than simple solutions above)
Store: reactive state, only changed by committing mutations
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
}
})
Trigger change
store.commit('increment')
// reason for the indirect committing is for tracing logging etc
console.log(store.state.count) // -> 1
Conventionally, under path /store:
index.js - assemble the store and export the store
actions: asynchronous logics
mutations: synchronous and the only places where change happens
modules: for non-trivial app
single state tree - all application state and truth
use state:
As computed state of Vue instance - requires importing and store in every component and write computed state, too much boilerplate codes
"Inject" into root, after "Vue.use(Vuex)", when creating the root component, provide the "store" option and the store will be injected to all child components, accessible as "this.$store". See https://vuex.vuejs.org/guide/state.html
Access state's properties:
direct reference: {{this.$store.state.counter}}
with mapState: import {mapState} from 'vuex'... computed: mapState({...
Lambda syntax: countLambda: state => state.counter,
Alias: countAlias: 'counter',
Same name: 'count'
Object spread operatior: ...mapState({...}) allow member spread into outer object
for derivative values
cached based on dependencies (like computed properties)
define:
getters: { something: (state, getters) =>{return...}} // can derive from getters as well
queryById: (state)=> (id)=>{ return...} // return a function which accept parameters, act like a query
Access:
this.$store.getters.something
this.$store.getters.queryById(someId)
use mapGetters: import {mapGetters} from 'vuex'... export default{ ... computed: { ...mapGetters(['something',...
Note: accept array or object, if all same name then array, otherwise object
Mutation is like event, "mutations: {someMutation(state, payload){..." is like event register. Cannot directly call mutation method. Call store.commit('someMutation') to trigger it. Payload can be passed. In most cases payload should be an object containing multiple fields (better for recording).
Commit syntax:
store.commit('someMutation', payload}
store.commit({type:'someMutation', payloadProperty:payloadValue})
Common practice: use a mutation-types.js file and export constants to allow tools to work and also as a reference.
Mutations must be synchronous for tools to work well. Tools will record before / after commit state. Asynchronous change will make change lose track. The point is to make all changes to store state in mutations so it could be tracked.
Helper: mapMutations map mutations into methods, use array for same names, object for different name
methods:{ ...mapMutations([...]), ...mapMutations({methodName:'mutationName'}) }
Reason behind mutation and actions:
Mutation - mutations are synchronously, and are the only places where state change happens, so state change is tracked; mutation cannot be asynchronous otherwise would be untrackable.
Action - where asynchronous operations happen and to commit mutations
Action methods:
can be function or async function
receive (context):
exposes same set of methods / properties of the state instance
call context.commit to commit mutation
context.state & context.getters
but context is NOT the state instance:
return:
if return Promise, then dispatch('xxx') also returns Promise
Trigger action:
this.$store.dispatch('actionName', optionalPayload)
mapActions helper: methods: {...mapActions(['action1','action2']), ...mapActions({method1:'action1'})
One store, can have multiple modules, each has state/mutations/actions/getters/&even nested modules.
Define:
define each module as an object
the store as new Vuex.Store({modules:{ a: moduleAObject, b:moduleBObject}}
Access:
State: store.state.a // module A's state
Mutation & Getters:
first argument (state) is the module's local state
getterMethod(state, getters, rootState) - root state is 3rd argument
Actions:
context.state // the local state
context.rootState // the root state
The helpers under module:
mapState: cannot use string, need to use lambda
mapGetters: 'some/module/getterName'
See document
Plugins are:
function
(store)=>{// do something}
Can call store.subscribe( (mutation, state)=>{})
register a callback
mutation.type - the type
mutation.payload - payload
be registered to store when creating plugins: [plugin1,...]
Should not directly change state, can commit mutations
Usage: for example listen to change and sync remote data
Built-in plugin:
Logger
strict : true
When enabled:
Error thrown if mutated outside
NOT FOR production
UI should not directly change state. So:
UI event handler commit changes
Use two way computed property with getter and setter, and find it with v-model
https://medium.com/dailyjs/mastering-vuex-zero-to-hero-e0ca1f421d45
Despite of whatever said in documents and other people's articles, here's what I learnt by playing with Vuex:
Get ad Set: some article suggest states are read-only, writing getters for everything, lots of boilerplates. Found: getters and setters are already provided, automatically, for every state and embedded states. ("this.$store.state.whateverState"). Maybe I should only write getter for computed values?