Knockout计算属性在加载时触发

时间:2017-09-22 10:09:21

标签: javascript knockout.js

我从敲门开始,我的计算观察者似乎总是在视图模型被实例化时触发,我不知道为什么。

我将问题简化为荒谬只是为了测试:计算属性只是在控制台中打印一条消息,并且没有绑定到DOM的任何元素。这是:

(function() {
    function HomeViewModel() {
        var self = this;

(...)

        self.FullName = ko.computed(function () {
            console.log("INSIDE");
        });

(...)

    };
    ko.applyBindings(new HomeViewModel());
})();

如何避免?

更新

以下是ViewModel的完整代码,仅供您更好地理解:

function HomeViewModel() {
    var self = this;

    self.teachers = ko.observableArray([]);
    self.students = ko.observableArray([]);

    self.FilterByName = ko.observable('');
    self.FilterByLastName = ko.observable('');

    self.FilteredTeachers = ko.observableArray([]);
    self.FilteredStudents = ko.observableArray([]);

    self.FilteredUsersComputed = ko.computed(function () {
        var filteredTeachers = self.teachers().filter(function (user) {                
            return (user.name.toUpperCase().includes(self.FilterByName().toUpperCase()) &&
                user.lastName.toUpperCase().includes(self.FilterByLastName().toUpperCase())
            );
        });
        self.FilteredTeachers(filteredTeachers); 
        var filteredStudents = self.students().filter(function (user) {
            return (user.name.toUpperCase().includes(self.FilterByName().toUpperCase()) &&
                user.lastName.toUpperCase().includes(self.FilterByLastName().toUpperCase())
            );
        });
        self.FilteredStudents(filteredStudents);
        $("#LLAdminBodyMain").fadeIn();
    }).extend({ rateLimit: { method: "notifyWhenChangesStop", timeout: 800 } });

    self.FilteredUsersComputed.subscribe(function () {
        setTimeout(function () { $("#LLAdminBodyMain").fadeOut(); }, 200);
    }, null, "beforeChange");

    $.getJSON("/api/User/Teacher", function (data) {            
        self.teachers(data);   
    });
    $.getJSON("/api/User/Student", function (data) {
        self.students(data);
    });
}

ko.applyBindings(new HomeViewModel());

})();

我需要它不能在加载时执行,因为在加载时self.studentsself.teachers数组不会被填充。

注意:只想突出显示两个代码(荒谬和完整),计算属性在加载时执行(或首次实例化ViewModel时)。

1 个答案:

答案 0 :(得分:1)

您的方法存在两个主要错误。

  • 您有一个针对过滤用户的单独observable。这不是必要的。 ko.computed将填充该角色,无需将计算结果存储在任何位置。 (计算器被缓存,它们在内部存储自己的值。重复调用计算器不会重新计算其值。)
  • 您正在从视图模型中与DOM进行交互。通常应该避免这种情况,因为它将视图模型耦合到视图。视图模型应该能够在不知道如何渲染的情况下进行操作。

小点/改进建议:

  • 不要对过滤结果进行评级。对包含过滤字符串的observable进行速率限制。
  • 不要调用您的计算属性...Computed - 这与您的观点无关,没有理由指出它。对于视图中的所有实际用途,计算和观察是完全相同的。
  • 如果教师和学生是同一个东西,即用户对象要显示在同一个列表中,为什么要将它们放在两个单独的列表中?在viewmodel中有一个列表是否更有意义,所以你不需要过滤两次?
  • Observables是函数。这意味着 $.getJSON("...", function (data) { someObservable(data) });
    可缩短为
    $.getJSON("...", someObservable);

这是一个更好的viewmodel:

function HomeViewModel() {
    var self = this;

    self.teachers = ko.observableArray([]);
    self.students = ko.observableArray([]);

    self.filterByName = ko.observable().extend({ rateLimit: { method: "notifyWhenChangesStop", timeout: 800 } });
    self.filterByLastName = ko.observable().extend({ rateLimit: { method: "notifyWhenChangesStop", timeout: 800 } });

    function filterUsers(userList) {
        var name = self.filterByName().toUpperCase(),
            lastName = self.filterByLastName().toUpperCase(),
            allUsers = userList();

        if (!name && !lastName) return allUsers;

        return allUsers.filter(function (user) {
            return (!name || user.name.toUpperCase().includes(name)) &&
                (!lastName || user.lastName.toUpperCase().includes(lastName));
        });
    }

    self.filteredTeachers = ko.computed(function () {
        return filterUsers(self.teachers);
    });
    self.filteredStudents = ko.computed(function () {
        return filterUsers(self.students);
    });
    self.filteredUsers = ko.computed(function () {
        return self.filteredTeachers().concat(self.filteredStudents());
        // maybe sort the result?
    });

    $.getJSON("/api/User/Teacher", self.teachers);
    $.getJSON("/api/User/Student", self.students);
}

有了它,不再重要的是立即计算计算结果。您可以将视图绑定到filteredTeachersfilteredStudentsfilteredUsers,视图将始终反映事态。

当涉及到使用户界面元素对viewmodel状态更改做出反应时,无论反应是“更改HTML”还是“淡入/淡出”都没有区别。这不是viewmodel的工作。它始终是绑定的任务。

如果没有符合您要求的“股票”绑定,make a new one。这个是直接来自the examples in the documentation

// Here's a custom Knockout binding that makes elements shown/hidden via jQuery's fadeIn()/fadeOut() methods
// Could be stored in a separate utility library
ko.bindingHandlers.fadeVisible = {
    init: function(element, valueAccessor) {
        // Initially set the element to be instantly visible/hidden depending on the value
        var value = valueAccessor();
        $(element).toggle(ko.unwrap(value)); // Use "unwrapObservable" so we can handle values that may or may not be observable
    },
    update: function(element, valueAccessor) {
        // Whenever the value subsequently changes, slowly fade the element in or out
        var value = valueAccessor();
        ko.unwrap(value) ? $(element).fadeIn() : $(element).fadeOut();
    }
};

它根据绑定值淡入/淡出绑定元素。空数组[]的计算结果为false是可行的,因此您可以在视图中执行此操作:

<div data-bind="fadeVisible: filteredUsers">
  <!-- show filteredUsers... --->
</div>

在绑定值更改之前和之后淡化元素的自定义绑定如下所示。

  • 我们在绑定的init阶段订阅了值。
  • 绑定中没有update阶段,它需要做的一切都是通过订阅完成的。
  • 当DOM元素消失时(例如,因为更高的ifforeach绑定触发器),我们的绑定也会清除订阅。

我们称之为fadeDuringChange

ko.bindingHandlers.fadeDuringChange = {
    init: function(element, valueAccessor) {
        var value = valueAccessor();

        var beforeChangeSubscription = value.subscribe(function () {
            $(element).delay(200).fadeOut();
        }, null, "beforeChange");

        var afterChangeSubscription = value.subscribe(function () {
            $(element).fadeIn();
        });

        // dispose of subscriptions when the DOM node goes away
        // see http://knockoutjs.com/documentation/custom-bindings-disposal.html
        ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
            // see http://knockoutjs.com/documentation/observables.html#explicitly-subscribing-to-observables
            beforeChangeSubscription.dispose();
            afterChangeSubscription.dispose();
        });
    }
};

用法与上述相同:

<div data-bind="fadeDuringChange: filteredUsers">
  <!-- show filteredUsers... --->
</div>