如何从父视图模型中的窗体访问属于组件的可观察性



如果我有一个包含由多个组件组成的表单的敲除视图,那么在提交表单时,我应该如何访问组件可观测值?我一直在做这样的事情:

组件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建议,将该功能授予家长。

最新更新