Cheat sheet
- dispatch actions, commit mutations
Concepts
- State management library
- Extract shared state out of components and manage it in a global singleton
- Use when you do a lot of passing data from one component to another through props.
- When you don't want your data all over the place
- Vuex stores are REACTIVE, meaning when vue components retrieves state from it, they will reactively and efficiently update the store's state changes.
- You cannot directly mutate the store's state. The only way to change a store's state is by explicitly committing mutations. Can log every mutation, take state snapshots, or perform time travel debugging.
- Modules - encapsulating/separating out logic for different components
- E.g., whenever
store.state.count
changes, it will cause the computed property to re-evaluate, and trigger associated DOM updates.
- By using
Vue.use(Vuex)
and passing the store
augument to the root instance, any component can get at the store by calling this.$store
Flux pattern concepts
- flux pattern concept created by Facebook
- All you technically need is an event system
- Unidirectional dataflow, because all actions go through the same dispatcher
- All components will know when data has changed and con update appropriately
- All starts with an action
- Calls a dispatcher to propagate an event
- Puts the result into a store
- Store emits an event to tell components that are subscribed to it that the data has changed
- The views consume the data from the store
- Many implementations. Redux is another example of Flux pattern but for React framework
Four types of entities in the modules
state
- put data members in here as an object
getters
- When you need to compute a derived state based on a store state.
- all getters go in computed properties.
- Getters are like computed properties for stores: will only re-evaluate when some of its dependencies have changed.
- Get things out of the store with these methods, otherwise you will need a computed property in your component that pulls directly:
this.$store.state.foo
- getter methods take a single argument
state
- Can also pass arguments to getters by returning a function
getTodoById: (state) => (id) => { return state.todos.find(todo => todo.id === id)}
- Can use getters via
this.$store.getters.doneTodos
or mapGetters helper
mutations
- always synchronous - you can't capture the "before" and "after" snapshots if mutations are asynchronous.
- list out the different possible ways the state can change.
- Vuex mutations are similar to events.
- Each mutation has a string type and a handler.
- Mutation handler methods takes one or two arguments,
state
, and optionally a payload
which can be a scalar or an object.
- YOU CANNOT DIRECTLY CALL A MUTATION HANDLER, instead invoke
store.commit
actions
- Similar to mutations except:
- Instead of mutating the state, they commit the mutations
- Contains arbitrary asynchronous operations.
- Called from components to commit a mutation
- Can be synchronous OR synchronous
- Can do multiple mutations at once
- All actions do is call the dispatcher with some parameters and an operation
- Actions method take two arguments
context
(similar to state
object, which exposes the same set of properties/methods on the store instance)
- Use ES2015 argument destructuring to pull a member out of an object so you don't have to keep using . to call a method on an object
payload
- Can daisy chain, a.k.a "compose" actions
actions: {
async actionA ({ commit }) {
commit('gotData', await getData())
},
async actionB ({ dispatch, commit }) {
await dispatch('actionA') // wait for `actionA` to finish
commit('gotOtherData', await getOtherData())
}
}
Vocabulary
- Dispatch actions versus commit mutations
const jokes = await fetch( 'jokes.json' )
- Commit a mutation
Vuex helper methods
- helper methods return objects
- Use the elipsis
...
"object spread operator" from ES2015 (Python equivalent is **
) if we ever want to use helper methods in combination with other local methods.
mapState
import { mapState } from 'vuex'
- FOUR ways to map store state to component state
- Arrow function:
count: state => state.count
- Map a property to a string if you want to rename
countAlias: 'count'
- Or just pass the string
- A normal function to mix local "this" state with store state:
countPlusLocalState (state) {return state.count + this.localCount}
mapGetters
- Put inside components
computed
properties
mapMutations
- Put inside component's
methods
properties
- Example: map `this.increment()` to `this.$store.commit('increment')`
methods: { ...mapMutations([ 'increment', 'incrementBy']) }
mapActions
- Like
mapMutations
, put inside component's methods
properties
- Example: map `this.increment()` to `this.$store.dispatch('increment')`
methods: { ...mapActions([ 'increment', 'incrementBy']) }
Code
App.vue
const store = new Vuex.Store({
})
store/index.js
import Vuex from 'vuex';
import ModuleName from './modules/ModulName'
// Load Vuex
Vue.use( Vuex );
// Create store
export default new Vuex.store( {
modules: {
ModuleName
}
}
);
Basic Directions
- Use <router-link> and <router-view> tags inside your component
- Import route components
- Define routes and map components to routes (each object is called a "route record")
- Instantiate router instance and pass the routes to the constructor
- Inject the router to make the whole app router aware
Knobs, dials, and best practices
- Now you get
this.$router
and this.$route
inside of the component
router-link
automatically gets the .router-link-active
class when target route is matched.
- Clicking on a
router-link
is totally equivalent to calling this.$router.push( { name: 'search', params: { job_handle: '0123456789ABCDEFFEDCBA9876543210'} } )
- Can have multiple
<router-view>
s with different names within a single path, and inside the routes definition component's attribute you can use a viewname: component mapping. The key for the unnamed view in this mapping is default
- When
props: true
inside route definition, route parameters get defined as a property on the component, so now you don't have to explicitly use this.$route.params
- Use VueRouter in history mode, add a catchall route to prevent weirdness related to direct navigation within single-page-apps, and use a try_files directive in your nginx.conf as a backstop:
location / { try_files $uri $uri/ /index.html; }
Dynamic Route Matching
Pulling parameters out of URL fragments
- Use dynamic segment in the route path
- When a route is matched the value of the dynamic segment will be exposed as
this.$route.params
- E.g., pattern /user/:username matches to path /user/evan and this.$route.params is object
username: 'evan'
Nested Routes
- Use children attribute when defining routes, level of nesting can be arbitrary
- Children components can have their own nested
<router-view>
tag
- Child component will be rendered inside parent component's <router-view> tag when route is matched
Asterisk
- Matching priority is given by route definition
- When using an asterisk, a param named pathMatch is automatically added to $route.params.
Navigation Guard methods for reactive components and data fetching
- If the route is the same and only your dynamic component is changing AND you need for reactive things to happen, you MUST define a beforeRouteUpdate method inside your component definition to make the reactive
beforeRouteUpdate (to, from, next) {
// react to route changes...
// don't forget to call next()
}
- to: Route: the target Route Object being navigated to.
- from: Route: the current route being navigated away from.
- next: Function: this function must be called to resolve the hook.
- next()
- next(false) - abort the navigation
- next( 'someOtherRoute' ) - redirect to a different location
- next( error ) - where error is of type Error and can be resolved on callbacks such as router.onError()
router.beforeEach((to, from, next) => {
if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
else next()
})