在Vuex + vue-router中具有集中状态的分离组件

时间:2016-11-29 16:37:31

标签: vue.js vue-router vuex

共享状态的问题是难以在不同组件中重用动作和突变。

让我们假设我们有一个组件Votes。此组件允许用户对项目

进行投票
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']
}

因此,当用户点击+时,就会发送一个操作upvote

但是如何在两个视图中重用此组件,列出所有项目的列表以及显示项目详细信息的详细信息。

在这两种情况下,我们都允许用户对项目进行投票。

Details view List view

[已添加] Vue-router

用户可以通过网址进行导航,例如。 /item/a

在这种情况下,应使用router params在数据库中查找项目。

  

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)
    }
  }

upvoteSET_VOTE是从不同点(不同视图)调用的动作和突变,因此状态是不同的。

问题

如何在具有不同状态的不同视图中重用相同的动作/突变?

[已添加] 请记住

  1. 用户可以通过网址导航,例如/item/a,并且应该显示项目
  2. 目标是重用动作/突变和组件。因此重复所有将无法解决此问题。
  3. 完全来源......

    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

    中添加了另一篇文章(交叉发布)

1 个答案:

答案 0 :(得分:2)

只需简单了解一下您的代码,您的问题主要在于您将当前项目复制到state.opened。您应该在state.opened中存储当前打开的项的ID的引用,而不是这样做,并使用该ID修改state.items

有关修复它的一些额外注释的工作示例。

http://jsfiddle.net/d30o31r8/