如何让Knockout在全局变量data变更时更新绑定的输入框
data Object 问题描述
我用Knockout绑定了一批输入框,初始化ViewModel时从全局data对象取值给observables,同时给每个observable加了订阅,把更新后的值同步回data。但当我直接修改data.visibleByRegisteredUsers.latitude这类属性时,对应的输入框并没有同步更新,请问我的配置哪里有问题?
我的ViewModel代码:
function AppViewModel() { this.username = ko.observable(data.user.name); this.name = ko.observable(data.visibleByRegisteredUsers.name); this.phone = ko.observable(data.visibleByRegisteredUsers.phone); this.gender = ko.observable(data.visibleByRegisteredUsers.gender); this.distance = ko.observable(data.visibleByRegisteredUsers.distance); this.address = ko.observable(data.visibleByRegisteredUsers.address); this.postcode = ko.observable(data.visibleByRegisteredUsers.postcode); this.latitude = ko.observable(data.visibleByRegisteredUsers.latitude); this.longitude = ko.observable(data.visibleByRegisteredUsers.longitude); this.name.subscribe(function (newData) { data.visibleByRegisteredUsers.name = newData; }); this.phone.subscribe(function (newData) { data.visibleByRegisteredUsers.phone = newData; }); this.gender.subscribe(function (newGender) { data.visibleByRegisteredUsers.gender = newGender; }); this.distance.subscribe(function (newData) { data.visibleByRegisteredUsers.distance = newData; }); this.address.subscribe(function (newData) { data.visibleByRegisteredUsers.address = newData; }); this.postcode.subscribe(function (newData) { data.visibleByRegisteredUsers.postcode = newData; }); this.latitude.subscribe(function (newData) { data.visibleByRegisteredUsers.latitude = newData; }); this.longitude.subscribe(function (newData) { data.visibleByRegisteredUsers.longitude = newData; }); } ko.applyBindings(new AppViewModel());
输入框绑定示例:
<input type="text" class="form-control" placeholder="Lat" id="lat" aria-describedby="basic-addon1" required data-bind="value:latitude">
问题原因
你的代码只实现了从ViewModel到全局data的单向同步:当输入框修改触发observable更新时,订阅函数会把新值同步到data。但反过来,直接修改data的普通JS属性时,Knockout完全感知不到这个变化——因为Knockout的响应式系统只对ko.observable/ko.observableArray这类特殊对象生效,普通JS属性的修改不会触发任何通知,ViewModel里的observable自然不会更新,输入框也就不会同步变化。
解决方案
方案1:把全局data的属性改成observable(推荐)
让data里的目标属性本身就是Knockout observable,这样ViewModel直接引用这些observable,实现真正的双向同步,不需要手动写订阅函数:
// 先把全局data的属性转换为observable data.user.name = ko.observable(data.user.name); data.visibleByRegisteredUsers.name = ko.observable(data.visibleByRegisteredUsers.name); data.visibleByRegisteredUsers.phone = ko.observable(data.visibleByRegisteredUsers.phone); data.visibleByRegisteredUsers.gender = ko.observable(data.visibleByRegisteredUsers.gender); data.visibleByRegisteredUsers.distance = ko.observable(data.visibleByRegisteredUsers.distance); data.visibleByRegisteredUsers.address = ko.observable(data.visibleByRegisteredUsers.address); data.visibleByRegisteredUsers.postcode = ko.observable(data.visibleByRegisteredUsers.postcode); data.visibleByRegisteredUsers.latitude = ko.observable(data.visibleByRegisteredUsers.latitude); data.visibleByRegisteredUsers.longitude = ko.observable(data.visibleByRegisteredUsers.longitude); // ViewModel直接引用全局observable function AppViewModel() { this.username = data.user.name; this.name = data.visibleByRegisteredUsers.name; this.phone = data.visibleByRegisteredUsers.phone; this.gender = data.visibleByRegisteredUsers.gender; this.distance = data.visibleByRegisteredUsers.distance; this.address = data.visibleByRegisteredUsers.address; this.postcode = data.visibleByRegisteredUsers.postcode; this.latitude = data.visibleByRegisteredUsers.latitude; this.longitude = data.visibleByRegisteredUsers.longitude; } ko.applyBindings(new AppViewModel());
这样不管是修改输入框(触发observable更新,同步到data),还是直接修改data.visibleByRegisteredUsers.latitude("新值")(触发observable更新,同步到输入框),两边都会自动同步。
方案2:手动更新ViewModel的observable(适合无法修改全局data的场景)
如果不能修改全局data的结构,那每次手动修改data后,需要主动调用ViewModel里对应的observable的赋值方法,通知Knockout更新UI:
// 保存ViewModel实例的引用,方便后续调用 var viewModel = new AppViewModel(); ko.applyBindings(viewModel); // 当你需要修改data并同步到输入框时: data.visibleByRegisteredUsers.latitude = "39.9042"; // 手动更新ViewModel里的observable viewModel.latitude(data.visibleByRegisteredUsers.latitude);
内容的提问来源于stack exchange,提问作者Tony Goodchild

