自定义元素提供了一种将组件注入视图的方便方法。

本节目录

  • 介绍
  • 例子
  • 传递参数
    • 父组件和子组件之间的通信
    • 传递监控属性的表达式
  • 将标记传递到组件中
  • 控制自定义元素标记名称
  • 注册自定义元素
  • 备注1:将自定义元素与常规绑定相结合
  • 备注2:自定义元素不能自行关闭
  • 备注3:自定义元素和Internet Explorer 6到8
  • 高级应用:访问$ raw参数

介绍

自定义元素是组件绑定的语法替代(实际上,自定义元素使用后台的组件绑定)。
例如,一个繁琐写法的示范:

<div data-bind='component: { name: "flight-deals", params: { from: "lhr", to: "sfo" } }'></div>

其实可以更简单:

<flight-deals params='from: "lhr", to: "sfo"'></flight-deals>

示例

这个例子声明了一个组件,然后将它的两个实例注入到一个视图中。 请参阅下面的源代码。

First instance, without parameters

Second instance, passing parameters

UI源码:

<h4>First instance, without parameters</h4>
<message-editor></message-editor> <h4>Second instance, passing parameters</h4>
<message-editor params='initialText: "Hello, world!"'></message-editor>

视图模型代码:

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对象字面值(就像数据绑定属性一样),因此您可以传递任何类型的任意值。 例:

<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属性中引用模型属性,那么当然是指组件外部的viewmodel(“parent”或“host”viewmodel)上的属性,因为组件本身尚未实例化。 在上面的示例中,myModelValue将是父视图模型上的一个属性,并且将被子组件viewmodel的构造函数接收为params.someModelProperty。

这是如何将属性从父视图模型传递到子组件。 如果属性本身是可观察的,则父视图模型将能够观察并对子组件插入的任何新值做出反应。

传递可观察的表达式

在以下示例中,

<some-component
params='simpleExpression: 1 + 1,
simpleObservable: myObservable,
observableExpression: myObservable() + 1'>
</some-component>

...组件viewmodel params参数将包含三个值:

  • simpleExpression

    • 这将是数字值2.它不会是可观察值或计算值,因为没有涉及可观察值。

      一般来说,如果参数的求值不涉及对可观察量的求值(在这种情况下,该值不涉及可观察量),那么该值将按字面意义传递。如果值是一个对象,那么子组件可以改变它,但是由于它不可观察,所以父组件不会知道子组件已经这样做。

  • simpleObservable
    • 这将是在父viewmodel上声明为myObservable的ko.observable实例。它不是一个包装器 - 它是父母引用的实际相同的实例。因此,如果子viewmodel写入此observable,父viewmodel将接收到该更改。

      一般来说,如果一个参数的求值不涉及一个可观察值的计算(在这种情况下,观察值被简单地传递而不对其进行求值),那么这个值被字面传递。

  • observableExpression
    • 表达式本身,当被评估时,读取一个observable。该observable的值可能随时间而变化,因此表达式结果可能会随时间而变化。

      为了确保子组件能够对表达式值的更改做出反应,Knockout会自动将此参数升级为计算属性。因此,子组件将能够读取params.observableExpression()以获取当前值,或使用params.observableExpression.subscribe(...)等。

      一般来说,对于自定义元素,如果参数的求值涉及评估一个可观察量,则Knockout自动构造一个ko.computed值以给出该表达式的结果,并将其提供给该组件。

总之,一般规则是:

  1. 如果参数的求值不涉及可观察/计算的计算,则按字面意义传递。

  2. 如果参数的求值涉及到计算一个或多个可观察量/计算,它将作为计算属性传递,以便您可以对参数值的更改做出反应。

将标记传递到组件中

有时,您可能需要创建接收标记并将其用作其输出的一部分的组件。例如,您可能想要构建一个“容器”UI元素,例如网格,列表,对话框或标签集,可以接收和绑定内部的任意标记。

