可观察数组中的可观察视图模型在 KnockoutJs 中不起作用
Observable viewmodels in observable array don't work in KnockoutJs
我有简单的 HTML 标记,其中包含我的视图模型列表和模板。另外,我在 <pre>
部分有我的视图模型:
<div class="container">
<div class="row">
<div class="col-md-6">
<div class="col-md-12">
<div class="panel panel-primary">
<div class="panel-heading">
Names
<button type="button" class="btn btn-link" data-bind="click: addName">
<span class="glyphicon glyphicon-plus"></span>
</button>
</div>
<div class="panel-body">
<ul class="list-group" id="namesList" data-bind="foreach: Names">
<li class="list-group-item"><my-comp></my-comp></li>
</ul>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="col-md-12">
<pre data-bind="text: ko.toJSON(Names, null, 2)"></pre>
</div>
</div>
</div>
</div>
<script type="text/html" id="fullNameTmpl">
<p>First name: <strong data-bind="text: firstName"></strong></p>
<p>Last name: <strong data-bind="text: lastName"></strong></p>
<p>First name: <input data-bind="textInput: firstName" /></p>
<p>Last name: <input data-bind="textInput: lastName" /></p>
<p>Full name: <input data-bind="textInput: fullName"></p>
<button data-bind="click: capitalizeLastName">Go caps</button>
</script>
<script>
function AppViewModel() {
var self = this;
self.firstName = ko.observable("Bert");
self.lastName = ko.observable("Bertington");
this.fullName = ko.pureComputed({
read: function () {
return self.firstName() + " " + self.lastName();
},
write: function (value) {
var lastSpacePos = value.lastIndexOf(" ");
if (lastSpacePos > 0) {
self.firstName(value.substring(0, lastSpacePos));
self.lastName(value.substring(lastSpacePos + 1));
}
},
owner: self
});
self.capitalizeLastName = function () {
var currentVal = this.lastName();
this.lastName(currentVal.toUpperCase());
};
}
ko.components.register('my-comp', {
viewModel: AppViewModel,
template: { element: "fullNameTmpl" }
});
function NamesViewModel() {
var self = this;
self.Names = ko.observableArray();
self.addName = function () {
var vm = ko.observable(new AppViewModel());
self.Names.push(vm);
}
}
ko.applyBindings(new NamesViewModel());
</script>
效果很好,我可以将一个项目添加到列表中,并且这些项目中的数据绑定有效。当我添加新的 AppViewModel
时,我可以在 <pre>
部分的 Names
数组中看到它。问题是,当我在嵌套视图模型中更改某些值时,比如输入新的姓氏等等,Names
数组中当前视图模型的 <pre>
部分没有任何变化。
如何制作一个真正可观察的可观察数组?添加这样的组件最有利可图的方法是什么?我已经完成了可观察的嵌套视图模型,但它似乎不起作用。
您的代码有几个问题。问题不一定出在可观察数组上——这是您使用组件的问题。列出了组件的用途 here。一些注意事项:
- 不需要制作可观察的数组元素
observable
。所以不需要做 var vm = ko.observable(new AppViewModel());
只需使用 var vm = new AppViewModel();
并推入你的数组。但是,如果您想使用一个组件,那么这也不正确!正在做的基本上是更新 AppViewModels 两次!你能明白为什么吗?您在上面的行中显式执行一次,然后组件隐式执行一次。每次创建组件时,它都会与 AppViewModel 相关联——这就是您注册组件时发生的情况。
- 组件与您相关联,您可以将其视为具有自己的属性、功能、计算等的迷你视图模型。因此,在您的情况下,您可能不需要使用组件 -> 复制并粘贴您的组件的内部结构
<script></script>
标记到 <li><li>
中,它应该可以正常工作。
我不确定这是否是您正在寻找的答案,但这应该符合您的要求。如果愿意,您可以使用组件,但您必须进行更多更改和一点点不必要的复杂性。我冒昧地更改了您的变量名。我认为他们这样可能更合适。希望这对您有所帮助!
编辑
我已编辑代码片段以使用组件。这个特定实现的问题是组件现在通过名称 observableArray 与 AppViewModel
紧密耦合。这在您的情况下可能没问题,但通常这会导致不良行为,尤其是当您的视图模型开始变得越来越大和越来越复杂时。当然,您可以传入 firstName
和 lastName
的未包装值,以便更好地解耦,但是您会回到第一个方块,因为 <pre data-bind="text: ko.toJSON(names, null, 2)"></pre>
不会注册对名称 observableArray。
ko.components.register('my-comp', {
viewModel: function(params) {
var self = this;
self.firstName = params.data.firstName
self.lastName = params.data.lastName
self.fullName = ko.pureComputed({
read: function() {
return self.firstName() + " " + self.lastName();
},
write: function(value) {
var lastSpacePos = value.lastIndexOf(" ");
if (lastSpacePos > 0) {
self.firstName(value.substring(0, lastSpacePos));
self.lastName(value.substring(lastSpacePos + 1));
}
},
owner: self
});
self.capitalizeLastName = function() {
var str = self.lastName();
self.lastName(str.toUpperCase());
};
},
template: {
element: "fullNameTmpl"
}
});
var Name = function(){
this.firstName = ko.observable("Bert");
this.lastName = ko.observable("McBertington");
}
function AppViewModel() {
var self = this;
self.names = ko.observableArray();
self.addName = function() {
self.names.push(new Name());
}
}
ko.applyBindings(new AppViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
Names
<button type="button" class="btn btn-link" data-bind="click: addName">
Add Dude
</button>
<ul class="list-group" id="namesList" data-bind="foreach: names">
<my-comp params="data: $data"></my-comp>
<!--<li class="list-group-item">
<p>First name: <strong data-bind="text: firstName"></strong></p>
<p>Last name: <strong data-bind="text: lastName"></strong></p>
<p>First name: <input data-bind="textInput: firstName" /></p>
<p>Last name: <input data-bind="textInput: lastName" /></p>
<p>Full name: <input data-bind="textInput: fullName"></p>
<button data-bind="click: capitalizeLastName">Go caps</button>
</li>-->
</ul>
<pre data-bind="text: ko.toJSON(names, null, 2)"></pre>
<script type="text/html" id="fullNameTmpl">
<li>
<p>First name: <strong data-bind="text: firstName"></strong></p>
<p>Last name: <strong data-bind="text: lastName"></strong></p>
<p>First name: <input data-bind="textInput: firstName" /></p>
<p>Last name: <input data-bind="textInput: lastName" /></p>
<p>Full name: <input data-bind="textInput: fullName"></p>
<button data-bind="click: capitalizeLastName">Go caps</button>
</li>
</script>
我有简单的 HTML 标记,其中包含我的视图模型列表和模板。另外,我在 <pre>
部分有我的视图模型:
<div class="container">
<div class="row">
<div class="col-md-6">
<div class="col-md-12">
<div class="panel panel-primary">
<div class="panel-heading">
Names
<button type="button" class="btn btn-link" data-bind="click: addName">
<span class="glyphicon glyphicon-plus"></span>
</button>
</div>
<div class="panel-body">
<ul class="list-group" id="namesList" data-bind="foreach: Names">
<li class="list-group-item"><my-comp></my-comp></li>
</ul>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="col-md-12">
<pre data-bind="text: ko.toJSON(Names, null, 2)"></pre>
</div>
</div>
</div>
</div>
<script type="text/html" id="fullNameTmpl">
<p>First name: <strong data-bind="text: firstName"></strong></p>
<p>Last name: <strong data-bind="text: lastName"></strong></p>
<p>First name: <input data-bind="textInput: firstName" /></p>
<p>Last name: <input data-bind="textInput: lastName" /></p>
<p>Full name: <input data-bind="textInput: fullName"></p>
<button data-bind="click: capitalizeLastName">Go caps</button>
</script>
<script>
function AppViewModel() {
var self = this;
self.firstName = ko.observable("Bert");
self.lastName = ko.observable("Bertington");
this.fullName = ko.pureComputed({
read: function () {
return self.firstName() + " " + self.lastName();
},
write: function (value) {
var lastSpacePos = value.lastIndexOf(" ");
if (lastSpacePos > 0) {
self.firstName(value.substring(0, lastSpacePos));
self.lastName(value.substring(lastSpacePos + 1));
}
},
owner: self
});
self.capitalizeLastName = function () {
var currentVal = this.lastName();
this.lastName(currentVal.toUpperCase());
};
}
ko.components.register('my-comp', {
viewModel: AppViewModel,
template: { element: "fullNameTmpl" }
});
function NamesViewModel() {
var self = this;
self.Names = ko.observableArray();
self.addName = function () {
var vm = ko.observable(new AppViewModel());
self.Names.push(vm);
}
}
ko.applyBindings(new NamesViewModel());
</script>
效果很好,我可以将一个项目添加到列表中,并且这些项目中的数据绑定有效。当我添加新的 AppViewModel
时,我可以在 <pre>
部分的 Names
数组中看到它。问题是,当我在嵌套视图模型中更改某些值时,比如输入新的姓氏等等,Names
数组中当前视图模型的 <pre>
部分没有任何变化。
如何制作一个真正可观察的可观察数组?添加这样的组件最有利可图的方法是什么?我已经完成了可观察的嵌套视图模型,但它似乎不起作用。
您的代码有几个问题。问题不一定出在可观察数组上——这是您使用组件的问题。列出了组件的用途 here。一些注意事项:
- 不需要制作可观察的数组元素
observable
。所以不需要做var vm = ko.observable(new AppViewModel());
只需使用var vm = new AppViewModel();
并推入你的数组。但是,如果您想使用一个组件,那么这也不正确!正在做的基本上是更新 AppViewModels 两次!你能明白为什么吗?您在上面的行中显式执行一次,然后组件隐式执行一次。每次创建组件时,它都会与 AppViewModel 相关联——这就是您注册组件时发生的情况。 - 组件与您相关联,您可以将其视为具有自己的属性、功能、计算等的迷你视图模型。因此,在您的情况下,您可能不需要使用组件 -> 复制并粘贴您的组件的内部结构
<script></script>
标记到<li><li>
中,它应该可以正常工作。
我不确定这是否是您正在寻找的答案,但这应该符合您的要求。如果愿意,您可以使用组件,但您必须进行更多更改和一点点不必要的复杂性。我冒昧地更改了您的变量名。我认为他们这样可能更合适。希望这对您有所帮助!
编辑
我已编辑代码片段以使用组件。这个特定实现的问题是组件现在通过名称 observableArray 与 AppViewModel
紧密耦合。这在您的情况下可能没问题,但通常这会导致不良行为,尤其是当您的视图模型开始变得越来越大和越来越复杂时。当然,您可以传入 firstName
和 lastName
的未包装值,以便更好地解耦,但是您会回到第一个方块,因为 <pre data-bind="text: ko.toJSON(names, null, 2)"></pre>
不会注册对名称 observableArray。
ko.components.register('my-comp', {
viewModel: function(params) {
var self = this;
self.firstName = params.data.firstName
self.lastName = params.data.lastName
self.fullName = ko.pureComputed({
read: function() {
return self.firstName() + " " + self.lastName();
},
write: function(value) {
var lastSpacePos = value.lastIndexOf(" ");
if (lastSpacePos > 0) {
self.firstName(value.substring(0, lastSpacePos));
self.lastName(value.substring(lastSpacePos + 1));
}
},
owner: self
});
self.capitalizeLastName = function() {
var str = self.lastName();
self.lastName(str.toUpperCase());
};
},
template: {
element: "fullNameTmpl"
}
});
var Name = function(){
this.firstName = ko.observable("Bert");
this.lastName = ko.observable("McBertington");
}
function AppViewModel() {
var self = this;
self.names = ko.observableArray();
self.addName = function() {
self.names.push(new Name());
}
}
ko.applyBindings(new AppViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
Names
<button type="button" class="btn btn-link" data-bind="click: addName">
Add Dude
</button>
<ul class="list-group" id="namesList" data-bind="foreach: names">
<my-comp params="data: $data"></my-comp>
<!--<li class="list-group-item">
<p>First name: <strong data-bind="text: firstName"></strong></p>
<p>Last name: <strong data-bind="text: lastName"></strong></p>
<p>First name: <input data-bind="textInput: firstName" /></p>
<p>Last name: <input data-bind="textInput: lastName" /></p>
<p>Full name: <input data-bind="textInput: fullName"></p>
<button data-bind="click: capitalizeLastName">Go caps</button>
</li>-->
</ul>
<pre data-bind="text: ko.toJSON(names, null, 2)"></pre>
<script type="text/html" id="fullNameTmpl">
<li>
<p>First name: <strong data-bind="text: firstName"></strong></p>
<p>Last name: <strong data-bind="text: lastName"></strong></p>
<p>First name: <input data-bind="textInput: firstName" /></p>
<p>Last name: <input data-bind="textInput: lastName" /></p>
<p>Full name: <input data-bind="textInput: fullName"></p>
<button data-bind="click: capitalizeLastName">Go caps</button>
</li>
</script>