4

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

让我们假设我们有一个组件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

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

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

详情视图 列表显示

[添加] Vue路由器

用户可以通过 URL 导航,例如/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是从不同点(不同观点)调用的动作和突变,因此状态是不同的。

问题

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

[已添加]记住

  1. 用户可以通过 URL 导航,例如/item/a应该显示项目
  2. 目标是重用动作/突变和组件。所以重复所有不会解决这个问题。

完整源码....

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 论坛中添加另一个帖子(交叉发帖)

4

1 回答 1

2

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

带有一些关于修复它的额外评论的工作示例。

http://jsfiddle.net/d30o31r8/

于 2016-11-29T17:21:18.783 回答