• KnockoutJS 3.X API 第六章 组件(4) 自定义元素


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

    本节目录

    • 介绍
    • 例子
    • 传递参数
      • 父组件和子组件之间的通信
      • 传递监控属性的表达式
    • 将标记传递到组件中
    • 控制自定义元素标记名称
    • 注册自定义元素
    • 备注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,以使其输出包括提供的标记。 下面是完整的工作示例:

    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。

  • 相关阅读:
    Hadoop3.0时代,怎么能不懂EC纠删码技术?| 个推技术实践
    linux系统安装mysql
    集合随机排序,list随机排序
    linux性能调优、内存、cpu使用相关命令
    java获取方法调用类
    移动云采用xshell连接
    nginx操作命令
    linux下mysql8版本修改登录密码
    登录验证码图片
    sudo: aptget: command not found
  • 原文地址:https://www.cnblogs.com/smallprogram/p/5973604.html
Copyright © 2020-2023  润新知