VueJS 不会从组件中重新渲染列表

Posted

技术标签:

【中文标题】VueJS 不会从组件中重新渲染列表【英文标题】:VueJS does not rerender list from components 【发布时间】:2019-12-08 02:24:05 【问题描述】:

尝试通过“v-for”从 vuex 渲染一个数组。

“玩家卡”组件未呈现。 但是“td”解决方案可以正常工作。

我在JSFiddle上的例子

HTML:

    <div id="app">
      <button v-on:click="moveItem">
        Move Item
      </button>

      <table cellspacing="2" border="1" cellpadding="5">
        <tr>
          <td v-for="(item, item_idx) in getItems" v-bind:key="item.col"> (item.card)? item.card.name : 'none' </td>
        </tr>
        <tr>
          <player-card v-for="(item, item_idx) in getItems" v-bind:key="item.col" v-bind:item="item"></player-card>
        </tr>
      </table>
      <br/>
      <p>msg</p>
    </div>

商店:

    const store = new Vuex.Store(
      state: 
        items: [ col: 0, row: 0 ,
                 col: 1, row: 0 ,
                 col: 2, row: 0, card:  name: "hello"   ]
      ,
      getters: 
        getterItems: state =>  return state.items; 
      ,

      mutations: 
        MOVE_ITEM: state => 
          state.items[0].card = state.items[2].card;
          delete state.items[2].card;
          state.message = JSON.stringify(state.items);
        
      

    );

组件:

    Vue.component('player-card', 
      props: 
        item: 
          type: Object,
          required: true
        
      ,
      template: '<td> (item.card)? item.card.name : "none" </td>'
    );

应用:

    new Vue(
      el: '#app',
      store,
      data: function() 
        return 
          msg: ''
        
      ,
      computed: 
        getItems()  return this.$store.getters.getterItems; 
      ,
      mounted: function()  
        this.msg = JSON.stringify(this.getItems); 
      ,
      methods: 
        moveItem() 
          this.$store.commit('MOVE_ITEM');
          this.msg = JSON.stringify(this.getItems);
        
      
    );

我已经尝试了很多解决方案,但没有找到一个简单的解决方案。也许有人会提供不同的架构解决方案。

【问题讨论】:

【参考方案1】:

你只需要改变这一行:

<player-card v-for="(item, item_idx) in getItems" v-bind:key="item.col" v-bind:item="item"></player-card>

到这里:

<td is="player-card" v-for="(item, item_idx) in getItems" v-bind:key="item.col" v-bind:item="item"></td>

这是必需的,因为您的模板是在 DOM 中指定的。浏览器会在 Vue 靠近它之前解析模板标记。 html 解析规则只允许某些元素成为&lt;tr&gt; 的直接子元素。任何其他元素都将被拉出&lt;table&gt;。当 Vue 解析模板时,&lt;player-card&gt; 元素已经被移到了&lt;table&gt; 之外。

如果您使用其他技术之一来指定模板,这将不是问题。

解决方法是使用is 属性来指定组件而不是标签名称。

这里的文档对此进行了解释:

https://vuejs.org/v2/guide/components.html#DOM-Template-Parsing-Caveats

【讨论】:

【参考方案2】:

您需要使用特殊的is 属性来绕过强加的元素结构,其中&lt;tr&gt; 期望&lt;td&gt; 作为子元素,但得到&lt;player-card&gt;(预转换)。此外,您更改 Array 的方式会导致反应性问题。考虑一下我的改变。

const store = new Vuex.Store(
  state: 
    items: [
        col: 0,
        row: 0
      ,
      
        col: 1,
        row: 0
      ,
      
        col: 2,
        row: 0,
        card: 
          name: "hello"
        
      
    ]
  ,
  getters: 
    getterItems: state => 
      return state.items;
    
  ,

  mutations: 
    MOVE_ITEM: state => 
      // Move the last element to the front
      state.items = [
        ...state.items.slice(-1),
        ...state.items.slice(0, -1)
      ];
      state.message = JSON.stringify(state.items);
    
  

);

Vue.component('player-card', 
  props: 
    item: 
      type: Object,
      required: true
    
  ,
  template: '<td> (item.card)? item.card.name : "none" </td>'
);

new Vue(
  el: '#app',
  store,
  data: function() 
    return 
      msg: ''
    
  ,
  computed: 
    getItems() 
      return this.$store.getters.getterItems;
    
  ,
  mounted: function() 
    this.msg = JSON.stringify(this.getItems);
  ,
  methods: 
    moveItem() 
      this.$store.commit('MOVE_ITEM');
      this.msg = JSON.stringify(this.getItems);
    
  
);
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vuex"></script>

<div id="app">
  <button v-on:click="moveItem">
        Move Item
      </button>

  <table cellspacing="2" border="1" cellpadding="5">
    <tr>
      <td v-for="(item, item_idx) in getItems" v-bind:key="item.col"> (item.card)? item.card.name : 'none' </td>
    </tr>
    <tr>
      <td is="player-card" v-for="(item, item_idx) in getItems" v-bind:key="item.col" v-bind:item="item"></td>
    </tr>
  </table>
  <br/>
  <p>msg</p>
</div>

【讨论】:

【参考方案3】:

我的问题也是更新 vuex 中的数组的问题。

关于此"Common Beginner Gotchas"的信息

mutations: 
MOVE_ITEM: state => 
  // Move card
  state.items[0].card = state.items[2].card;
  delete state.items[2].card;

  // Simple deep array cloning
  state.items = JSON.parse(JSON.stringify(state.items));

  state.message = JSON.stringify(state.items);

更新JSFiddle

非常感谢大家。

【讨论】:

以上是关于VueJS 不会从组件中重新渲染列表的主要内容,如果未能解决你的问题,请参考以下文章

在 Vuejs 中使用 beforemount 进行数据渲染,每次打开该组件/页面时都会不断增加渲染的数据列表

vue 改变数组中对象的属性怎么重新渲染列表

vuejs2从入门到精通视频教程

在我滚动之前,react-virtualized 列表项不会使用更改的道具重新渲染

VueJS 使用 props 渲染嵌套列表

渲染 vueJs 后重新编译模板