如果我有一个包含由多个组件组成的表单的敲除视图,那么在提交表单时,我应该如何访问组件可观测值?我一直在做这样的事情:
组件foo:
function component_vm(params) {
this.item = params.item || ko.observable();
}
在父视图模型中:
function parent_vm() {
this.item = ko.observable();
}
在父视图中:
<form>
<foo params="item: item"></foo>
</form>
这在我看来很草率。有更好的方法吗?
您可以使用createViewModel工厂函数直接访问父上下文,因此:
ko.components.register('childComponent', {
viewModel: component_vm,
template: ...
})
指定工厂功能:
ko.components.register('childComponent', {
viewModel: {
createViewModel: function(params, componentInfo) {
return new component_vm(params, componentInfo);
},
template: ...
})
这样我们就可以访问componentInfo,从中我们可以获取与组件相关联的元素的绑定上下文:
function component_vm(params, componentInfo) {
var parentContext = ko.contextFor(componentInfo.element).$parent;
//anything available in the parent context is now available here
}
您可以使用这种方法访问链中的任何上下文,一直到根上下文,而无需手动向传递给每个子级的参数中添加任何内容。
第一个音符
您使用的机制(通过params
传递某些东西)是AFAIK,它是实现这一点的主要内置机制。
你要做的第一件事就是问问自己为什么需要这样做。您没有显示真实案例,而是显示了一个抽象的repo;在您的真实场景中,可能有一个设计问题需要解决。我无法完全想象你演示的抽象场景的任何特定实例(即组件视图模型完全重新使用其父级的可观察对象),你确定你没有破坏封装吗?
我确实认为组件与其父级之间的联系是有意义的一件典型事情是,父级(计算的)可观察性因其子级而异。你可以考虑使用ko邮箱,或者手动进行pub-sub,比如:
function component_vm(params) {
this.item = ko.observable();
if (!!params.itemChangedHandler) {
this.item.subscribe(params.itemChangedHandler);
}
}
function parent_vm() {
this.someHandler = function() {
alert('Parent knows something is up!');
}
}
<form>
<foo params="itemChangedHandler: someHandler "></foo>
</form>
第二个音符
我通常喜欢组件是自包含的(正如您可能从我之前的建议中了解到的那样,在组件间通信中使用类似ko邮箱的东西)。我的组件通常有自己的"任务"给用户和他们自己的相关工作单元
不要忘记KnockoutJS也支持template
绑定,因为在我看来(至少从你对这个答案的评论中),子"组件"实际上是父级正常工作所必需的,在这种情况下,我建议使用模板来模块化你的应用程序,使你的代码更易于重用。
// Mock ajax calls:
var $ = { post: function(url, dto) { console.log(dto); } };
function Address() {
this.addressLine1 = ko.observable();
this.addressLine2 = ko.observable();
this.addressLine3 = ko.observable();
}
function Person() {
this.firstName = ko.observable();
this.lastName = ko.observable();
this.mainAddress = new Address();
this.secondaryAddress = new Address();
}
function RootVm() {
var self = this;
self.person = new Person();
self.submit = function() {
$.post("my_url", ko.toJSON(self.person));
}
}
ko.applyBindings(new RootVm());
div { margin: 10px; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.0/knockout-min.js"></script>
<script type="text/html" id="addressTmpl">
<input data-bind="textInput: addressLine1" placeholder="address line 1"><br>
<input data-bind="textInput: addressLine2" placeholder="address line 2"><br>
<input data-bind="textInput: addressLine3" placeholder="address line 3">
</script>
<script type="text/html" id="personTmpl">
Name:
<div>
<input data-bind="textInput: firstName" placeholder="firstname">
<input data-bind="textInput: lastName" placeholder="surname">
</div>
Main address: <div data-bind="template: { name: 'addressTmpl', data: mainAddress }"></div>
Secondary address: <div data-bind="template: { name: 'addressTmpl', data: secondaryAddress }"></div>
</script>
<div data-bind="template: { name: 'personTmpl', data: person }"></div>
<button data-bind="click: submit">submit</button>
最后一句话
如果你真的想在两个视图模型之间建立一个硬链接,并且不喜欢上面两个选项/坚持使用component
s,那么我支持@Andrew的回答中的建议,并使用createViewModel
工厂函数。
详细信息将根据您的实际情况/应用程序而定,但您可以通过自己应用程序中的一种方式或另一个答案中的contextFor
建议,将该功能授予家长。