Ways to share data between Vue components without middleware
-
component props: parent --> child
-
$parent, $child, ref and $refs: access parent, child or referred component data
-
emit events: child (this.$emit('event_name', data)) ---> parent ( @event_name="eventHandler")
-
$attrs/$listeners: pass comoponent props(v-bind) or events(v-on) downside level by level
-
provide/inject: ancient component provides shared data for all the descendant components to use.
// A.vue export default { provide: { sharedData: "value", // provide shared data for descendent components } }
// B.vue: A's descendant component export default { inject: ['sharedData'], // take shared data from ancient component mounted() { console.log(this.sharedData); } }
-
Event Bus, $emit and $on: use an empty Vue instance as the global shared event bus. By means of events carried by the event bus, parent, child, sibling components can share data without restriction.
// EventBus.js: Use it as a global shared bus/bridge to send/receive data by components import Vue from 'vue' export default const Event = new Vue(); // Sender.js: Sender component import { EventBus } from './EventBus' EventBus.$emit('event_name', data); // send data via specific event // Receiver.js: Receiver component import { EventBus } from './EventBus' // get data passed by event namefrom Sender component EventBus.$on('event_name', data => { handle data; }); // Remove event listener import { EventBus } from './EventBus' // get data passed by event namefrom Sender component EventBus.$off('event_name', {});
or add Event Bus to Vue prototype property in main.js
import Vue from 'vue' Vue.prototype.$eventBus = new Vue(); this.$eventBus.$emit('event_name', data); // send data this.$eventBus.$on('event_name', data => { handle data; }); // receive data this.$eventBus.$off('event_name', {}); // remove event listener
-
Web Storage API: HTML5 provides localStorage and sessionStorage API for data persistance for different scale and period. Thus, the page components can call these APIs to read/write data for sharing.
-
slot-scope: Put
slot-scope
into template tag, and bind the template with data exposed by the child component. This allows the parent to render child component with different UI styles or data by the template slot. By this way, the parent component can used data inside the child component and render it with styles from parent itself. This is a very limited scope data sharing between parent and child components.// parent component <template> <div class="parent"> <h3>This is the Parent Component</h3> <child> <template slot-scope="scope"> <ul class="list-class"> <li v-for="item in scope.childData">{{item}}</li> </ul> </template> </child> <child> <template slot-scope="scope"> <div class="div-class"> <span v-for="(item, index) in scope.childData">{{ index+1 }}: {{item}}</span> </div> </template> </child> </div> </template> <style> .parent { ... } .list-class{ ... } .div-class{ ... } </style>
// child component <template> <div class="child"> <h3>This is the Child Component</h3> // child component exposes data for parent component <slot :childData="data"></slot> </div> </template> export default { data: function(){ return { data: ['Alex','Brian','Jeremy','Teddy','Travis'] } } }
Why and When to use Vuex?
Ways to communicate between components mentioned above can be used according to different rscenarios. Some ways are strictly restricted by components relationship and are not flexible for global data sharing;
Athough the Event Bus provides a lightweight way for share data globally, it's not easy to manage events and state when it's used for large-scale application which has many components with complex relationship of data sharing, like: data may be shared from different levels of components, or multiple components, etc. The shared data mutation may cause issues, and code is not easy to maintain. Thus, a centralised state manager with data protection or other auxiliary features will provide more flexibility, maintenance to our development.
Vuex serves as a centralised store for all the components in an application, with rules ensuring that the state can only be mutated in a predictable fashion. It also integrates with Vue's official devtools extension to provide advanced features such as zero-config time-travel debugging and state snapshot export / import.
Vuex Quick Start
npm install vuex
// store.js import Vue from "vue"; import Vuex from "vuex"; Vue.use(Vuex); // create store and export export default new Vuex.Store({ state: {}, // add state content here mutations: {}, // add state mutation methods here actions: {}, // add async operation methods here: setTimeout(), axios.get(), ... getters: {}, // add computed methods(using state data to form new data and return) here modules: {}, // });
// main.js import store from "./store"; new Vue({ store, // mount store to App root component render: (h) => h(App), }).$mount("#app")
Vuex Core Concepts and How to Use
-
Store: the global object contains application state and operation methods.
-
State: ways to access(read) state in store. It is a single state tree - that is, this single object contains all your application level state and serves as the "single source of truth".
const store = new Vuex.store({ // signle state tree state: { count: 0 }, })
2 ways to use state in store:
// way 1 this.$store.state.count // way 2: call <code>mapState</code> function to map the state as component computed property import { mapState } from 'vuex'; computed: { ...mapState(['count']); // use count as a computed property in this component }
-
Mutations: Do not use
this.$store.state
to modify the data directly(this is not the recommended way), call mutation method defined in store to mutate state. This lets store provide centralised management of state data, mutating state only by store's own methods.const store = new Vuex.store({ state: { count: 0 }, // All the mutation methods are defined here mutations: { // define mutation methods addCount(state, payload) { state.count += payload; } } })
There are 2 ways to mutate state:
// way 1: use this.$store.commit() this.$store.commit('addCount', payload); // submit data change in component // way 2: similarly to mapState(), mapMutations() will map the mutatioin method as a component method import { mapMutations } from 'vuex'; methods: { ...mapMutations(['addCount']); mutateHandler(){ this.addCount(3); // call mapped mutation method } }
Restriction: synchronised operations are not allowed here, like setTimeout(), IO requests, etc.
-
Actions: Use action methods defined in store to modify data asynchronously, but an action method must call mutation methods to update state.
const store = new Vuex.store({ state: { count: 0 }, // All the mutation methods are defined here mutations: { // define mutation methods addCount(state, payload) { state.count += payload; } }, // All the aysnc operations methods are defined here actions: { // define async mutation operation methods // call mutation methods only to update state asyncAddCount(context, payload) { setTimeout(() => context.commit('addCount', payload), 1000); } } })
There are 2 ways to mutate state asynchronously:
// way 1: use this.$store.dispatch() this.$store.dispatch('addCount', payload); // submit data change in component // way 2: similarly to mapState(), mapMutations() will map the mutatioin method as a component method import { mapActions } from 'vuex'; methods: { ...mapActions(['asyncAddCount']); actionHandler(){ this.asyncAddCount(3); // call mapped action method } }
-
Getters Methods are used to wrap state in store and form new data to return.
- Getters won't mutate the state in store, it just uses the data to generate new data.
- The data returned by a gettter is reactive, that means when data(belongs to state) used by getter is changed in the store, the data returned by the getter will change as well.
const store = new Vuex.store({ state: { count: 0 }, // All the mutation methods are defined here mutations: { // define mutation methods addCount(state, payload) { state.count += payload; } }, // All the aysnc operations methods are defined here actions: { // define async mutation operation methods // call mutation methods only to update state asyncAddCount(context, payload) { setTimeout(() => context.commit('addCount', payload), 1000); } }, // getters: { showCount(state) { return <code>Latest count is ${state.count}</code> } } })
Two ways to use getters:
// way 1 this.$store.getters.showCount // way 2: call mapGetters to map getters as a compute property of current component // then the getter can be used as a compute property import { mapGetters } from 'vuex'; computed: { ...mapGetters(['showCount']); // showCount will be a compute property }
-
Modlues: Modules are used when the application state are too complex, we divide the store into categories by modules and manage them seprately in order to provide clearer logic and maintanable code. The following example is from official docs.
const moduleA = { state: () => ({ ... }), mutations: { ... }, actions: { ... }, getters: { ... } } const moduleB = { state: () => ({ ... }), mutations: { ... }, actions: { ... } } const store = new Vuex.Store({ modules: { a: moduleA, b: moduleB } }) store.state.a // -> <code>moduleA</code>'s state store.state.b // -> <code>moduleB</code>'s state
Comments