Components 是将UI代码组织成自包含的、可重用的块的一种强大而干净的方法。他们:javascript
这种模式对大型应用程序是有益的,由于它经过清晰的组织和封装简化了开发,并根据须要增量地加载应用程序代码和模板,从而帮助提升运行时性能。html
自定义元素是用于消费组件的可选但方便的语法。不须要使用占位符<div>
来将绑定注入组件,您可使用更多带有自定义元素名称的自描述性标记(例如,<voting-button>
or <product-editor>
))。淘汰赛会确保即便与IE 6等老浏览器兼容。html5
To get started, you can register a component using ko.components.register
(technically, registration is optional, but it’s the easiest way to get started). A component definition specifies a viewModel
and template
. For example:
要开始,您可使用ko.components.register
注册一个组件(从技术上讲,注册是可选的,但这是最简单的方法)。组件定义指定视图模型和模板。例如java
ko.components.register('like-widget', { viewModel: function(params) { // Data: value is either null, 'like', or 'dislike' this.chosenValue = params.value; // Behaviors this.like = function() { this.chosenValue('like'); }.bind(this); this.dislike = function() { this.chosenValue('dislike'); }.bind(this); }, template: '<div class="like-or-dislike" data-bind="visible: !chosenValue()">\ <button data-bind="click: like">Like it</button>\ <button data-bind="click: dislike">Dislike it</button>\ </div>\ <div class="result" data-bind="visible: chosenValue">\ You <strong data-bind="text: chosenValue"></strong> it\ </div>' });
一般,您会从外部文件加载视图模型和模板,而不是像这样内嵌声明它们。咱们稍后再谈。node
如今,要使用这个组件,您能够从应用程序中的任何其余视图引用它,或者使用component
binding ,或者使用 custom element。下面是一个将它用做自定义元素的实时示例:web
Source code: Viewexpress
<ul data-bind="foreach: products"> <li class="product"> <strong data-bind="text: name"></strong> <like-widget params="value: userRating"></like-widget> </li> </ul>
Source code: View modelnpm
function Product(name, rating) { this.name = name; this.userRating = ko.observable(rating || null); } function MyViewModel() { this.products = [ new Product('Garlic bread'), new Product('Pain au chocolat'), new Product('Seagull spaghetti', 'like') // This one was already 'liked' ]; } ko.applyBindings(new MyViewModel());
在本例中,组件在Product
view model类上同时显示和编辑一个名为userRating
的可观察属性。编程
在大多数应用程序中,您都但愿将组件视图模型和模板保存在外部文件中。若是将击倒配置为经过AMD模块加载器(如require.js)获取它们。而后,它们能够预先加载(多是绑定/缩小),也能够根据须要增量加载。api
下面是一个示例配置:
ko.components.register('like-or-dislike', { viewModel: { require: 'files/component-like-widget' }, template: { require: 'text!files/component-like-widget.html' } });
必要条件
为了让它发挥做用,文件files/component-like-widget.js
和 files/component-like-widget.html
必须存在。检查它们(并在.html文件中查看源代码)——您将看到,这比在定义中内联代码更干净、更方便。
此外,您还须要引用一个合适的模块加载器库(如 require.js),或者实现一个知道如何获取文件的自定义组件加载器(custom component loader)。
使用组件
如今like-or-dislike
能够像之前同样被消耗掉,使用component
binding 或者 custom element
Source code: View
<ul data-bind="foreach: products"> <li class="product"> <strong data-bind="text: name"></strong> <like-or-dislike params="value: userRating"></like-or-dislike> </li> </ul> <button data-bind="click: addProduct">Add a product</button>
Source code: View model
function Product(name, rating) { this.name = name; this.userRating = ko.observable(rating || null); } function MyViewModel() { this.products = ko.observableArray(); // Start empty } MyViewModel.prototype.addProduct = function() { var name = 'Product ' + (this.products().length + 1); this.products.push(new Product(name)); }; ko.applyBindings(new MyViewModel());
若是在第一次单击“添加产品”以前打开浏览器开发人员工具的网络检查器,您将看到组件的 .js
/.html
文件在首次须要时按需提取,而后保留以供重复使用。
要Knockout可以加载和实例化组件,必须使用ko.components.register
注册它们,并提供以下所述的配置。
注意:做为一种替代方法,能够实现一个自定义组件加载器,它根据您本身的约定而不是显式配置来获取组件。
You can register a component as follows:
ko.components.register('some-component-name', { viewModel: <see below>, template: <see below> });
your-component-name
),以便组件名称能够有效地用做自定义元素(例如<your-component-name>
)。viewModel
是可选的,而且能够采用下面描述的任何viewModel
格式。template
是必须的, 而且能够采用 下面描述的任何template
格式.若是没有给出viewmodel,则将组件视为一个简单的HTML块,它将绑定到传递给组件的任何参数。
视图模型能够用如下任何一种形式指定:
一个构造函数
function SomeComponentViewModel(params) { // 'params' is an object whose key/value pairs are the parameters // passed from the component binding or custom element. this.someProperty = params.something; } SomeComponentViewModel.prototype.doSomething = function() { ... }; ko.components.register('my-component', { viewModel: SomeComponentViewModel, template: ... });
Knockout将为组件的每一个实例调用构造函数一次,为每一个实例生成单独的viewmodel对象。结果对象或其原型链上的属性(例如,上面示例中的someProperty
和doSomething
)可用于在组件的视图中绑定。
一个共享对象实例
若是但愿组件的全部实例共享同一个viewmodel对象实例(一般不但愿这样):
var sharedViewModelInstance = { ... }; ko.components.register('my-component', { viewModel: { instance: sharedViewModelInstance }, template: ... });
注意,须要指定 viewModel: { instance: object }
,而不只仅是viewModel: object
。这与下面的其余状况不一样。
一个createViewModel
工厂函数
若是但愿在关联元素绑定到viewmodel以前对其运行任何设置逻辑,或者使用任意逻辑来决定实例化哪一个viewmodel类:
ko.components.register('my-component', { viewModel: { createViewModel: function(params, componentInfo) { // - 'params' is an object whose key/value pairs are the parameters // passed from the component binding or custom element // - 'componentInfo.element' is the element the component is being // injected into. When createViewModel is called, the template has // already been injected into this element, but isn't yet bound. // - 'componentInfo.templateNodes' is an array containing any DOM // nodes that have been supplied to the component. See below. // Return the desired view model instance, e.g.: return new MyViewModel(params); } }, template: ... });
注意,一般,最好只经过custom bindings执行直接DOM操做,而不是从createViewModel
内部对 componentInfo.element
执行操做。这将致使更加模块化、可重用的代码。
若是您想要构建一个接受任意标记以影响其输出的组件(例如,将提供的标记注入自身的网格、列表、对话框或选项卡集),componentInfo.templateNodes
数组很是有用。有关完整示例,请参见将标记传递到组件。
一个AMD模块,其值描述viewmodel
若是您的页面中已经有一个AMD加载器(如require.js,那么您可使用它来获取一个viewmodel。有关如何工做的更多细节,请参见下面介绍如何经过AMD加载组件。例子:
ko.components.register('my-component', { viewModel: { require: 'some/module/name' }, template: ... });
返回的AMD模块对象能够是viewmodel容许的任何形式。所以,它能够是一个构造函数,例如。
// AMD module whose value is a component viewmodel constructor define(['knockout'], function(ko) { function MyViewModel() { // ... } return MyViewModel; });
或共享对象实例,例如。
// AMD module whose value is a shared component viewmodel instance define(['knockout'], function(ko) { function MyViewModel() { // ... } return { instance: new MyViewModel() }; });
或createViewModel
函数,例如。
// AMD module whose value is a 'createViewModel' function define(['knockout'], function(ko) { function myViewModelFactory(params, componentInfo) { // return something } return { createViewModel: myViewModelFactory }; });
或者,即便你不太可能想这样作,一个不一样的AMD模块的引用,例如
// AMD module whose value is a reference to a different AMD module, // which in turn can be in any of these formats define(['knockout'], function(ko) { return { module: 'some/other/module' }; });
模板能够用如下任何一种形式指定。最经常使用的是现有的元素id和AMD模块。
For example, the following element:
<template id='my-component-template'> <h1 data-bind='text: title'></h1> <button data-bind='click: doSomething'>Click me right now</button> </template>
… 能够经过指定其ID来用做组件的模板:
ko.components.register('my-component', { template: { element: 'my-component-template' }, viewModel: ... });
注意,只有指定元素中的节点才会被克隆到组件的每一个实例中。容器元素(在本例中为<template>
元素)将不被视为组件模板的一部分。
您不只限于使用<template>
元素,并且这些元素(在支持它们的浏览器上)也很方便,由于它们不会本身呈现。任何其余元素类型也能够。
现有元素实例
若是代码中有对DOM元素的引用,能够将其用做模板标记的容器:
var elemInstance = document.getElementById('my-component-template'); ko.components.register('my-component', { template: { element: elemInstance }, viewModel: ... });
一样,只有指定元素中的节点将被克隆,以用做组件的模板。
一串字符串标记
ko.components.register('my-component', { template: '<h1 data-bind="text: title"></h1>\ <button data-bind="click: doSomething">Clickety</button>', viewModel: ... });
This is mainly useful when you’re fetching the markup from somewhere programmatically (e.g., AMD - see below), or as a build system output that packages components for distribution, since it’s not very convenient to manually edit HTML as a JavaScript string literal.
这主要是有用的,当你从某处以编程方式获取标记(例如,AMD -见下文),或者做为构建系统输出包组件分发,由于它不是很方便手工编辑HTML做为JavaScript字符串文字。
一组DOM节点
若是以编程方式构建配置,而且有一个DOM节点数组,则能够将它们用做组件模板:
var myNodes = [ document.getElementById('first-node'), document.getElementById('second-node'), document.getElementById('third-node') ]; ko.components.register('my-component', { template: myNodes, viewModel: ... });
在本例中,全部指定的节点(及其后代节点)都将被克隆并链接到要实例化的组件的每一个副本中。
一个文档片断
若是您正在以编程方式构建配置,而且您有一个DocumentFragment
对象,那么您能够将它用做组件模板:
ko.components.register('my-component', { template: someDocumentFragmentInstance, viewModel: ... });
因为文档片断能够有多个顶级节点,所以整个文档片断(不只仅是顶级节点的后代)被视为组件模板。
若是您的页面中已经有一个AMD加载器(如require.js
),那么您可使用它来获取模板。有关如何工做的更多细节,请参见下面介绍如何经过AMD加载组件。例子
ko.components.register('my-component', { template: { require: 'some/template' }, viewModel: ... });
返回的AMD模块对象能够是viewmodel容许的任何形式。所以,它能够是一个标记字符串,例如使用require.js的文本插件获取:
ko.components.register('my-component', { template: { require: 'text!path/my-html-file.html' }, viewModel: ... });
...或这里描述的任何其余表单,尽管其余表单在经过AMD获取模板时很是有用。
除了(或代替)template
和 viewModel
以外,组件配置对象还能够具备任意其余属性。此配置对象可用于您可能正在使用的任何自定义组件加载程序。
若是组件配置具备一个boolean synchronous
属性,则Knockout将使用此属性肯定是否容许同步加载和注入组件。默认值为false
(即,必须是异步的)。例如:
ko.components.register('my-component', { viewModel: { ... anything ... }, template: { ... anything ... }, synchronous: true // Injects synchronously if possible, otherwise still async });
一般,Knockout确保了组件加载以及组件注入老是异步完成,由于有时它别无选择,只能异步完成(例如,由于它涉及到对服务器的请求)。即便能够同步注入特定的组件实例(例如,由于组件定义已经被加载),它也会这样作。这种始终异步的策略是一个一致性问题,是从其余现代异步JavaScript技术(如AMD)继承而来的一个公认的惯例。约定是一个安全的缺省值——它减轻了潜在的错误,在这些错误中,开发人员可能没有考虑到典型异步过程的可能性,有时同步完成,反之亦然。
若是要更改特定组件的策略,能够在该组件的配置中指定synchronous: true
。而后它可能在第一次使用时异步加载,随后在全部后续使用中同步加载。若是您这样作,那么您须要在任何等待组件加载的代码中考虑这种可变行为。可是,若是您的组件老是能够同步加载和初始化,那么启用此选项将确保一致的同步行为。若是您在foreach
绑定中使用组件,而且但愿使用 afterAdd
或 afterRender
选项进行后处理,这可能很重要。
在Knockout 3.4.0以前,您可能须要使用同步加载来防止多个DOM在同时包含多个组件时发生重流(例如使用foreach绑定)。在Knockout 3.4.0中,组件使用Knockout的微指令(microtasks )来确保异步性,所以一般会执行同步加载。
当您经过require
声明加载视图模型或模板时,例如:
ko.components.register('my-component', { viewModel: { require: 'some/module/name' }, template: { require: 'text!some-template.html' } });
...全部Knockout都是调用require(['some/module/name'], callback)
和require(['text!some-template.html'], callback)
,并使用异步返回的对象做为视图模型和模板 定义。 因此,
require.js
或任何其余特定的模块加载器。任何提供amd风格的模块加载器都须要API。若是但愿与API不一样的模块加载器集成,能够实现自定义组件加载器。require()
。所以,淘汰赛固然不知道或不关心从哪里加载模块文件。这取决于你的AMD加载器和你如何配置它。在实例化组件以前,Knockout不会调用require([moduleName], ...)
。这是组件按需加载的方式,而不是预先加载。
例如,若是组件位于具备if binding
(或另外一个控制流绑定)的其余元素中,则在if
条件为真以前,不会致使加载AMD模块。固然,若是AMD模块已经加载(例如,在一个预加载包中),那么require
调用将不会触发任何额外的HTTP请求,所以您能够控制什么是预加载的,什么是按需加载的。
为了更好的封装,您能够将组件封装到一个自描述的AMD模块中。而后,您能够简单地引用组件:
ko.components.register('my-component', { require: 'some/module' });
请注意,没有指定视图 viewmodel/template 对。AMD模块自己可使用上面列出的任何定义格式提供 viewmodel/template 对。例如,文件 some/module.js
能够声明为::
// AMD module 'some/module.js' encapsulating the configuration for a component define(['knockout'], function(ko) { function MyComponentViewModel(params) { this.personName = ko.observable(params.name); } return { viewModel: MyComponentViewModel, template: 'The name is <strong data-bind="text: personName"></strong>' }; });
在实践中最有用的是建立具备内联视图模型类的AMD模块,并显式地依赖于外部模板文件。
例如,若是如下内容在path/my-component.js
的文件中,
// Recommended AMD module pattern for a Knockout component that: // - Can be referenced with just a single 'require' declaration // - Can be included in a bundle using the r.js optimizer define(['knockout', 'text!./my-component.html'], function(ko, htmlString) { function MyComponentViewModel(params) { // Set up properties, etc. } // Use prototype to declare any public methods MyComponentViewModel.prototype.doSomething = function() { ... }; // Return component definition return { viewModel: MyComponentViewModel, template: htmlString }; });
... 模板标记在文件path/my-component.html
中,那么您有如下好处:
ko.components.register('my-component', { require: 'path/my-component' });
path/my-component.js
) 和一个 template (path/my-component.html
) ,这是开发过程当中很是天然的安排。define
调用中显式地声明了对模板的依赖关系,这将自动与 r.js
optimizer或相似的捆绑工具一块儿工做。所以,在构建步骤中,整个组件(viewmodel + template)能够简单地包含在一个bundle
文件中。
r.js optimizer
很是灵活,它有不少选项,可能须要一些时间来设置。您可能想从一个经过r.js
优化Knockout组件的现成示例开始。在这种状况下,请参阅e Yeoman 和 generator-ko 生成器。博客文章即将发布。自定义元素提供了一种将组件注入视图的方便方法。
自定义元素是 component
binding 的语法替代(实际上,自定义元素在幕后使用组件绑定)。
例如,与其写这个:
<div data-bind='component: { name: "flight-deals", params: { from: "lhr", to: "sfo" } }'></div>
你能够写:
<flight-deals params='from: "lhr", to: "sfo"'></flight-deals>
这容许以一种很是现代的、相似于web组件的方式组织代码,同时保留对很是旧的浏览器的支持(请参阅自定义元素和IE 6到8)。
这个例子声明了一个组件,而后将它的两个实例注入到一个视图中。参见下面的源代码。
Source code: View
<h4>First instance, without parameters</h4> <message-editor></message-editor> <h4>Second instance, passing parameters</h4> <message-editor params='initialText: "Hello, world!"'></message-editor>
Source code: View model
ko.components.register('message-editor', { viewModel: function(params) { this.text = ko.observable(params.initialText || ''); }, template: 'Message: <input data-bind="value: text" /> ' + '(length: <span data-bind="text: text().length"></span>)' }); ko.applyBindings();
注意:在更实际的状况下,您一般会从外部文件加载组件视图模型和模板,而不是将它们硬编码到注册中。参见示例和注册文档。
正如您在上面的示例中所看到的,您可使用params
属性向组件视图模型提供参数。params
属性的内容被解释为JavaScript对象文本(就像data-bind
属性同样),所以能够传递任何类型的任意值。例子:
<unrealistic-component params='stringValue: "hello", numericValue: 123, boolValue: true, objectValue: { a: 1, b: 2 }, dateValue: new Date(), someModelProperty: myModelValue, observableSubproperty: someObservable().subprop'> </unrealistic-component>
若是您在params
属性中引用模型属性,那么您固然是在引用组件(父视图模型或主机视图模型)以外的视图模型上的属性,由于组件自己尚未实例化。在上面的例子中,myModelValue
将是父视图模型上的一个属性,子组件viewmodel的构造函数将以 params.someModelProperty
的形式接收它。
这就是如何将属性从父视图模型传递到子组件。若是属性自己是可观察的,那么父视图模型将可以观察并响应子组件插入到它们中的任何新值。
在下面的示例中,
<some-component params='simpleExpression: 1 + 1, simpleObservable: myObservable, observableExpression: myObservable() + 1'> </some-component>
... 组件viewmodel的params
参数将包含三个值:
simpleExpression
这将是数值2
。它将不是一个可观察值或计算值,由于不涉及可观察值。
一般,若是参数的计算不涉及对可观察值的计算(在本例中,该值根本不涉及可观察值),则按字面意义传递该值。若是值是一个对象,那么子组件能够对它进行修改,可是因为它是不可观察的,因此父组件不会知道子组件已经这样作了。
simpleObservable
这将是在父视图模型上声明为myObservable
的ko.observable
实例。它不是包装器——它实际上与父级引用的实例相同。所以,若是子视图模型写入这个可观察到的内容,父视图模型将接收到这个更改。
通常来讲,若是一个参数的评估不涉及评估一个可观测值(在这种状况下,可观测值只是简单地传递而没有评估它),那么该值就按字面意义传递。
observableExpression
这个更棘手。表达式自己在计算时读取一个可观察值。可observable的值会随时间变化,因此表达式结果也会随时间变化。
为了确保子组件可以对表达式值的更改作出反应,Knockout将自动将该参数升级为计算属性。所以,子组件将可以读取params.observableExpression()
来获取当前值,或者使用params.observableExpression(...)
等。
一般,对于自定义元素,若是参数的计算涉及到计算一个可观察到的值,那么敲除将自动构造一个ko.computed
值来给出表达式s的结果,并将其提供给组件。
总之,总的规则是:
Sometimes you may want to create a component that receives markup and uses it as part of its output. For example, you may want to build a “container” UI element such as a grid, list, dialog, or tab set that can receive and bind arbitrary markup inside itself.
Consider a special list component that can be invoked as follows:
<my-special-list params="items: someArrayOfPeople"> <!-- Look, I'm putting markup inside a custom element --> The person <em data-bind="text: name"></em> is <em data-bind="text: age"></em> years old. </my-special-list>
By default, the DOM nodes inside <my-special-list>
will be stripped out (without being bound to any viewmodel) and replaced by the component’s output. However, those DOM nodes aren’t lost: they are remembered, and are supplied to the component in two ways:
$componentTemplateNodes
, available to any binding expression in the component’s template (i.e., as a binding context property). Usually this is the most convenient way to use the supplied markup. See the example below.componentInfo.templateNodes
, passed to its createViewModel
functionThe component can then choose to use the supplied DOM nodes as part of its output however it wishes, such as by using template: { nodes: $componentTemplateNodes }
on any element in the component’s template.
For example, the my-special-list
component’s template can reference $componentTemplateNodes
so that its output includes the supplied markup. Here’s the complete working example:
Source code: View
<!-- This could be in a separate file --> <template id="my-special-list-template"> <h3>Here is a special list</h3> <ul data-bind="foreach: { data: myItems, as: 'myItem' }"> <li> <h4>Here is another one of my special items</h4> <!-- ko template: { nodes: $componentTemplateNodes, data: myItem } --><!-- /ko --> </li> </ul> </template> <my-special-list params="items: someArrayOfPeople"> <!-- Look, I'm putting markup inside a custom element --> The person <em data-bind="text: name"></em> is <em data-bind="text: age"></em> years old. </my-special-list>
Source code: View model
ko.components.register('my-special-list', { template: { element: 'my-special-list-template' }, viewModel: function(params) { this.myItems = params.items; } }); ko.applyBindings({ someArrayOfPeople: ko.observableArray([ { name: 'Lewis', age: 56 }, { name: 'Hathaway', age: 34 } ]) });
This “special list” example does nothing more than insert a heading above each list item. But the same technique can be used to create sophisticated grids, dialogs, tab sets, and so on, since all that is needed for such UI elements is common UI markup (e.g., to define the grid or dialog’s heading and borders) wrapped around arbitrary supplied markup.
This technique is also possible when using components without custom elements, i.e., passing markup when using the component
binding directly.
By default, Knockout assumes that your custom element tag names correspond exactly to the names of components registered using ko.components.register
. This convention-over-configuration strategy is ideal for most applications.
If you want to have different custom element tag names, you can override getComponentNameForNode
to control this. For example,
ko.components.getComponentNameForNode = function(node) { var tagNameLower = node.tagName && node.tagName.toLowerCase(); if (ko.components.isRegistered(tagNameLower)) { // If the element's name exactly matches a preregistered // component, use that component return tagNameLower; } else if (tagNameLower === "special-element") { // For the element <special-element>, use the component // "MySpecialComponent" (whether or not it was preregistered) return "MySpecialComponent"; } else { // Treat anything else as not representing a component return null; } }
You can use this technique if, for example, you want to control which subset of registered components may be used as custom elements.
If you are using the default component loader, and hence are registering your components using ko.components.register
, then there is nothing extra you need to do. Components registered this way are immediately available for use as custom elements.
If you have implemented a custom component loader, and are not using ko.components.register
, then you need to tell Knockout about any element names you wish to use as custom elements. To do this, simply call ko.components.register
- you don’t need to specify any configuration, since your custom component loader won’t be using the configuration anyway. For example,
ko.components.register('my-custom-element', { /* No config needed */ });
Alternatively, you can override getComponentNameForNode
to control dynamically which elements map to which component names, independently of preregistration.
A custom element can have a regular data-bind
attribute (in addition to any params
attribute) if needed. For example,
<products-list params='category: chosenCategory' data-bind='visible: shouldShowProducts'> </products-list>
However, it does not make sense to use bindings that would modify the element’s contents, such as the text
or template
bindings, since they would overwrite the template injected by your component.
Knockout will prevent the use of any bindings that use controlsDescendantBindings
, because this also would clash with the component when trying to bind its viewmodel to the injected template. Therefore if you want to use a control flow binding such as if
or foreach
, then you must wrap it around your custom element rather than using it directly on the custom element, e.g.,:
<!-- ko if: someCondition --> <products-list></products-list> <!-- /ko -->
或者:
<ul data-bind='foreach: allProducts'> <product-details params='product: $data'></product-details> </ul>
You must write <my-custom-element></my-custom-element>
, and not <my-custom-element />
. Otherwise, your custom element is not closed and subsequent elements will be parsed as child elements.
This is a limitation of the HTML specification and is outside the scope of what Knockout can control. HTML parsers, following the HTML specification, ignore any self-closing slashes (except on a small number of special “foreign elements”, which are hardcoded into the parser). HTML is not the same as XML.
Knockout tries hard to spare developers the pain of dealing with cross-browser compatiblity issues, especially those relating to older browsers! Even though custom elements provide a very modern style of web development, they still work on all commonly-encountered browsers:
IE 6-8’s HTML parser will discard any unrecognized elements. To ensure it doesn’t throw out your custom elements, you must do one of the following:
ko.components.register('your-component')
before the HTML parser sees any <your-component>
elementsdocument.createElement('your-component')
before the HTML parser sees any <your-component>
elements. You can ignore the result of the createElement
call — all that matters is that you have called it.For example, if you structure your page like this, then everything will be OK:
<!DOCTYPE html> <html> <body> <script src='some-script-that-registers-components.js'></script> <my-custom-element></my-custom-element> </body> </html>
If you’re working with AMD, then you might prefer a structure like this:
<!DOCTYPE html> <html> <body> <script> // Since the components aren't registered until the AMD module // loads, which is asynchronous, the following prevents IE6-8's // parser from discarding the custom element document.createElement('my-custom-element'); </script> <script src='require.js' data-main='app/startup'></script> <my-custom-element></my-custom-element> </body> </html>
Or if you really don’t like the hackiness of the document.createElement
call, then you could use a component
binding for your top-level component instead of a custom element. As long as all other components are registered before your ko.applyBindings
call, they can be used as custom elements on IE6-8 without futher trouble:
<!DOCTYPE html> <html> <body> <!-- The startup module registers all other KO components before calling ko.applyBindings(), so they are OK as custom elements on IE6-8 --> <script src='require.js' data-main='app/startup'></script> <div data-bind='component: "my-custom-element"'></div> </body> </html>
$raw
参数Consider the following unusual case, in which useObservable1
, observable1
, and observable2
are all observables:
<some-component params='myExpr: useObservable1() ? observable1 : observable2'> </some-component>
Since evaluating myExpr
involves reading an observable (useObservable1
), KO will supply the parameter to the component as a computed property.
However, the value of the computed property is itself an observable. This would seem to lead to an awkward scenario, where reading its current value would involve double-unwrapping (i.e., params.myExpr()()
, where the first parentheses give the value of the expression, and the second give the value of the resulting observable instance).
This double-unwrapping would be ugly, inconvenient, and unexpected, so Knockout automatically sets up the generated computed property (params.myExpr
) to unwrap its value for you. That is, the component can read params.myExpr()
to get the value of whichever observable has been selected (observable1
or observable2
), without the need for double-unwrapping.
In the unlikely event that you don’t want the automatic unwrapping, because you want to access the observable1
/observable2
instances directly, you can read values from params.$raw
. For example,
function MyComponentViewModel(params) { var currentObservableInstance = params.$raw.myExpr(); // Now currentObservableInstance is either observable1 or observable2 // and you would read its value with "currentObservableInstance()" }
This should be a very unusual scenario, so normally you will not need to work with $raw
.