考虑可以如下调用的特殊列表组件:

<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>

默认情况下,<my-special-list>中的DOM节点将被剥离(不绑定到任何viewmodel)并由组件的输出替换。 但是,这些DOM节点不会丢失:它们被记住,并以两种方式提供给组件:

  • 作为数组$ componentTemplateNodes,可用于组件模板中的任何绑定表达式(即作为绑定上下文属性)。 通常这是使用提供的标记的最方便的方法。 请参见下面的示例。

  • 作为一个数组,componentInfo.templateNodes,传递给它的createViewModel函数

组件可以选择使用提供的DOM节点作为其输出的一部分,但是它希望,例如通过对组件模板中的任何元素使用template:{nodes:$ componentTemplateNodes}。

例如,my-special-list组件的模板可以引用$ componentTemplateNodes,以使其输出包括提供的标记。 下面是完整的工作示例:

Here is a special list

  • Here is another one of my special items

The person is years old.

UI源码:

<!-- 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>

视图模型源码:

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 }
])
});

这个“特殊列表”示例在每个列表项上面插入一个标题。 但是相同的技术可以用于创建复杂的网格,对话框,选项卡集等,因为这样的UI元素所需要的是常见的UI标记(例如,定义网格或对话框的标题和边框) 提供标记。

当使用没有自定义元素的组件时,即当直接使用组件绑定时传递标记,这种技术也是可能的。

控制自定义元素标记名称

默认情况下,Knockout假定您的自定义元素标记名称完全对应于使用ko.components.register注册的组件的名称。 这种约定超配置策略是大多数应用程序的理想选择。

如果你想要有不同的自定义元素标签名称,你可以覆盖getComponentNameForNode来控制这个。 例如,

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;
}
}

如果要控制哪些已注册组件的子集可以用作自定义元素,则可以使用此技术。

注册自定义元素

如果你使用默认的组件加载器,因此使用ko.components.register注册你的组件,那么没有什么额外的你需要做。 以这种方式注册的组件可以立即用作自定义元素。

如果你实现了一个自定义组件加载器,并且没有使用ko.components.register,那么你需要告诉Knockout你想要用作自定义元素的任何元素名称。 为此,只需调用ko.components.register - 您不需要指定任何配置,因为您的自定义组件加载器将不会使用配置。 例如,

ko.components.register('my-custom-element', { /* No config needed */ });

或者,您可以覆盖getComponentNameForNode以动态控制哪些元素映射到哪些组件名称,而与预注册无关。

备注1:将自定义元素与常规绑定相结合

如果需要,自定义元素可以具有常规的数据绑定属性(除了任何params属性)。 例如,

<products-list params='category: chosenCategory'
data-bind='visible: shouldShowProducts'>
</products-list>

但是,使用将修改元素内容的绑定(例如,文本或模板绑定)是没有意义的,因为它们会覆盖您的组件注入的模板。

Knockout将阻止使用任何使用controlsDescendantBindings的绑定,因为当尝试将其viewmodel绑定到注入的模板时,这也会与组件发生冲突。 因此,如果要使用if或foreach等控制流绑定,则必须将其包装在自定义元素周围,而不是直接在自定义元素上使用,例如:

<!-- ko if: someCondition -->
<products-list></products-list>
<!-- /ko -->

或者

<ul data-bind='foreach: allProducts'>
<product-details params='product: $data'></product-details>
</ul>

备注2:自定义元素不能自行关闭

您必须写入<my-custom-element> </ my-custom-element>,而不是<my-custom-element />。否则,您的自定义元素不会关闭,后续元素将被解析为子元素。

这是HTML规范的限制,不在Knockout可以控制的范围之内。 HTML解析器遵循HTML规范,忽略任何自闭合斜杠(除了少量的特殊“外来元素”,它们被硬编码到解析器中)。 HTML与XML不同。

注意:自定义元素和Internet Explorer 6到8

Knockout努力让开发人员处理跨浏览器兼容性问题的痛苦,特别是那些与旧版浏览器相关的问题!即使自定义元素提供了一种非常现代的web开发风格,他们仍然可以在所有常见的浏览器上工作:

  • HTML5时代的浏览器,包括Internet Explorer 9和更高版本,自动允许自定义元素没有困难。

  • Internet Explorer 6到8也支持自定义元素,但前提是它们在HTML解析器遇到任何这些元素之前注册。

IE 6-8的HTML解析器将丢弃任何无法识别的元素。为了确保不会丢弃您的自定义元素,您必须执行以下操作之一:

  • 确保在HTML解析器看到任何<your-component>元素之前调用ko.components.register('your-component')

  • 或者,至少在HTML解析器看到任何<your-component>元素之前调用document.createElement('your-component')。你可以忽略createElement调用的结果 - 所有重要的是你已经调用它。

例如,如果你像这样构造你的页面,那么一切都会OK:

<!DOCTYPE html>
<html>
<body>
<script src='some-script-that-registers-components.js'></script> <my-custom-element></my-custom-element>
</body>
</html>

如果你使用AMD,那么你可能更喜欢这样的结构:

<!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>

或者如果你真的不喜欢document.createElement调用的hackiness,那么你可以使用一个组件绑定为你的顶层组件,而不是一个自定义元素。 只要所有其他组件在您的ko.applyBindings调用之前注册,他们可以作为自定义元素在IE6-8生效:

<!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参数

考虑以下不寻常的情况,其中unObservable,observable 1和observable 2都是可观测量:

<some-component
params='myExpr: useObservable1() ? observable1 : observable2'>
</some-component>

由于评估myExpr涉及读取observable(useObservable1),KO将向组件提供参数作为计算属性。

但是,计算属性的值本身是可观察的。这似乎导致一个尴尬的情况,其中读取其当前值将涉及双解开(即,params.myExpr()(),其中第一个括号给出表达式的值,第二个给出的值结果可见实例)。

这种双重解开将是丑陋,不方便和意想不到的,所以Knockout自动设置生成的计算属性(params.myExpr)来为你解开它的值。也就是说,组件可以读取params.myExpr()以获取已选择的可观察值(observable1或observable2)的值,而不需要双重解开。

在不太可能发生的情况下,您不想自动解包,因为您想直接访问observable1 / observable2实例,您可以从params。$raw读取值。例如,

function MyComponentViewModel(params) {
var currentObservableInstance = params.$raw.myExpr(); // Now currentObservableInstance is either observable1 or observable2
// and you would read its value with "currentObservableInstance()"
}

这应该是一个非常不寻常的情况,所以通常你不需要使用$raw。

KnockoutJS 3.X API 第六章 组件(4) 自定义元素的更多相关文章

  1. KnockoutJS 3.X API 第六章 组件(5) 高级应用组件加载器

    无论何时使用组件绑定或自定义元素注入组件,Knockout都将使用一个或多个组件装载器获取该组件的模板和视图模型. 组件加载器的任务是异步提供任何给定组件名称的模板/视图模型对. 本节目录 默认组件加 ...

  2. KnockoutJS 3.X API 第六章 组件(3) 组件绑定

    组件绑定将指定的组件注入到元素中,并且可选地将参数传递给它. 本节目录 一个例子 API 组件生命周期 备注1:仅限模板组件 备注2:使用没有容器元素的组件 备注3:将标记传递给组件 处置和内存管理 ...

  3. KnockoutJS 3.X API 第六章 组件(2) 组件注册

    要使Knockout能够加载和实例化组件,必须使用ko.components.register注册它们,从而提供如此处所述的配置. 注意:作为替代,可以实现一个自定义组件加载器(自定义加载器下一节介绍 ...

  4. KnockoutJS 3.X API 第六章 组件(1) 组件和自定义元素 - 概述

    Components (组件)是一个强大的,干净的方式组织您的UI代码,可重复使用的块. : -可以表示单独的控件/窗口小部件或应用程序的整个部分 -包含自己的视图,通常(但可选)自己的视图模型 -可 ...

  5. KnockoutJS 3.X API 第四章 数据绑定(5) 控制流component绑定

    本节目录: 一个例子 API 备注1:仅模板式的component 备注2:component虚拟绑定 备注3:传递标记到component绑定 内存管理 一个例子 First instance, w ...

  6. 第六章 组件 67 使用ref获取DOM元素和组件引用

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8&quo ...

  7. KnockoutJS 3.X API 第七章 其他技术(8) 异步错误处理

    注意:本文档适用于Knockout 3.4.0及更高版本. ko.onError Knockout包装内部异步调用,并在抛出原始错误之前查找可选的ko.onError回调以执行(如果遇到异常). 这使 ...

  8. KnockoutJS 3.X API 第七章 其他技术(7) 微任务

    注意:本文档适用于Knockout 3.4.0及更高版本. Knockout的微任务队列 Knockout的微任务队列支持调度任务尽可能快地运行,同时仍然是异步的,努力安排它们在发生I / O,回流或 ...

  9. KnockoutJS 3.X API 第七章 其他技术(4) 速率限制

    注意:这个速率限制API是在Knockout 3.1.0中添加的. 通常,更改的observable立即通知其订户,以便依赖于observable的任何计算的observable或绑定都会同步更新. ...

随机推荐

  1. c#接口与抽象类的区别

    abstract 修饰符用于表示所修饰的类是不完整的,并且它只能用作基类.抽象类与非抽象类在以下方面是不同的: 抽象类不能直接实例化,并且对抽象类使用 new 运算符是编译时错误.虽然一些变量和值在编 ...

  2. C++: Virtual Table and Shared Memory

    See at: 补充栏3: C++对象和共享内存 (叙述内容和Link1的内容基本一致) <C++网络编程 卷1:运用ACE和模式消除复杂性> <C++ Network Progra ...

  3. bzoj3123: [Sdoi2013]森林

    题面传送门 复出的第一道题.. md就遇到坑了.. 简单来说就是可持久化线段树+启发式合并啊.. 感觉启发式合并好神奇好想学 每一次建边就暴力合并,每一个节点维护从根到它的权值线段树 按照题面的话最省 ...

  4. MVC Razor视图引擎的入门

    首先我们来说说他的给我们开发者带来那些好处吧: Razor语法易于输入,易于阅读,微软当时是这样定义的:简洁,富有表现力和灵活性,支持所有文本编辑器,强大的智能提示功能,单元测试. Rozor文件类型 ...

  5. 关于Web服务器的认识

    马上就要毕业了,也要开始找工作了,大学写了这么多代码了,却没有好好总结一下常用的概念很是遗憾额,就通过这篇博客记录一下我最常用的一些知识好了. 说到Web服务器,有很多文章都介绍的很好,之前看到一篇非 ...

  6. VS调式显示问题

    调式时,发现与以前的显示不太一样,虽然也能看到结果,但不是很方便,后来网上查找到与VS中的一个文件被修改有关. 找个别人安装过的VS2005,替换Common7\Packages\Debugger\a ...

  7. browsersync实现网页实时刷新(修改LESS,JS,HTML时)

    var gulp = require("gulp"), less = require("gulp-less"), browserSync = require(& ...

  8. HTTP协议入门要点

    应用层协议.基于tcp HTTP/0.9 命令 GET 特点 服务器只能回应HTML字符串 服务器发送完毕后就关闭tcp连接 HTTP/1.0 命令 GET POST HEAD 特点 每次通信都必须包 ...

  9. ACCEPTANCE CRITERIA FOR USER STORIES

    One of the teams I have recently coached quickly got a grasp of how to phrase user stories but found ...

  10. codeforces 360 D - Remainders Game

    D - Remainders Game Description Today Pari and Arya are playing a game called Remainders. Pari choos ...