Welcome back! This is the second part in n (still not decided) part series trying to help developers with knowledge of either Redux or Vuex understand the other one. If you want to read about reducers, mutations, actions and async action, you can read them here in part- Ⅰ.
OK, so with the customary disclaimer for multi-part series out of the way, let’s dig into our main topic.
Creating a store
To create a new store, you need to import createStore from redux.
1 2 3 4 |
import { createStore } from 'redux'; // also import our reducer function let store = createStore(reducer); |
Similarly, for Vuex store can be created using Store constructor.
1 2 3 4 5 6 7 8 9 10 11 |
import Vuex from 'vuex'; import Vue from 'vue'; Vue.use(Vuex); let store = new Vuex.Store({ state: { // properties for state obj }, mutations: {}, actions: {} }); |
Passing the store
Now that we’ve created our store, we need to make sure that our components can use it. We can either pass the store object to each component (too tedious 😩) or we can pass the store to the root element such that it becomes available to all the components.
In React, we create a new parent to our root component, Provider component from react-redux and pass the store as an attribute to it.
1 2 3 4 5 6 7 8 |
import { Provider } from 'react-redux' render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') ) |
In Vue too it’s done while initializing our root element by passing the store as a part of object being passed to the Vue constructor.
1 2 3 4 5 6 7 |
import Vue from 'vue'; import App from './App.vue'; new Vue({ store, render: h => h(App) }).$mount('#app') |
Now that store has become available in our components, next we’ll see how to read/write to them from components.
Using in components
React
To connect your component to the store you can either use store.subscribe() in your container component, or use the connect API. We are going to use the connect API as it has lot of performance optimization.
Let’s take a simple component Counter.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class Counter extends Component { render() { const { value, onIncrement, onDecrement } = this.props return ( <p> Clicked: {value} times {' '} <button onClick={onIncrement()}>+</button> {' '} <button onClick={onDecrement()}>-</button> </p> ) } } export default Counter |
This is a simple React component and has nothing to do with Redux. Now let’s write a container, which will connect to the store.
To use the connect function we need to define a function that tells how to map store’s state to components properties.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import Counter from '../components/Counter' const mapStateToProps = (state) => ({ value: state.value }) const mapDispatchToProps = (dispatch) => ({ onIncrement: () => dispatch({type: 'increment'}), onDecrement: () => dispatch({type: 'decrement'}) }) const CounterLink = connect( mapStateToProps, mapDispatchToProps )(Counter) export default CounterLink |
mapStateToProps, defines mapping between state and component’s properties and similarly mapDispatchToProps defines the mapping between store’s and component’s action handlers.
If you want to read this topic in more depth please read it in Redux’s official documentation, you can also see the complete source code.
Vue
In Vue, store becomes available to all components via this.$store. Reactive nature of Vue makes sure that whenever our store’s state changes our local properties also gets recomputed.
Let’s have a look at an example for properties to state mapping:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<template> Clicked: {{value}} Times <button type="button" @click.prevent="increment">+</button> <button type="button" @click.prevent="decrement">-</button> </template> <script> export default { name: 'counter', computed: { value () { return this.$store.state.value; } }, methods: { increment () { this.$store.commit('increment'); }, decrement () { this.$store.commit('decrement'); } } } </script> |
In a large application it will be tedious to map all properties one by one. So, Vuex provides a helper function mapState.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
import { mapState } from 'Vuex'; computed: mapState({ value: state => state.value, val: 'value', // passing string is same as arrow function above computedValue (state) { return parseInt(state.value, 10); } }) //or you can also pass array to mapState computed: mapState([ 'value', // other props ]) // If you have local computed state you can still use mapState function computed: { localState() { }, ...mapState({}) } // You can also have multiple mapState in one component component: { ...mapState([]), ...mapState({}) } |
Note: Similar to mapState, Vuex also provides other helper functions like mapMutations, mapActions and mapGetters (getters are discussed later). Implementations shown above holds true for all of them.
You can check few of examples in code of Vuex.
Computing derived data
Redux
In Redux, when you map a state to a property it gets recomputed every time the component is updated.
To avoid this, we can use Reselect library which can help us create memoized selector functions. These functions will recalculate only if it’s arguments change.
Memoized Selectors
To create a memorized selector, we use createSelector function. The function takes one or more selectors as its argument and then passes their value to resultFunction.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
const mySelector = createSelector( state => state.values.value1, state => state.values.value2, (value1, value2) => value1 + value2 // result function ) // You can also pass an array of selectors and access properties of components const totalSelector = createSelector( [ (state, prop) => state.values.value1 + prop.value1, (state, prop) => state.values.value2 + prop.value1 ], (value1, value2) => value1 + value2 ) |
These selectors have cache size of 1, meaning every change in input selectors will be recalculated. You can also use memorized selector as an input selector to another memorized selector.
Connecting with Redux Store
In order to connect a selector to our store we don’t need to make too many changes to our application. We just need to pass it as regular function to our mapStateToProp object.
1 2 3 4 5 6 |
// containers/sum.js import { totalSelector } from '../selectors' const mapStateToProps = state => ({ total: totalSelector(state) // you can pass props as second arg }) |
Vuex
Vuex’s store contains another object called getters, to store some common operations like filtering out counters by id.
1 2 3 4 5 6 7 8 9 10 11 |
const store = new Vuex.Store({ state: { counters: [ { id: 1, value: 1 }, { id: 2, value: 5 } ] }, getters: { getCounterById: (state) => (id) => state.counters.find(counter => counter.id === id) } }); |
These getters are available in component via store.getters objects. You don’t need to worry about performance of these getters as Vuex implements memoization for them (will try and cover this in some future blog, but not as a part of this series).
I hope that by now you guys have got basic (and some advance) understanding of both Redux and Vuex and will find migrating between them easier than you would in the past. I still have few more topics to cover and will continue with them in the next article.
Pingback: Migrating between Redux and Vuex (Part 1) – experience@imaginea