Centralized decoupled components in Vuex + vue-router

The problem with shared states is that it is difficult to reuse actions and mutations in different components.

Suppose we have a component Votes. This component allows users to vote on an item.

const Votes = {
  template: `<span>
        <i>{{ item.votes }}</i> <a href="#" @click.prevent="upvote">+</a>
    </span>
    `,
   methods: {
     upvote: function() {
       this.$store.dispatch('upvote', this.item.id)
     }
   },
   props: ['item']
}

Therefore, when the user clicks +, the action is sent upvote.

But how to reuse this component in two views, a list that lists all the elements, and information that displays information about the element.

In both cases, we allow users to vote on the item.

Detailed view View list

[ADDED] Vue-router

Users can navigate through the URL , for example./item/a

.

store.items !


.

state: { items: [], opened: {} },
  actions: {
    open: function({commit, state}, payload) {
        let it = db.find(item => payload === item.id) // Find in db because user can navigate via Copy/Paste URL
      commit('SET_OPENED', it)
    },
    upvote: function({commit, state}, payload) {
        let it = state.items.find(item => payload === item.id) // Problem here, state.items is when i vote in ListingView, in ItemView (our details view) should use state.opened
      commit('SET_VOTE', { id: it.id, votes: it.votes + 1 })
    }
  },
  mutations: {
    SET_VOTE: function(state, payload) {
            let it = state.items.find(item => payload.id === item.id) // Problem here, state.items is when i vote in ListingView, in ItemView (our details view) should use state.opened
      console.log('Voted', db, it)
      Vue.set(it, 'votes', payload.votes)
    },
    SET_OPENED: function(state, payload) {
        Vue.set(state, 'opened', payload)
    }
  }

upvote SET_VOTE - , ( ), .

/ ?

[]

  • URL-, /item/a,
  • - / . .

....

const db = [{
  id: 'a',
  name: 'Item #1',
  image: 'http://lorempicsum.com/simpsons/350/200/1',
  votes: 0
}, {
  id: 'b',
  name: 'Item #2',
  image: 'http://lorempicsum.com/simpsons/350/200/2',
  votes: 0
}, {
  id: 'c',
  name: 'Item #3',
  image: 'http://lorempicsum.com/simpsons/350/200/3',
  votes: 0
}]

const Votes = {
  name: 'Votes',
  template: `<span>
	  	<i>{{ item.votes }}</i> <a href="#" @click.prevent="upvote">+</a>
    </span>
	`,
  methods: {
    upvote: function() {
      this.$store.dispatch('upvote', this.item.id)
    }
  },
  props: ['item']
}

const ListingView = {
  name: 'ListingView',
  template: `
    <ul class="listing">
    	<li v-for="item in $store.state.items">
				<router-link :to="{ name: 'item', params: { id: item.id }}">
      		<img :src="item.image" />
	  	    <br>{{ item.name }}	      
	      </router-link>
      	Votes: <votes :item=item></votes> 
    	</li>
		</ul>
  `,
  created() {
    this.$store.dispatch('fetch')
  },
  components: {
    Votes
  }
}

const ItemView = {
  name: 'ItemView',
  template: `<div class="item-view">
  		<router-link class="back-listing" :to="{name: 'listing'}">Back to listing</router-link>
	  	<div class="item">
  	  	<h1>{{ item.name }} <votes :item=item></votes> </h1>
    		<img :src="item.image" />
	    </div>
		</div>
  </div>`,
  computed: {
    item: function() {
      return this.$store.state.opened
    }
  },
  created() {
    this.$store.dispatch('open', this.$route.params.id) // I need this because user can navigate via Copy/Paste URL
  },
  components: {
    Votes
  }
}

const store = new Vuex.Store({
  state: {
    items: [],
    opened: {}
  },
  actions: {
    fetch: function({
      commit, state
    }, payload) {
      commit('SET_LIST', db.map(a => Object.assign({}, a))) // Just clone the array
    },
    open: function({
      commit, state
    }, payload) {
      let it = db.find(item => payload === item.id) // Find in db because user can navigate via Copy/Paste URL
      commit('SET_OPENED', it)
    },
    upvote: function({
      commit, state
    }, payload) {
      let it = state.items.find(item => payload === item.id) // Problem here, state.items is when i vote in ListingView, in ItemView should use state.opened
      commit('SET_VOTE', {
        id: it.id,
        votes: it.votes + 1
      })
    }
  },
  mutations: {
    SET_VOTE: function(state, payload) {
      let it = state.items.find(item => payload.id === item.id) // Problem here, state.items is when i vote in ListingView, in ItemView should use state.opened
      console.log('Voted', db, it)
      Vue.set(it, 'votes', payload.votes)
    },
    SET_OPENED: function(state, payload) {
      Vue.set(state, 'opened', payload)
    },
    SET_LIST: function(state, payload) {
      Vue.set(state, 'items', payload)
    }
  }
})
const router = new VueRouter({
  routes: [{
    name: 'listing',
    path: '/',
    component: ListingView
  }, {
    name: 'item',
    path: '/item/:id',
    component: ItemView
  }]
})
new Vue({
  el: '#app',
  store,
  router
})
* {
  box-sizing: border-box;
}
.listing {
  list-style-type: none;
  overflow: hidden;
  padding: 0;
}
.listing li {
  float: left;
  width: 175px;
  text-align: center;
  border: 1px #ddd solid;
  background: white;
  margin: 5px;
  cursor: pointer;
}
.listing li img {
  width: 100%;
  margin-bottom: 4px;
}
.listing li > a:hover {
  background: #eee;
}
.item-view {
  text-align: center;
}
.item {
  padding: 10px;
}
a {
  font-size: 16px;
  display: inline-block;
  padding: 10px;
  border: 1px #ddd solid;
  background: white;
  color: black;
  margin: 10px;
  &.back-listing {
    position: absolute;
    left: 0;
    top: 0;
  }
}
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vuex/dist/vuex.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<div id="app">
  <router-view></router-view>
</div>

: http://jsfiddle.net/Ridermansb/sqmofcbo/3/

( ) Vue Forum

+4
1

, , state.opened. state.opened state.items.

.

http://jsfiddle.net/d30o31r8/

+2

Source: https://habr.com/ru/post/1662303/


All Articles