【问题标题】:Why does vue.js not update the dom with datepicker using moment.js为什么 vue.js 不使用 moment.js 使用 datepicker 更新 dom
【发布时间】:2017-04-18 23:22:51
【问题描述】:

我正在 vue.js 中编写一个(非常)简单的 datepicker 控件,使用 moment.js 来格式化和改变日期。

我遇到的问题是,即使当我单击任一按钮时修改了组件中的日期实例,显示也不会更新。

我尝试将其更改为简单的整数而不是日期实例,并且按预期工作(DOM 已正确更新)

这是我的源代码:

App.js

Vue.component("datePicker", {
    props: ["value"],
    data: function() {
        return { selectedDate: moment(this.value) };
    },
    template: "<div><button v-on:click='decrement'>&lt;</button>{{formattedSelectedDate}}<button v-on:click='increment'>&gt;</button></div>",
    methods: {
        increment: function () {
            this.selectedDate.add(1, "days");
            this.$emit('input', this.selectedDate);
        },
        decrement: function () {
            this.selectedDate.subtract(1, "days");
            this.$emit('input', this.selectedDate);
        }
    },
    computed: {
        formattedSelectedDate: function() {
            return this.selectedDate.format("YYYY-MM-DD");
        }
    }
});

var PointTracker = new Vue({
    el: "#PointTracker",
    data: {
        selectedDate: moment(),
        todoItems: {}
    }
});

索引.html

<html>
    <head>
        <meta charset="utf-8">
        <title>Point Tracker</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>

        <div id="PointTracker">
            <date-picker v-model="selectedDate">
        </div>

        <script src="node_modules/moment/moment.js"></script>
        <script src="node_modules/vue/dist/vue.js"></script>
        <script src="node_modules/vue-resource/dist/vue-resource.js"></script>
        <script src="app.js"></script>
    </body>
</html>

【问题讨论】:

  • 我已经为你的代码创建了一个fiddle,我还在 div 中添加了更多元素,不知道为什么它们没有呈现。

标签: javascript vue.js


【解决方案1】:

您必须更改 selectedDate 的引用,因为它是从 moment 函数返回的,它们始终具有相同的引用,因此不会为这些触发 vue 观察者。

您必须进行以下更改才能更改参考:

methods: {
    increment: function () {
        this.selectedDate = moment(this.selectedDate).add(1, "days")
    },
    decrement: function () {
        this.selectedDate = moment(this.selectedDate).subtract(1, "days")
    }
},

工作小提琴:http://jsfiddle.net/mimani/pLcfyrvy/1/


以下是来自moment 库的add/subtract 的实现:

function addSubtract (duration, input, value, direction) {
    var other = createDuration(input, value);

    duration._milliseconds += direction * other._milliseconds;
    duration._days         += direction * other._days;
    duration._months       += direction * other._months;

    return duration._bubble();
}

// supports only 2.0-style add(1, 's') or add(duration)
export function add (input, value) {
    return addSubtract(this, input, value, 1);
}

// supports only 2.0-style subtract(1, 's') or subtract(duration)
export function subtract (input, value) {
    return addSubtract(this, input, value, -1);
}

它返回相同的对象,因此日期对象的引用是相同的。


为什么会发生

发生这种情况是因为 moment.js 是一个 isn't immutable,这意味着当您在更改属性后对 moment 对象 it returns the same object 调用 add/subtract 函数时。

vue 的 reactivity 上有一些 caveats,由于 Object.observe 现在已过时,它无法跟踪 javascript 对象是否在内部发生了更改,除非您克隆该对象并创建一个新的对象你的情况。

还有其他 workarounds 也为此使用,包括使用 frozen-moment 库。

【讨论】:

  • 这种方法有效,但我有一个额外的澄清问题:为什么 vue 需要更改引用?底层属性的改变还不够吗?或者他们没有被监控
  • @Patrick 发生这种情况是因为 moment 库是可变的,我在答案中添加了解释。
  • 我接受了这个答案,因为有额外的信息。最后一个问题,以确保我理解这一点,如果道具类似于 { field: "abc" } 并且我将 this.field 更改为 "cde" 它也不会更新 dom?
  • @Patrick 你想要的是Dynamic-Props,语法是:v-bind:my-field="field",我希望这就是你要问的。作为标准 SO 指南,如果问题仍然存在,我建议您提出一个单独的问题,包括详细信息和重现步骤。
  • 如果已经怀疑这个问题,感谢您提出这个问题。不知何故,我仍然使用输入字段来处理反应性,所以我为此苦苦挣扎了很长时间。
【解决方案2】:

问题是对 selectedDate 的引用永远不会改变。即使您在其上调用方法,它也是同一个对象。
这里的一种方法是以不同的格式存储日期。作为日期对象、数字等。然后,当您对其进行更改时,您可以生成该数据的新实例。

这是一个例子:

var datePicker = Vue.component("datePicker", {
    data: function() {
        return { selectedDate: moment().toDate() }
    },
    template: `<div>
        <button v-on:click='decrement'>&lt;</button>
          {{formattedSelectedDate}}
        <button v-on:click='increment'>&gt;</button>
        {{selectedDate}}
      </div>`,
    methods: {
        increment: function () {
            var currentDate = moment(this.selectedDate);
            currentDate.add(1, "days");
            this.selectedDate = currentDate.toDate();
        },
        decrement: function () {
            var currentDate = moment(this.selectedDate);
            currentDate.subtract(1, "Days");
            this.selectedDate = currentDate.toDate();
        }
    },
    computed: {
        formattedSelectedDate: function() {
            return moment(this.selectedDate).format("YYYY-MM-DD");
        }
    }
});

var PointTracker = new Vue({
    el: "#PointTracker",
    data: {
        date: 0
    },
    components: {
        datePicker
    }
});
<div id="PointTracker">
  <date-picker />
</div>

<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.17.1/moment.js"></script>

【讨论】:

    【解决方案3】:

    这是因为 moment 和 vue 都依赖于定义对象的 getter 和 setter,这会导致在您的情况下一个覆盖另一个。 (因此更改参考有效,因为 vm.selectedDate.set 被覆盖,而 vm.set 未被覆盖)

    来自瞬间的doc

    Moment.js 使用重载的 getter 和 setter。

    来自vue的doc

    Vue 会递归地将其属性转换为 getter/setter 以使其“反应”。对象必须是普通的:浏览器 API 对象和原型属性等原生对象将被忽略。一个经验法则是数据应该只是数据——不建议观察具有自身状态行为的对象。`

    快速验证:我在创建的小提琴 saurabh 中添加了console.log(this.selectedDate.get),记录的函数是moment.js 中的stringGet,然后我在我的项目中检查了vm.fooget,它是proxyGetterreactiveGetter 的包装器,都在vue.runtime.common.js 中。

    【讨论】:

      猜你喜欢
      • 2020-04-30
      • 2019-05-22
      • 1970-01-01
      • 2018-02-12
      • 1970-01-01
      • 2017-02-09
      • 2018-04-28
      • 2017-10-29
      • 2018-07-03
      相关资源
      最近更新 更多