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
.