组件绑定将指定的组件注入到元素中,并且可选地将参数传递给它。
本节目录
- 一个例子
- API
- 组件生命周期
- 备注1:仅限模板组件
- 备注2:使用没有容器元素的组件
- 备注3:将标记传递给组件
- 处置和内存管理
一个例子
First instance, without parameters
Second instance, passing parameters
UI源码:
<h4>First instance, without parameters</h4>
<div data-bind='component: "message-editor"'></div> <h4>Second instance, passing parameters</h4>
<div data-bind='component: {
name: "message-editor",
params: { initialText: "Hello, world!" }
}'>
</div>
视图模型源码:
ko.components.register('message-editor', {
viewModel: function(params) {
this.text = ko.observable(params && params.initialText || '');
},
template: 'Message: <input data-bind="value: text" /> '
+ '(length: <span data-bind="text: text().length"></span>)'
}); ko.applyBindings();
注意:在更现实的情况下,通常从外部文件加载组件视图模型和模板,而不是将它们硬编码到注册中。
API
有两种方法使用组件绑定:
快速语法:
如果你只传递一个字符串,它被解释为一个组件名称。 然后注入命名的组件,而不向其提供任何参数。 例:
<div data-bind='component: "my-component"'></div>
也可以将监控属性作为组件名称。 在这种情况下,如果监控属性值更改,组件绑定将处理旧组件实例,并注入新引用的组件。 例:
<div data-bind='component: observableWhoseValueIsAComponentName'></div>
完整语法:
要向组件提供参数,请传递具有以下属性的对象:
-
name
— 要注入的组件的名称。 同样,这可以是监控属性。 -
params
—将被传递给组件的对象。 通常,这是一个包含多个参数的键值对象,通常由组件的viewmodel构造函数接收。
例如:
<div data-bind='component: {
name: "shopping-cart",
params: { mode: "detailed-list", items: productsList }
}'>
</div>
组件生命周期
当组件绑定注入组件时,
-
请求您的组件加载器提供viewmodel工厂和模板
- 可以查阅多个组件加载器,直到第一个识别组件名称并提供视图模型/模板。 此过程仅对每个组件类型发生一次,因为Knockout在内存中缓存生成的定义。
- 默认组件加载器根据您注册的内容提供viewmodels /template。 如果适用,这是从AMD加载器请求任何指定的AMD模块的阶段。
通常,这是一个异步过程。 它可能涉及对服务器的请求。 对于API一致性,Knockout默认确保加载过程作为异步回调完成,即使组件已经加载并缓存在内存中。 有关更多信息以及如何允许同步加载,请参阅上一节的控制同步/异步加载。
-
组件模板被克隆并注入到容器元素中
任何现有内容都将被删除并丢弃。
-
如果组件有一个viewmodel,它被实例化
如果viewmodel是作为构造函数给出的,这意味着Knockout调用新的YourViewModel(params)。
如果viewmodel作为createViewModel工厂函数给出,Knockout callscreateViewModel(params,componentInfo),其中componentInfo.element是尚未绑定的未绑定模板的元素。
这个阶段总是同步完成(构造函数和工厂函数不允许是异步的),因为每次组件被实例化时都会出现这种情况,如果涉及等待网络请求,性能将是不可接受的。
-
viewmodel被绑定到视图
如果组件没有viewmodel,则视图将绑定到您提供给组件绑定的任何参数。
-
组件处于活动状态
现在组件正在运行,并且可以在需要时保持在屏幕上。
如果传递给组件的任何参数是监控属性,则组件当然可以观察到任何改变,或者甚至回写修改的值。 这是它如何能够干净地与其父进行通信,而不是将组件代码紧密地耦合到使用它的任何父进程。
-
组件被拆卸,并且视图模型被删除
If如果组件绑定的名称值可观察地改变,或者如果封闭的控制流绑定导致容器元素被移除,则在从DOM移除容器元素之前调用视图模型上的任何dispose函数。 参见本节:处置和内存管理。
注意:如果用户导航到完全不同的网页,浏览器会执行此操作,而不会要求页面中运行的任何代码进行清理。 所以在这种情况下不会调用dispose函数。 这是正常的,因为浏览器会自动释放所有使用的对象使用的内存。
备注1:仅限模板组件
组件通常有viewmodels,但它们不一定必须。 组件只能指定一个模板。
在这种情况下,组件视图所绑定的对象是您传递给组件绑定的params对象。 例:
ko.components.register('special-offer', {
template: '<div class="offer-box" data-bind="text: productName"></div>'
});
...可以注入参数:
<div data-bind='component: {
name: "special-offer-callout",
params: { productName: someProduct.name }
}'></div>
或者,更方便地,作为自定义元素:
<special-offer params='productName: someProduct.name'></special-offer>
备注2:使用没有容器元素的组件
有时,您可能想要将一个组件注入到视图中,而不使用额外的容器元素。 您可以使用基于注释标签的无容器控制流语法。 例如,
<!-- ko component: "message-editor" -->
<!-- /ko -->
...或传递参数:
<!-- ko component: {
name: "message-editor",
params: { initialText: "Hello, world!", otherParam: 123 }
} -->
<!-- /ko -->
<!-- ko -- >和<!-- / ko -- >注释作为开始/结束标记,定义一个包含标记的“虚拟元素”。 Knockout理解这个虚拟元素的语法,并绑定,就像你有一个真正的容器元素。
备注3:将标记传递给组件
您附加组件绑定的元素可能包含进一步的标记。 例如,
<div data-bind="component: { name: 'my-special-list', params: { items: someArrayOfPeople } }">
<!-- Look, here's some arbitrary markup. By default it gets stripped out
and is replaced by the component output. -->
The person <em data-bind="text: name"></em>
is <em data-bind="text: age"></em> years old.
</div>
虽然此元素中的DOM节点将被删除,并且不会默认绑定,但它们不会丢失。 相反,它们被提供给组件(在这种情况下,my-special-list),它可以将它们包括在它希望的输出中。
如果要构建表示“容器”UI元素的组件(如网格,列表,对话框或标签集),这需要注入并将任意标记绑定到公共结构中,这将非常有用。 它也可以在没有自定义元素的情况下使用上面显示的语法。
处置和内存管理
您的viewmodel类可能具有dispose函数。 如果实现了,当组件被删除并从DOM中删除时,Knockout会调用它(例如,因为相应的项从foreach中删除,或者如果绑定变为false)。
您必须使用dispose释放任何本质上不可回收的资源。 例如:
-
setInterval
回调将继续触发,直到显式清除。- 使用clearInterval(handle)来停止它们,否则你的viewmodel可能被保存在内存中。
- ko.computed属性继续从其依赖关系接收通知,直到明确处置。
- 如果一个依赖是一个外部对象,那么一定要使用.dispose()在computed属性,否则它(可能还有你的viewmodel)将被保存在内存中。 或者,考虑使用pureComputed以避免手工处置的需要。
-
监控属性订阅将继续运行,直到明确被处理。
- 如果你订阅了一个外部的observable,一定要使用.dispose()在订阅,否则回调(可能还有你的viewmodel)将被保存在内存中。
- 外部DOM元素上手动创建的事件处理程序(如果在createViewModelfunction中创建)(甚至在常规组件视图模型内部,尽管适合您不应该使用的MVVM模式)必须删除。
- 当然,您不必担心在视图中释放由标准Knockout绑定创建的任何事件处理程序,因为KO会在删除元素时自动注销它们。
例如:
var someExternalObservable = ko.observable(123); function SomeComponentViewModel() {
this.myComputed = ko.computed(function() {
return someExternalObservable() + 1;
}, this); this.myPureComputed = ko.pureComputed(function() {
return someExternalObservable() + 2;
}, this); this.mySubscription = someExternalObservable.subscribe(function(val) {
console.log('The external observable changed to ' + val);
}, this); this.myIntervalHandle = window.setInterval(function() {
console.log('Another second passed, and the component is still alive.');
}, 1000);
} SomeComponentViewModel.prototype.dispose = function() {
this.myComputed.dispose();
this.mySubscription.dispose();
window.clearInterval(this.myIntervalHandle);
// this.myPureComputed doesn't need to be manually disposed.
} ko.components.register('your-component-name', {
viewModel: SomeComponentViewModel,
template: 'some template'
});
不必严格地需要仅仅依赖于相同viewmodel对象的属性来处理计算和订阅,因为这仅创建了JavaScript垃圾收集器知道如何释放的循环引用。 然而,为了避免不必记住哪些事情需要处理,你可能更喜欢在任何可能的地方使用pureComputed,并且显式地处置所有其他计算/订阅,无论技术上是否必要。