【问题标题】:What causes VueJS to stop updating the DOM in this instance?是什么导致 VueJS 在这种情况下停止更新 DOM?
【发布时间】:2018-04-06 05:28:05
【问题描述】:

我查看了所有可以找到的类似帖子,但似乎没有任何答案可以解决我的问题。具体来说,它不会更新 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“没有要显示的条目”单元格。但是,在发送并返回过滤器请求后,表格不会更新以反映更新的数据(尽管在控制台中检查时数据正确显示为已更改)。

预期行为:

当使用查询参数对 /questions 的 AJAX 调用解析并更新应用程序中的条目数据字段时,表会更新以反映更改。

尝试的修复:

探索了 $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);
            }
        }
    });

【问题讨论】:

    标签: javascript jquery ajax vue.js


    【解决方案1】:

    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>

    【讨论】:

    • 我尝试进行直接分配(app.entries = result.data),但这也不起作用。我已经阅读了一些 Vue.set 函数可能会触发 DOM 的强制更新(没有工作)。你会推荐什么?
    • 这是因为你也不能在 Vue 实例的根数据对象上设置属性......使用 Vuex,这是你能做的最好的事情。
    • 我会给 Vuex 一个机会,看看是否能解决问题。感谢您的建议!试一试后我会检查的。
    • 如果你愿意,我可以在这里写一个例子。异步数据的基本 Vuex 用法。
    • 嗨 WaldemarIce。感谢所有的帮助。在经历了极大的挫折之后,我确实让它工作了,只是为了以后看到这个的人,我会在原始帖子中发布我的发现。问题最终是错误引用根范围中已经存在的数据和从 AJAX 调用加载数据之间的混合问题。
    【解决方案2】:

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

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

    这种问题可能在其他js代码出现异常时出现,中断vue的渲染。

    所以你能检查一下控制台,看看有没有错误?

    【讨论】:

    • 服务器响应本质上只是一个 JSON 对象数组,我将发布控制台图像,显示 AJAX 解析后应用程序具有的内容 Post AJAX 条目内容:imgur.com/a/Qcuby JSON 模式示例imgur.com/a/V9F91
    • 有一个 VueJS 错误,但是根据我可以用谷歌搜索的内容,这与加载之前的数据引用有关,并且在加载后自行解决。我将在此处提供错误的照片。 imgur.com/a/OEVuF
    猜你喜欢
    • 2021-08-15
    • 2013-04-23
    • 1970-01-01
    • 2021-12-03
    • 1970-01-01
    • 2012-01-24
    • 1970-01-01
    • 2020-04-04
    • 2017-03-28
    相关资源
    最近更新 更多