文章出處

自定義元素提供了一種將組件注入視圖的方便方法。

本節目錄

  • 介紹
  • 例子
  • 傳遞參數
    • 父組件和子組件之間的通信
    • 傳遞監控屬性的表達式
  • 將標記傳遞到組件中
  • 控制自定義元素標記名稱
  • 注冊自定義元素
  • 備注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。


    文章列表




    Avast logo

    Avast 防毒軟體已檢查此封電子郵件的病毒。
    www.avast.com


    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()