是什么原因导致VueJS在此实例中停止更新DOM?

时间:2017-10-25 08:03:35

标签: javascript jquery ajax vue.js

我看过我能找到的每一个类似的帖子,但似乎没有答案可以解决我的问题。具体来说,它不会使用id“table”更新表。

HTML:

<section id="body">
    <div class="container-fluid">
        <div class="row">
            <div class="col-xs-12">
                <div class="panel panel-primary">
                    <div class="panel-heading" id="panel">
                        <div class="row">
                            <div class="col-sm-12">
                                <h3 class="panel-title">Filters</h3>
                            </div>
                        </div>
                    </div>
                    <div class="panel-body" id="panel-body">
                        <div class="row">
                            <div class="col-sm-12">
                                <form id="filterForm" class="form-horizontal">
                                    <div class="form-group">
                                        <div class="col-sm-12">
                                            <label class="control-label" for="focusedInput">Category:</label>
                                            <select id="category" class="js-example-basic-single form-control">
                                                <option value="">Any</option>
                                                <option v-for="category in categories" value="category.categoryTitle">
                                                {{category.categoryTitle}}</option>
                                            </select>
                                        </div>
                                    </div>
                                    <div class="form-inline row">
                                        <div class="col-sm-12">
                                            <label class="control-label" style="margin-right:20px;">Air Date:</label>
                                            <div style="width:35%" class="form-group">
                                                <div class='input-group date' id='datetimepicker1'>
                                                    <input type='text' class="form-control" v-model="airDate"/>
                                                    <span class="input-group-addon">
                                                        <span class="glyphicon glyphicon-calendar"></span>
                                                    </span>
                                                </div>
                                            </div>
                                            <label class="control-label">Show Number:</label>
                                            <input style="width:35%" class="form-control" type="number" id="showNumber" v-model="showNumber">
                                        </div>
                                    </div>
                                    <div class="form-inline row">
                                        <div class="col-sm-12">
                                            <label class="control-label">Question contains:</label>
                                            <input style="width:35%" class="form-control" type="text" v-model="questionText">
                                            <label class="control-label">Dollar Value:</label>
                                            <input style="width:35%" class="form-control" type="number" id="showNumber" v-model="dollarValue">
                                        </div>
                                    </div>
                                </form>
                            </div>
                        </div>
                        <div class="row">
                            <div class="col-sm-offset-9 col-sm-3" style="margin-top:5px;">                                   
                                <button type="button" class="btn btn-warning" v-on:click="reset">Reset Filters</button>
                                <button type="button" class="btn btn-primary" v-on:click="filter">Filter</button>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        <div class="row">
            <div class="col-xs-12">
                <div class="panel panel-primary" id="tableCard" style="margin-bottom:20px; margin-top:40px;">
                    <div class="panel-heading">
                        <div class="row">
                            <div class="col-sm-10">
                                <h3 class="panel-title">Jeopardy Questions</h3>
                            </div>
                            <div class="col-sm-2">
                                <span id="totalQuestionsSpan">Total Entries: {{entries.length}} entries</span>
                            </div>
                        </div>
                    </div>
                    <div class="panel-body" style="padding-top:45px;">
                        <div class="wrapper">
                            <table id="tableScroll" class="table table-striped table-fixed">
                                <thead style="background-color:white;">
                                    <tr>
                                        <th style="cursor:pointer; min-width: 110px;">
                                            Question
                                            <span v-if="questionSort == 1" id="questionUp">&#9650;</span>
                                            <span v-else-if="questionDown == -1" id="questionDown">&#9660;</span>
                                        </th>
                                        <th style="cursor:pointer; min-width: 120px; ">
                                            Answer
                                            <span v-if="answerSort == 1" id="answerUp">&#9650;</span>
                                            <span v-else-if="answerDown == -1" id="answerDown">&#9660;</span>
                                        </th>
                                        <th style="cursor:pointer; min-width: 80px;">
                                            Value
                                            <span v-if="valueSort == 1" id="valueUp">&#9650;</span>
                                            <span v-else-if="valueDown == -1" id="valueDown">&#9660;</span>
                                        </th>
                                        <th style="cursor:pointer; min-width: 80px;">
                                            Show Number
                                            <span v-if="showNumberSort == 1" id="showNumberUp">&#9650;</span>
                                            <span v-else-if="showNumberDown == -1" id="showNumberDown">&#9660;</span>
                                        </th>
                                        <th style="cursor:pointer; min-width: 80px;">
                                            Category
                                            <span v-if="categorySort == 1" id="categoryUp">&#9650;</span>
                                            <span v-else-if="categoryDown == -1" id="categoryDown">&#9660;</span>
                                        </th>
                                        <th style="cursor:pointer; min-width: 80px;">
                                            Air Date
                                            <span v-if="airDateSort == 1" id="airDateUp">&#9650;</span>
                                            <span v-else-if="airDateDown == -1" id="airDateDown">&#9660;</span>
                                        </th>
                                    </tr>
                                </thead>
                                <tbody id="table">
                                    <tr v-for="entry in entries">
                                        <td>{{entry.questionText}}</td>
                                        <td>{{entry.answerText}}</td>
                                        <td>{{entry.dollarValue}}</td>
                                        <td>{{entry.showNumber}}</td>
                                        <td>{{entry.categoryTitle}}</td>
                                        <td>{{entry.airDate}}</td>
                                    </tr>
                                    <tr v-if="entries.length == 0">
                                        <td colspan="6" style="text-align: center;"> No entries to display </td>
                                    </tr>
                                </tbody>
                            </table>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</section>

JS

var app = new Vue({
        el: '#body',
        data: {
            loggedIn: false,
            questionSort: 0,
            answerSort: 0,
            valueSort: 0,
            showNumberSort: 0,
            categorySort: 0,
            airDateSort: 0,
            entries: [],
            url: "/questions",
            categories: [],

            // form model data
            categoryTitle: '',
            airDate: '',
            questionText: '',
            dollarValue: '',
            showNumber: '',
        },
        mounted: function () {
            $.get("/api/categories", function(result) {
               Vue.set(app, "categories", result.data);
                $('.js-example-basic-single').select2();
            }, "json").fail(function(err) {
                showErrorMessage(err.responseJSON.message_prettified);
            });
        },
        methods: {
            filter : function() {
                var queryParams = "?";
                var params = 0;
                app.categoryTitle = $('#category :selected').text().trim();
                if (typeof app.categoryTitle !== "undefined" && app.categoryTitle != null) {
                    params++;
                    queryParams += "categoryTitle=" + app.categoryTitle
                }
                if (app.airDate.length > 0) {
                    params++;
                    if (params > 0) {
                        queryParams += "&";
                    }
                    queryParams += "airDate=" + app.airDate
                }
                if (app.questionText.length > 0) {
                    params++;
                    if (params > 0) {
                        queryParams += "&";
                    }
                    queryParams += "questionText=" + app.questionText
                }
                if (app.dollarValue.length > 0) {
                    params++;
                    if (params > 0) {
                        queryParams += "&";
                    }
                    queryParams += "dollarValue=" + app.dollarValue
                }
                if (app.showNumber.length > 0) {
                    params++;
                    if (params > 0) {
                        queryParams += "&";
                    }
                    queryParams += "showNumber=" + app.showNumber
                }
                if (queryParams.length == 1) {
                    queryParams = "";
                }
                var url = "/questions"
                var URL = url + queryParams;
                $.get(URL, result => {   
                        Vue.set(app, "entries", result.data);
                        app.$forceUpdate();
                    }, "json").fail(function(err) {
                        showErrorMessage(err.responseJSON.message_prettified);
                    }).always(function() {
                       $("#loader").addClass("toggled");
                });
            }
        }
    });

当前行为:

对/ api / categories的AJAX调用正确地更新了DOM上的下拉列表,允许我选择一个类别。当应用程序安装时,它会更新表格,显示colspan 6“没有要显示的条目”单元格。但是,在发送并返回过滤器请求后,表不会更新以反映更新的数据(尽管在控制台中检查时数据正确显示为已更改)。

预期行为:

当AJAX调用/ question with query params解析并更新app中的条目数据字段时,表格会更新以反映更改。

尝试修复:

探索$ forceUpdate,$ set,Vue.set,并使用for循环手动覆盖数组。

修改

经过大量的窥探并整合VueX(如下所述@WaldemarIce)可能有所帮助,但无论如何改进了我的迷你程序的整体代码结构,我已经找到了解决方案。

Laracast上的这篇文章让我想知道是否存在数据问题:https://laracasts.com/discuss/channels/vue/v-for-loop-rendering-keeps-throwing-undefined-error

然后让我意识到问题出现在这行代码中:

<option v-for="category in categories" value="category.categoryTitle">
                                            {{category.categoryTitle}}</option>

这导致了一个问题,因为value =“category.categoryTitle”中的类别直到生命周期的后期才定义。我将其更改为v-bind:value =“category.categoryTitle”并更新了我的JS以使其现在正常工作。我在关于@Kaicui帖子的后续讨论中发布的TypeError导致Vue失去了数据的反应性。一旦我解决了这个问题,Vue就开始再次做出正确反应。

更新了HTML:

<section id="body">
    <div class="container-fluid">
        <div class="row">
            <div class="col-xs-12">
                <div class="panel panel-primary">
                    <div class="panel-heading" id="panel">
                        <div class="row">
                            <div class="col-sm-11">
                                <h3 class="panel-title">Filters</h3>
                            </div>
                            <div class="col-sm-1">
                                <i id="toggleFilter" class="fa fa-chevron-down filter-collapsed" style="cursor:pointer; display:none;" aria-hidden="true"></i>
                                <i id="toggleFilter" class="fa fa-chevron-up filter-collapsed"  aria-hidden="true" style="cursor:pointer;"></i>
                            </div>
                        </div>
                    </div>
                    <div class="panel-body" id="panel-body">
                        <div class="row">
                            <div class="col-sm-12">
                                <form id="filterForm" method="GET" action="/questions" class="form-horizontal">
                                    <div class="form-inline">
                                        <div class="col-sm-12" style="margin-bottom:15px;">
                                            <input type="hidden" name="categoryTitle" id="categoryTitleHidden">
                                            <label class="control-label" for="focusedInput">Category:</label>
                                            <select style="width:90%; height:120% !important;" v-model="categorySelect" id="category" class="js-example-basic-single form-control">
                                                <option value="">Any</option>
                                                <option v-for="category in categories" v-bind:value="category.categoryTitle">
                                                {{category.categoryTitle}}</option>
                                            </select>
                                        </div>
                                    </div>
                                    <div class="form-inline">
                                        <div class="col-sm-12" style="margin-bottom:15px;">
                                            <label class="control-label" style="margin-right:20px;">Air Date:</label>
                                            <div style="width:35%; margin-right:10px" class="form-group">
                                                <div style="width:100%" class='input-group date' id='datetimepicker1'>
                                                    <input type='text' class="form-control" name="airDate"/>
                                                    <span class="input-group-addon">
                                                        <span class="glyphicon glyphicon-calendar"></span>
                                                    </span>
                                                </div>
                                            </div>
                                            <label class="control-label">Show Number:</label>
                                            <input style="width:35%" class="form-control" type="number" id="showNumber" name="showNumber">
                                        </div>
                                    </div>
                                    <div class="form-inline">
                                        <div class="col-sm-12">
                                            <label class="control-label">Question contains:</label>
                                            <input style="width:35%" class="form-control" type="text" id="questionText" name="questionText">
                                            <label class="control-label">Dollar Value:</label>
                                            <input style="width:35%" class="form-control" type="number" id="dollarValue" name="dollarValue">
                                        </div>
                                    </div>
                                </form>
                            </div>
                        </div>
                        <div class="row">
                            <div class="col-sm-offset-9 col-sm-3" style="margin-top:5px;">
                                <button type="button" class="btn btn-warning" v-on:click="reset">Reset Filters</button>
                                <button type="button" class="btn btn-primary" v-on:click="filter">Filter</button>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        <div class="row">
            <div class="col-xs-12">
                <div class="panel panel-primary" id="tableCard" style="margin-bottom:20px; margin-top:40px;">
                    <div class="panel-heading">
                        <div class="row">
                            <div class="col-sm-10">
                                <h3 class="panel-title">Jeopardy Questions</h3>
                            </div>
                            <div class="col-sm-2">
                                <span id="totalQuestionsSpan">Total Entries: {{entryCount}} entries</span>
                            </div>
                        </div>
                    </div>
                    <div class="panel-body" style="padding-top:45px;">
                        <div class="wrapper">
                            <table id="tableScroll" class="table table-striped table-fixed">
                                <thead style="background-color:white;">
                                    <tr>
                                        <th style="cursor:pointer; min-width: 110px;">
                                            Question
                                        </th>
                                        <th style="cursor:pointer; min-width: 120px; ">
                                            Answer
                                        </th>
                                        <th style="cursor:pointer; min-width: 80px;">
                                            Value
                                        </th>
                                        <th style="cursor:pointer; min-width: 80px;">
                                            Show Number
                                        </th>
                                        <th style="cursor:pointer; min-width: 80px;">
                                            Category
                                        </th>
                                        <th style="cursor:pointer; min-width: 80px;">
                                            Air Date
                                        </th>
                                    </tr>
                                </thead>
                                <tbody id="table">
                                    <tr v-if="entriesValid" v-for="entry in entries">
                                        <td>{{entry.questionText}}</td>
                                        <td>{{entry.answerText}}</td>
                                        <td>{{entry.dollarValue}}</td>
                                        <td>{{entry.showNumber}}</td>
                                        <td>{{entry.categoryTitle}}</td>
                                        <td>{{entry.airDate}}</td>
                                    </tr>
                                    <tr v-if="!entriesValid">
                                        <td colspan="6" style="text-align: center;"> No entries to display </td>
                                    </tr>
                                </tbody>
                            </table>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <div style="position: absolute; left: 45%; top:25%; z-index:3;">
        <i id="loader" class="fa fa-circle-o-notch fa-spin fa-5x fa-fw toggled" style="z-index:3"></i>
    </div>
</section>

更新了JS

    Vue.use(Vuex)
    Vue.config.debug = false;
    Vue.config.silent = true;
    var URL;
    const store = new Vuex.Store({
        state: {
            loggedIn: false,

            // ordering data
            questionSort: 0,
            answerSort: 0,
            valueSort: 0,
            showNumberSort: 0,
            categorySort: 0,
            airDateSort: 0,

            // server related ata
            entries: [],
            url: "/questions",
            categories: [{
                categoryTitle: "Test",
            }],
        },
        mutations: {
            categories (state, data) {
                state.categories = data;
            },
            entries (state, data) {
                console.log(data);
                state.entries = data;
                console.log(state.entries)
            }
        },
        actions: {
            fetchCategories ({ commit }) {
                $("#loader").removeClass("toggled");
                $.get("/api/categories", function(result) {
                    commit('categories', result.data);
                }, "json")
                .fail(function(err) {
                    if (err.status == 0) {
                        showErrorMessage("Network Problem");
                    }
                    else {
                        showErrorMessage(err.responseJSON.message_prettified);
                    }
                }).always(function() {
                    $("#loader").addClass("toggled");
                });
            },
        },
    });

    var app = new Vue({
        el: '#body',
        store: store,
        data: {
            categorySelect: "",
        },
        mounted: function() {
            store.dispatch("fetchCategories").then(() => {
                $('.js-example-basic-single').select2();
            });
        },
        computed: {
            categories: function() {
                return store.state.categories;
            },
            entryCount: function() {
                if (store.entries) {
                    if (typeof store.entries.length !== "undefined") {
                        return store.entries.length;
                    }
                    else {
                        return 0;
                    }
                }
                else {
                    return 0;
                }
            },
            entriesValid: function() {
                if (store.state.entries) {
                    if (typeof store.state.entries.length !== "undefined" && store.state.entries.length > 0) {
                        return true;
                    }
                    else {
                        return false;
                    }
                }
                else {
                    return false;
                }
            },
            entries: function() {
                return store.state.entries;
            },
            loggedIn: function() {
                return store.state.loggedIn;
            },
        },
        methods: {
            reset: function() {
                $('.js-example-basic-single').val('').trigger('change');
                $("#datetimepicker1").datetimepicker("clear");
                $("#categoryTitleHidden").val("");
                $("#showNumber").val("");
                $("#questionText").val("");
                $("#showNumber").val("");
                $("#dollarValue").val("");
            },
            filter : function() {
                var value = $('#category :selected').text().trim();
                if (value !== "Any") {
                    $("#categoryTitleHidden").val(value);
                }
                else {
                    $("#categoryTitleHidden").val("");   
                }
                var options = {   
                    success: function(responseText, statusText, xhr, $form) {
                        store.commit("entries", JSON.parse(xhr.responseText).data)
                    } 
                }; 
                $("#filterForm").ajaxSubmit(options);
            }
        }
    });

2 个答案:

答案 0 :(得分:0)

您的代码无法运行,因为它依赖于您的服务器响应。

但我认为将您的共鸣数据设置为entries的代码很好。

当其他js代码导致异常时,可能会出现这种问题,从而中断了vue的渲染。

所以你可以查看控制台,看看有没有错误?

答案 1 :(得分:0)

IMO问题导致Vue.set(app,...)。 AFAIK您无法在Vue实例本身上设置属性。

编辑:带有Vuex的实例和使用jQuery的异步数据

var store = new Vuex.Store({
  state: {
    // Car manufacturers for datalist will be held here.
    // Cars are globally accessible, in every component,
    // as this.$store.state.cars
    cars: null
  },
  mutations: {
    // Mutations changes state, but must be sync,
    // so you can't call $.get() or another 
    // async function in any mutation.
    updateCars: function (state, payload) {
      state.cars = payload
    }
  },
  actions: {
    // For async ops there are actions,
    // but they can't change state - for state
    // change fire particular mutation.
    loadCars: function (context, payload) {
      $.get(payload.src).then(function (data) {
        context.commit('updateCars', data)
      })
    }
  }
})

Vue.component('my-list', {
  template: '#my-list',
  props: ['src'],
  // All components see this.$store.state.cars, but
  // still can have own local data.
  data: function () {
    return {
      manufacturer: ''
    }
  },
  // Fire async store action
  created: function () {
    this.$store.dispatch({
      type: 'loadCars',
      src: this.src
    })
  }
  // Alternatively, you can use this
  // version - w/o action. It's OK to use
  // mutation here, in callback of async function.
  /* created: function () {
    $.get(this.src).then(function (data) {
      this.$store.commit('updateCars', data)
    })
  } */
})

new Vue({
  el: '#app',
  // Inject store state to all components
  store: store
})
<div id="app">
  <my-list src="https://api.mockaroo.com/api/32318a80?count=20&key=cdbbbcd0">
  </my-list>
</div>

<template id="my-list">
  <div>
    <label>
      Choose a car manufacturer:<br>
      <input list="cars" name="myCars" v-model="manufacturer">
    </label>
    <datalist id="cars">
      <option
        v-for="car in $store.state.cars"
        :value="car.car"
      >
        {{ car.id }}
      </option>
    </datalist>
    <p>
      Selected manufacturer:<br>
      {{ manufacturer }}
    </p>
  </div>
</template>

<script src="https://unpkg.com/vue@2.5.2/dist/vue.min.js"></script>
<script src="https://unpkg.com/vuex@3.0.0/dist/vuex.min.js"></script>
<script src="https://unpkg.com/jquery@3.2.1/dist/jquery.min.js"></script>