【问题标题】:VueJS does not rerender list from componentsVueJS 不会从组件中重新渲染列表
【发布时间】:2023-05-04 18:16:02
【问题描述】:

尝试通过“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);
        }
      }
    });

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

【问题讨论】:

    标签: javascript vue.js vuex


    【解决方案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

        非常感谢大家。

        【讨论】: