【问题标题】:Vue doesn't update DOM upon 'indirectly' changing the value of an expressionVue 不会在“间接”更改表达式的值时更新 DOM
【发布时间】:2018-06-23 07:53:50
【问题描述】:

TL;DR

我正在尝试从 JSON 动态构建 UI。 JSON 表示具有应用程序状态(变量)和以这些变量为条件的 UI 构建逻辑的 vue.js 应用程序。

"type": "switch" 的 JSON 对象(参见下面链接的小提琴),根据状态变量 "variable": "key" /*translates to vueApp.key */ 的值,指示 vue.js 应用显示多个 "cases": {"case1": {..}, "case2": {..}} 之一。

更改其中一个变量 (update_status) 最初会导致 DOM 更新。遗憾的是,在安装应用程序后再次更改它不会影响 DOM。我很确定我在做一些愚蠢的事情或遗漏了一些微妙的事情。


稍长的版本

(如果你还在读这篇文章,please look at the fiddle 在这一点上。没有它,下面的任何内容都没有意义。谢谢!)

Vue.js 模板(带有app.variables.update_status = "available"

<script type="text/x-template" id="template-switch">
  <div>
      <!-- Debug statements -->
      Switch cases: {{data.cases}}<br>
      Variables: {{$root.variables}}


      <div v-for="(value, key) in data.cases">
          <div v-bind:class="$root.variables[data.variable]"
               v-if="key == $root.variables[data.variable]">
              <all-components v-bind:data="value"></all-components>
          </div>
      </div>
  </div>
</script>

输入 JSON(在上述模板中绑定为 data):

{
    // Switch on value of app.variables.update_status
    "type": "switch",
    "variable": "update_status",   // Refers to app.variables.update_status
                                   // Used in <script id="template-switch">
    "cases": {
        // if app.variables.update_status == "checking" (Initial value)
        "checking": {
          "type": "paragraph",
          "text": "Checking for updates"
        },
        // if app.variables.update_status == "available" (Changed below)
        "available": {
            "type": "paragraph",
            "text": "Updates available."
        }
    }
}

我的问题:

假设 app 是 Vue.js 应用程序,我希望设置 app.variables.update_status = "available" 会导致 DOM 更改。但它不像 TL;DR 部分中描述的那样。我希望了解原因。

我的尝试

  • 让应用监视对象下的整个层次结构。
  • 我最初认为这是因为 Vue 无法监控 object[key] 表达式,其中 key 可以更改。 But its definitely able to do it.
  • 显示安装应用程序之前设置的最后一个值。安装应用程序后,对变量的任何更改都会保留,但不会触发 DOM 更新。 (在小提琴中标有“不起作用!”。)

试试看!

这里是 JS Fiddle(大幅缩小,并为更容易理解添加了评论:))

尝试什么:

小提琴运行后,打开浏览器控制台并尝试执行以下语句:

  • DEBUG.variables.update_status = "available";
  • DEBUG.variables.update_status = "checking";

Vue.js 版本:2.5.16


更新

另外,我刚刚发现如果我将数据对象传递为:

new Vue({.., data: { .. , variables: {update_status: "temp"}}})

– 有效!

我不明白这一点,主要是因为变量字段设置为有深度观察者。我假设当它的字段更新时(例如variables.update_status = "new-value";),观察者最终会触发 DOM 更新。但由于某种原因,这不会发生。

我真的希望我在做一些愚蠢的事情,这不是一个错误。

显示此行为的新 Fiddle 的链接:https://jsfiddle.net/g0z3xcyk/

【问题讨论】:

  • 在第二个小提琴中,当您实例化Vue 时,您正在设置update_status 属性,而在第一个小提琴中您没有。还有vue can't detect property addition or deletion
  • 检查this fiddle,正如@LuisOrduz 所说,Vue 无法检测到属性添加或删除。这就是为什么第一个小提琴不起作用的原因。所以一个解决方案是像你的第二个小提琴那样首先贴上它,另一个解决方案是使用Vue.setvm.$set。然后你不需要使用 JQUERY 将 $el 附加到目标 dom 对象,只需使用 app.$mount(selector)
  • 是的,stackoverflow.com/questions/49143631/… 的另一个变体(希望我能找到更规范的答案……)。一个更简单的改动(使用Vue.set)和一个交互按钮:jsfiddle.net/xo7rhpec

标签: javascript vue.js


【解决方案1】:

它不会在您的第一个小提琴中更新的原因是因为 Vue 没有检测到属性添加或删除,并且您在实例化 vue 时没有传递 update_status 属性,the docs explain it further

在您的第二把小提琴中,您在实例化 vue 时设置了 update_status,这就是在这种情况下检测到更改的原因。

如文档中所述,另一种选择是使用 Vue.set 或通过再次使用 Object.assign 重新分配对象来完全重新创建对象

【讨论】:

  • 非常感谢!我回家后测试一下。 :)
【解决方案2】:

您的代码存在一些问题

  1. 检查Reactivity In depth @LuisOrduz 评论并回答,Vue 无法检测到属性添加或删除。所以有两个解决方案: 首先声明它(就像你的第二个小提琴那样),或者使用 Vue.setvm.$set 添加一个属性。

  2. 使用vm.$mount(selector)而不是使用JQuery追加vm.$el;检查vm.$mount

  3. 最好使用vm.$data而不是vm[key]来访问数据属性;检查vm.$data

以下是一个演示

function registerComponents() {
    Vue.component('all-components', {
      template: '#template-all-components',
      props: ['data']
    });
    Vue.component('weave-switch', {
      template: '#template-switch',
      props: ['data'],
      methods: {
        toggleStatus: function () {
          this.$root.$data.variables.update_status += ' @'
        }
      }
    });
    Vue.component('paragraph', {
      template: '#template-paragraph',
      props: ['data']
    });
}

function GenericCard(selector, options) {
    var data = Object.assign({}, options.data, {variables: {}});
    var watch = {};
    Object.keys(data).forEach(function(key) {
        watch[key] = {handler: function(val) {
        }, deep: true};
    });
    var app = new Vue({
        template: options.template,
        data: function () { // uses function instead
            return data
        },
        watch: watch
    });
    
    DEBUG = app;

    return {
        load: function(data) {
            Object.keys(data).forEach(function(key) {
                app.$data[key] = data[key];
            });
            
            //app.$data.variables.update_status = "checking"; // orginal method
            
                        app.$set(app.$data.variables, 'update_status', 'checking') // new solution
            app.$mount(selector);
            
            //var dom = app.$el;
            //$(selector).append(dom); // uses vm.$mount(selector) instead
            
            DEBUG.$set(DEBUG.$data.variables, 'update_status', 'available')  // new solution
            //DEBUG.$data.variables.update_status = 'available1'  // or new solution
            //DEBUG.variables.update_status = "available";  // orginal method
        },
        DEBUG: DEBUG
    };
}

registerComponents();

card = GenericCard('#app', {
  template: "#template-card",
  data: {
    ui: {}
  }
});
card.load({
        ui: {
      // Switch on value of app.variables.update_status
      "type": "switch",
      "variable": "update_status",   // Refers to app.variables.update_status
                                     // Used in <script id="template-switch">
            "cases": {
        // if app.variables.update_status == "checking" (Initial value)
        "checking": {
          "type": "paragraph",
          "text": "Checking for updates"
        },
        // if app.variables.update_status == "available" (Changed below)
        "available": {
          "type": "paragraph",
          "text": "Updates available."
        }
      }
    }
  });
Vue.config.productionTip = false

function toggleStatus() {
  card.DEBUG.$data.variables.update_status += ' #'
}
<script src="https://unpkg.com/vue@2.5.16/dist/vue.js"></script>
<script type="text/x-template" id="template-all-components">
  <div v-if="data.type == 'paragraph'">
      <paragraph v-bind:data="data.text"></paragraph>
  </div>
  <div v-else-if="data.type == 'switch'">
      <weave-switch v-bind:data="data"></weave-switch>
  </div>
</script>
<script type="text/x-template" id="template-switch">
  <div>
        <!-- Debug statements -->
      Switch cases: {{data.cases}}<br>
      Variables: {{$root.variables}}
      <button @click="toggleStatus()">Toggle</button>
      
      <div v-for="(value, key) in data.cases">
          <div v-bind:class="$root.$data.variables[data.variable]"
               v-if="key == $root.$data.variables[data.variable]">
              <all-components v-bind:data="value"></all-components>
          </div>
      </div>
  </div>
</script>
<script type="text/x-template" id="template-paragraph">
  <p>{{data}}</p>
</script>

<script type="text/x-template" id="template-card">
  <all-components v-bind:data="ui"></all-components>
</script>


<div id="app">
  
</div>

<button onclick="toggleStatus()">Toggle2</button>

【讨论】:

    猜你喜欢
    • 2021-04-01
    • 2021-05-21
    • 2021-07-29
    • 2018-10-06
    • 2021-11-11
    • 1970-01-01
    • 2021-10-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多