Vue.js
The Practical Guide
@N_Tepluhina
What will we learn today?
- Shared state: when it becomes necessary
- Vuex
- Provide/inject
@N_Tepluhina
When do we need a shared state?
@N_Tepluhina
@N_Tepluhina
- Deep nested components to avoid prop drilling
- Router views
- Independent components
- Views with API requests
@N_Tepluhina
Sharing non-reactive piece of state
- We don't add reactivity when it's not necessary
- Properties are available globally
- Great for injecting dependencies
@N_Tepluhina
Sharing non-reactive piece of state
// main.js
const app = createApp({
...App,
provide: { baseUrl: 'https://rickandmortyapi.com/api/' },
})
app.mount('#app')
@N_Tepluhina
Sharing non-reactive piece of state
// Home.vue
export default {
inject: ['baseUrl'],
data() {
return {
...
}
}
};
@N_Tepluhina
Vuex
@N_Tepluhina
On project creation with Vue CLI
@N_Tepluhina
With plugin on CLI-based project
@N_Tepluhina
Manually
npm install vuex@next
## OR
yarn add vuex@next
@N_Tepluhina
Manually
// store/index.js
import { createStore } from 'vuex'
export default createStore({
state() {
return {
items: [],
}
},
})
@N_Tepluhina
Manually
// main.js
import store from './store'
const app = createApp(App)
app.use(router)
app.use(store)
app.mount('#app')
@N_Tepluhina
Accessing state in the component
// Home.vue
computed: {
searchResults() {
return this.$store.state.items
},
},
@N_Tepluhina
Better: mapping the state
// Home.vue
import { mapState } from 'vuex'
export default {
computed: {
...mapState({
searchResults: (state) => state.items,
})
}
}
@N_Tepluhina
Never ever change the state outside the mutation!
@N_Tepluhina
Mutations
export default createStore({
state() {
return {
items: [],
}
},
mutations: {
fetchResults(state) {
state.items = [
{
id: '1',
name: 'Test',
description: 'Test description',
liked: false,
},
]
},
},
})
@N_Tepluhina
Running a mutation from the component (not recommended)
// Home.vue
created() {
this.$store.commit('fetchResults')
},
@N_Tepluhina
Constants for naming
mutations: {
FETCH_RESULTS(state) {
...
},
},
Mutation types (optional)
// store/mutationTypes.js
export const FETCH_RESULTS = 'FETCH_RESULTS'
// store/index.js
import * as types from './mutationTypes';
export default createStore({
state() {
return {
items: [],
}
},
mutations: {
[types.FETCH_RESULTS](state) {
/*...*/
},
},
})
@N_Tepluhina
Actions
// store/index.js
import * as types from './mutationTypes';
export default new Vuex.Store({
state: {
...
},
mutations: {
...
},
actions: {
async fetchResults({ commit }, searchTerm) {
// axios request to fetch the results
commit(types.FETCH_RESULTS, results)
},
},
});
@N_Tepluhina
Actions
// Home.vue
created() {
this.$store.dispatch('fetchResults', this.searchTerm)
},
@N_Tepluhina
// Home.vue
import { mapActions } from 'vuex';
export default {
methods: {
...mapActions(['fetchResults']),
},
created() {
this.fetchResults(this.searchTerm);
},
};
Mapped actions
@N_Tepluhina
Fine-grained mutations
// store/index.js
mutations: {
[types.REQUEST_RESULTS](state) {
state.loading = true
state.error = null
},
[types.RECEIVE_RESULTS_SUCCESS](state, payload) {
state.loading = false
state.items = payload
state.error = false
},
[types.RECEIVE_RESULTS_ERROR](state) {
state.loading = false
state.items = []
state.error = true
},
},
@N_Tepluhina
Fine-grained mutations
// store/index.js
actions: {
async fetchResults({ commit }, searchTerm) {
const endpoint = searchTerm.length ? '/breeds/search' : '/breeds'
commit(types.REQUEST_RESULTS)
try {
/* Logic for fetching results */
commit(types.RECEIVE_RESULTS_SUCCESS, results)
} catch {
commit(types.RECEIVE_RESULTS_ERROR)
}
},
},
@N_Tepluhina
Fine-grained mutations
fetch<Entity> action
REQUEST_<Entity> mutation
RECEIVE_<Entity>_SUCCESS mutation
RECEIVE_<Entity>_ERROR
mutation
@N_Tepluhina
Getters
// store/index.js
getters: {
numberOfLiked(state) {
return state.items.filter((item) => item.liked).length
},
},
@N_Tepluhina
Do not overuse getters!
// store/index.js
getters: {
getItems(state) {
return state.items // This is bad, use mapState instead!
}
},
@N_Tepluhina
Modules
// store/index.js
const items = {
namespaced: true,
state: () => ({...}),
mutations: {...},
actions: {...},
getters: {...},
};
export default createStore({
modules: {
items,
},
...
})
@N_Tepluhina
Modules
// Characters.vue
computed: {
...mapState('items', {
items: (state) => state.items,
loading: (state) => state.loading,
}),
},
@N_Tepluhina
Dynamic module registration
// store/index.js
const store = createStore({ /* options */ })
store.registerModule('items', {
// ...
})
@N_Tepluhina
Router + Vuex
// main.js
import store from './store'
const router = createRouter({
routes,
history: createWebHistory(),
})
router.beforeEach((to, from, next) => {
if(!store.state.isAuthenticated) next({name: 'login'})
else next()
})
Q & A
@N_Tepluhina
Vue.js: The Practical Guide
By Natalia Tepluhina
Vue.js: The Practical Guide
Smashing Workshop - Day 4
- 884