(三)Knockout 控制流程

foreach

示例1:迭代數組

foreach binding主要做用於lists或是tables內數據單元的動態綁定。下面是一個簡單的例子:javascript

<table>
          <thead>
            <tr>
              <th>First Name</th>
              <th>Last Name</th>
            </tr>
          </thead>
          <tbody data-bind="foreach: people">
            <tr>
              <td data-bind="text: firstName"></td>
              <td data-bind="text: lastName"></td>
            </tr>
          </tbody>
        </table>
    <script type="text/javascript">
      var myViewModel = {
        people: [
          { firstName: "Chiaki", lastName: "Izumi" },
          { firstName: "Kazusa", lastName: "Touma" },
          { firstName: "Haruki", lastName: "Murakami" }
        ]
      };
      ko.applyBindings(myViewModel);
    </script>

示例2:添加 / 刪除的實例

在上述示例中,咱們簡單的在ko.applybindings中添加了一個數組並將其綁定在一個tbody元素中,咱們也能夠自定義一個view model來實現這一效果,下面是一個更爲複雜一些的例子:html

<h4>People</h4>
    <ul data-bind="foreach: people">
      <li>
        Person at position <span data-bind="text: $index"></span>:
        <span data-bind="text: name"></span>
        <a href="#" data-bind="click: $parent.removePerson">Remove</a>
      </li>
    </ul>
    <button data-bind="click: addPerson">Add</button>
    <script type="text/javascript">
      function MyViewModel() {
        var self = this;

        self.people = ko.observableArray([
          { name: "Chiaki" },
          { name: "Yuki" },
          { name: "Kazusa" }
        ]);

        self.addPerson = function() {
          self.people.push({ name: "New name at " + new Date() });
        };

        self.removePerson = function() {
          self.people.remove(this);
        };
      }

      ko.applyBindings(new MyViewModel());
    </script>


參數java

  • 主要參數
    傳遞但願迭代的數組。綁定將爲每一個條目輸出一段標記。
    或者,傳遞一個JavaScript對象文本和一個名爲data的屬性,該屬性是您但願迭代的數組。對象文字還可能具備其餘屬性,如afterAddincludeDestroyed,有關這些額外選項的詳細信息及其使用示例,請參見下面。
    若是您提供的數組是可見的,那麼foreach綁定將經過在DOM中添加或刪除標記的相應部分來響應數組內容的任何將來更改。

註釋1:使用 $data 引用每一個數組條目

如上面的示例所示,foreach塊中的綁定能夠引用數組條目上的屬性。 例如,示例1引用了每一個數組條目的firstName和lastName屬性。
可是,若是您想引用數組條目自己(而不只僅是它的一個屬性),該怎麼辦?在這種狀況下,可使用特殊的上下文屬性$data。在foreach塊中,它表示當前項。例如:node

<ul data-bind="foreach: months">
  <li>
      The current item is: <b data-bind="text: $data"></b>
  </li>
</ul>
  
<script type="text/javascript">
  ko.applyBindings({
      months: [ 'Jan', 'Feb', 'Mar', 'etc' ]
  });
</script>

若是須要,能夠在引用每一個條目上的屬性時使用$data做爲前綴。例如,您能夠將示例1的部分重寫以下jquery

<!-- $data 引用對象每一個值-->
<td data-bind="text: $data.firstName"></td>

但您沒必要這樣作,由於firstName在默認狀況下將在$data上下文中進行計算。若是數組中的項是被監控的,$data將引用每一個監控的值。要引用可觀察對象自己,推薦使用$rawDatagit

<!--$rawDat 引用對象自己-->
<td data-bind="text: $rawData.firstName"></td>

註釋2:使用$index、$parent和其餘上下文屬性

從上面的示例2能夠看出,可使用$index引用當前數組項的從零開始的索引。$index是一個可觀察的對象,當項目的索引起生變化時(例如,若是項目被添加到數組中或從數組中刪除),$index就會被更新。
相似地,您可使用$parent 引用來自foreach外部的數據,例如。github

<h1 data-bind="text: blogPostTitle"></h1>
    <ul data-bind="foreach: likes">
        <li>
            <b data-bind="text: name"></b> likes the blog post <b data-bind="text: $parent.blogPostTitle"></b>
        </li>
    </ul>
   
  <script type="text/javascript">
     function AppViewModel() {
    var self = this;

    self.blogPostTitle =ko.observable('你好');

    self.likes = ko.observableArray([
        { name: 'Bert' },
        { name: 'Charles' },
        { name: 'Denise' }
    ]);
}
 
ko.applyBindings(new AppViewModel());
  </script>

註釋3:使用 「as」 的別名爲 「foreach」 項目

在註釋1中提到,咱們可以經過$data來調用foreach綁定的數組自己,可是當咱們使用嵌套的foreach,內層foreach如何可以調用外層foreach綁定的數組呢?這時咱們能夠藉由as給外層foreach所綁定的數組定義一個另外的名稱,進而在內層foreach中利用此名稱來調用外層foreach所綁定的數組。接下來是一個簡單的例子:ajax

<ul data-bind="foreach: { data: person, as: 'person' }">
      <li>
        <ul data-bind="foreach: { data: friends, as: 'friends' }">
          <li>
            <span data-bind="text: person.name"></span>:
            <span data-bind="text: friends"></span>
          </li>
        </ul>
      </li>
    </ul>

    <script>
      var viewModel = {
        person: ko.observableArray([
          { name: "Chiaki", friends: ["Charlie", "Kazusa"] },
          { name: "Kazusa", friends: ["Chiaki", "Charlie"] },
          { name: "Charlie", friends: ["Chiaki", "Kazusa"] }
        ])
      };

      ko.applyBindings(viewModel);
    </script>


這個例子中的外層foreach綁定的是person數組,person數組中有一個屬性name和另一個數組firends,在內層foreach中綁定的是數組firends。當咱們在內層foreach要調用外層的person數組內的屬性時,藉由as,使用了person.name來調用。而這裏也有一個有趣的狀況,就是當一個數組裏面只有單純的元素,好比說friends數組,它的元素並非object,也就不存在這identifier的問題,這時咱們要調用它的元素時,只須要調用數組自己便可,這也就是爲何在以前的示例中若是咱們調用綁定的數組自己會返回[object, object]。api

這代表,通常狀況下,遍歷數組中的元素只須要調用數組名(as指定)或是調用$data便可,而碰到那些內部元素是object的時候,咱們要調用object內的屬性則須要調用相應屬性的名稱。數組

另外須要注意的一點就是,as後所跟着的必須是一個字符串(as: "person"而不是as: person)。

註釋4:不使用foreach當容器

有些狀況下,咱們使用foreach的場合會比較複雜,好比說以下的例子:

<ul>
      <li>Header item</li>
      <!-- The following are generated dynamically from an array -->
      <li>Item A</li>
      <li>Item B</li>
      <li>Item C</li>
    </ul>

這種狀況下,咱們並不知道改在哪一個元素內添加foreach。若是是在ul添加的話,咱們事先寫好的header item便會被覆蓋掉,而ul元素內又只能嵌套li元素,添加另外一個容器來實現foreach也是不可行的。爲了解決這一問題,咱們須要使用無容器的控制流語法(containerless control flow syntax),與先前所提到的containerless text syntax相似。一個簡單的例子以下:

<!-- 不使用foreach,使用容器 -->
    <ul>
      <li>Header item</li>
      <!-- ko foreach: people -->
      <li>name: <span data-bind="text: $data"></span></li>
      <!-- /ko -->
    </ul>
    
    <!-- 使用foreach -->
    <ul data-bind="foreach: people">
      <li>Header item</li>
      <li>name: <span data-bind="text: $data"></span></li>
    </ul>

    <script>
      var viewModel = {
        people: ko.observableArray(["Kazusa", "Chiaki", "Yuki"])
      };

      ko.applyBindings(viewModel);
    </script>

註釋7:處理後所生成的 DOM 元素

當咱們須要在生成的DOM元素上運行一些自定義的邏輯時,咱們能夠用到

  • afterRender
  • afterAdd
  • beforeRemove
  • beforeMove
  • afterMove
    等回調函數。

須要注意的是,這些回調函數僅僅適用於觸發與列表的改變有關的動畫,若是咱們須要對新添加的DOM元素附加一些其餘的行爲,好比說事件的處理或是第三方的UI控制,將這些行爲做爲自定義的綁定(custom binding)會更爲合適,由於這樣設定的行爲是與foreach互相獨立的。

接下來是一個調用afterAdd的簡單的例子,其中用到了jQuery Color plugin

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.1/jquery.js"></script>
<script src="http://code.jquery.com/color/jquery.color-2.1.2.min.js"></script>
<script src="../js/knockout-3.5.0rc2.debug.js"></script>
<ul data-bind="foreach: { data: people, afterAdd: yellowFadeIn }">
      <li data-bind="text: $data"></li>
    </ul>

    <button data-bind="click: addItem">Add</button>

    <script>
      var viewModel = {
        people: ko.observableArray(["Kazusa", "Chiaki", "Yuki"]),

        yellowFadeIn: function(element, index, data) {
          $(element)
            .filter("li")
            .animate({ backgroundColor: "yellow" }, 200)
            .animate({ backgroundColor: "white" }, 800);
        },

        addItem: function() {
          this.people.push("New person");
        }
      };

      ko.applyBindings(viewModel);
    </script>

如下是對一些回調函數詳盡的介紹:

afterRender是在foreach模塊初始化或是添加新的元素時觸發的,其接受的參數爲(爲了可以保持願意,這裏用英文顯示,下同):

An array of the inserted DOM elements
The data item against which they are being bound
afterAddafterRender相似,不過它只會在新元素添加時觸發(foreach初始化的時候並不會觸發),它所接受的參數爲:

A DOM node being added to the document
The index of the added array element
The added array element
beforeRemove函數會在數組中的某一項被刪除時觸發。須要注意的是,beforeRemove實際上替代了UI界面對remove所作出的迴應,即在beforeRemove函數中若是不對DOM相應的元素進行remove操做,則在頁面上的元素是不會被刪除的,可是viewModel中的數組相應的元素卻已經被刪除了。beforeRemove函數接受如下參數:

A DOM node that you should remove
The index of the removed array element
The removed array element
beforeMove函數會在數組中的元素索引起生變化時觸發,beforeMove會應用於全部索引產生變化的元素,即倘若咱們在數組開頭插入元素,則其後全部元素都將受到beforeMove回調函數的影響。一個比較經常使用的作法是經過beforeMove來保存原有元素的座標以便咱們可以在afterMove中控制元素移動的動畫。beforeMove函數接受如下參數:

A DOM node that may be about to move
The index of the moved array element
The moved array element
afterMove函數也會在數組中的元素索引起生變化時觸發,afterMove會應用於全部索引產生變化的元素,即倘若咱們在數組開頭插入元素,則其後全部元素都將受到afterMove回調函數的影響。afterMove函數接收如下參數:

A DOM node that may have moved
The index of the moved array element
The moved array element
對於回調函數中的beforeafter,咱們應該有一個比較清醒的認識。before和after針對的都是UI界面中的元素變化,也就是頁面產生變化以前和頁面產生變化以後,與此同時,viewModel部分的數組已經發生了變化,正是viewModel部分的數組的變化才觸發了before和after所對應的回調函數。

if和ifnot綁定

if binding與visible binding相似。不一樣之處在於,包含visible binding的元素會在DOM中一直保存,而且該元素相應的data-bind屬性會一直保持,visible binding只是利用CSS來觸發元素的可見性。另外一方面,if binding是物理地增長或刪除包含它的元素,而且元素內的data-bind只有在判斷語句爲真時才生效。

例子1

下面是一個簡單的if binding的例子:

<label>
      <input type="checkbox" data-bind="checked: displayMessage" /> 
      Display message
    </label>

    <div data-bind="if: displayMessage">Here is a message. Astonishing.</div>
    <script type="text/javascript">
      ko.applyBindings({
        displayMessage: ko.observable(false)
      });
    </script>

例子2

在下面的例子中,<div>元素對於「Mercury」是空的,可是對於「Earth」是填充的。這是由於 Earth 有一個非空 capital 屬性,而 Mercury 的 capital 屬性爲 null

<ul data-bind="foreach: planets">
          <li>
              Planet: <b data-bind="text: name"> </b>
              <div data-bind="if: capital">
                  Capital: <b data-bind="text: capital.cityName"> </b>
              </div>
          </li>
      </ul>
      
      <script>
          ko.applyBindings({
              planets: [
                  { name: 'Mercury', capital: null }, 
                  { name: 'Earth', capital: { cityName: 'Barnsley' } }        
              ]
          });
      </script>

重要的是要理解 if 綁定對於使代碼正常工做很是重要。沒有它,在評估時就會出現錯誤。「Mercury」上下文中的 capital.cityName ,其中 capitalnull。在JavaScript中,不容許計算空值或未定義值的子屬性。

無容器

if binding也支持無容器的控制流語法,一個簡單的示例以下:

<label>
      <input type="checkbox" data-bind="checked: displayMessage" /> 
      Display message
    </label>

        <ul>
            <li>Item 1</li>
            <!-- ko if: displayMessage -->
            <li>Message</li>
            <!-- /ko -->
        </ul>

    <div data-bind="if: displayMessage">Here is a message. Astonishing.</div>
    <script type="text/javascript">
      ko.applyBindings({
        displayMessage: ko.observable(false)
      });
    </script>

ifnot

<div data-bind="ifnot: someProperty">...</div>

等價於

<div data-bind="if: !someProperty()">...</div>

with和using綁定

withusing 綁定建立一個新的綁定上下文,以便將後代元素綁定到指定對象的上下文中。(這些綁定之間的區別將在下面的參數中描述。)
固然,您能夠任意嵌套使用with綁定以using及其餘控制流綁定(如ifforeach )。

例子1:

下面是一個將綁定上下文切換到子對象的很是基本的示例。注意,在 data-bind屬性中,沒有必要在經緯度前面加上coords。,由於綁定上下文已切換到coords
這裏也可使用 with

<h1 data-bind="text: city"> </h1>
      <p data-bind="using: coords">
          Latitude: <span data-bind="text: latitude"> </span>,
          Longitude: <span data-bind="text: longitude"> </span>
      </p>
       
      <script type="text/javascript">
          ko.applyBindings({
              city: "London",
              coords: {
                  latitude:  51.5001524,
                  longitude: -0.1262362
              }
          });
      </script>

例子2

<form data-bind="submit: getTweets">
      Twitter account:
      <input data-bind="value: twitterName" />
      <button type="submit">Get tweets</button>
    </form>

    <div data-bind="with: resultData">
      <h3>
        Recent tweets fetched at <span data-bind="text: retrievalDate"> </span>
      </h3>
      <ol data-bind="foreach: topTweets">
        <li data-bind="text: text"></li>
      </ol>

      <button data-bind="click: $parent.clearResults">Clear tweets</button>
    </div>

    <script type="text/javascript">
      function AppViewModel() {
        var self = this;
        self.twitterName = ko.observable("@example");
        self.resultData = ko.observable(); // No initial value

        self.getTweets = function() {
          var name = self.twitterName(),
            simulatedResults = [
              { text: name + " What a nice day." },
              { text: name + " Building some cool apps." },
              { text: name + " Just saw a famous celebrity eating lard. Yum." }
            ];

          self.resultData({
            retrievalDate: new Date(),
            topTweets: simulatedResults
          });
        };

        self.clearResults = function() {
          self.resultData(undefined);
        };
      }

      ko.applyBindings(new AppViewModel());
    </script>

從這裏例子中,咱們能夠看出with binding在使用時的幾個特色。

  • 當with binding所綁定的binding context爲null或是undefined時,包含with binding的元素的全部子元素都將從UI頁面中移除。
  • 若是咱們須要從parent binding context中獲取data或是function,咱們可使用特殊的context properties好比說$parentroot,這部分能夠參考The binding context

假若綁定的binding context是一個observable,包含with binding的元素會隨着observable的變化而移除現有的子孫元素並添加一系列隸屬於新的binding context的子孫元素。

相似的,with binding也提供無容器的控制流語法,這裏省略例子,能夠參考if binding等。

with和using區別

若是您提供的表達式包含任何監控的值,則每當這些值發生更改時,將從新計算該表達式。這些綁定在綁定值發生變化時的反應不一樣:
- 對於with綁定,將清除後代元素,並將標記的新副本添加到文檔中,並在新值的上下文中綁定。
- 對於using綁定,後代元素將保留在文檔中,並使用新的上下文值從新評估它們的綁定。

附加參數

  • as
    as選項容許爲新上下文對象設置別名。雖然您可使用$data上下文變量引用對象,可是使用as選項爲它提供一個更具描述性的名稱可能會頗有用
<div data-bind="with: currentPerson, as: 'person'"></div>
  • noChildContext
      as選項的默認行爲是爲提供的對象設置一個名稱,同時仍然將內容綁定到對象。可是您可能更願意保持上下文不變,只設置對象的名稱。後一種行爲多是未來版本的擊倒的默認行爲。若要打開特定綁定,請將noChildContext選項設置爲true。當這個選項與as一塊兒使用時,對對象的全部訪問都必須經過給定的名稱,而且$data將保持設置爲外部viewmodel。
      對於using綁定,雖然您可使用此選項,但使用let綁定一般會更有效和描述性。 而不是 using: currentPerson, as: 'person', noChildContext: true,你可使用 let: { person: currentPerson }

註釋1:無容器

與其餘控制流綁定(如if和foreach)同樣,可使用with和using而不使用任何容器元素來承載它。若是您須要在不合法的地方使用這些綁定,僅僅爲了保存綁定而引入新的容器元素,那麼這是很是有用的。

<ul>
    <li>Header element</li>
    <!-- ko with: outboundFlight -->
        ...
    <!-- /ko -->
    <!-- ko with: inboundFlight -->
        ...
    <!-- /ko -->
</ul>

註釋2:爲何使用兩個相似的綁定?

當不須要從新呈現後代元素時,他在knockoutjs 3.5中using了綁定來替代with。(參考with和using區別) 由於using從新評估後代綁定而不是從新呈現,每一個後代綁定將包含對使用上下文的附加依賴。

component 綁定

component 組件綁定將指定的組件注入元素,並可選地向其傳遞參數。

示例1:計算字數

<h4>First instance, without parameters</h4>
      <div data-bind='component: "message-editor"'></div>
       
      <h4>Second instance, passing parameters</h4>
      <div data-bind='component: {
          name: "message-editor",
          params: { initialText: "Hello, world!" }
      }'></div>

    <script type="text/javascript">
      ko.components.register('message-editor', {
        viewModel: function (params) {
          this.text = ko.observable(params && params.initialText || '');
        },
        template: 'Message: <input data-bind="value: text" /> '
          + '(length: <span data-bind="text: text().length"></span>)'
      });

      ko.applyBindings();
    </script>


注意:在更現實的狀況下,您一般會從外部文件加載組件視圖模型和模板,而不是將它們硬編碼到註冊中。請參見示例和註冊文檔。

API

有兩種方法可使用組件綁定

  • 速記語法
    若是隻傳遞一個字符串,它將被解釋爲組件名。而後注入命名組件,而不向其提供任何參數。例子
<div data-bind='component: "my-component"'></div>

簡寫值也能夠被監控到。在本例中,若是組件綁定發生更改,則組件綁定將處理(dispose)舊組件實例,並注入新引用的組件。例子

<div data-bind='component: observableWhoseValueIsAComponentName'></div>
  • 完整語法
    要向組件提供參數,請傳遞具備如下屬性的對象
    • name 要注入的組件的名稱。這也是能夠觀察到的。
    • params 將傳遞給組件的對象。一般,這是一個包含多個參數的鍵值對象,一般由組件的viewmodel構造函數接收。
    <div data-bind='component: {
      name: "shopping-cart",
      params: { mode: "detailed-list", items: productsList }
    }'></div>
    請注意,不管什麼時候刪除組件(要麼是由於名稱更改了observable,要麼是由於封閉的控制流綁定刪除了整個元素),刪除的組件被釋放( disposed)。

組件生命週期

1.組件加載器被要求提供 viewmodel 工廠和模板
  一般,這是一個異步過程。 它可能涉及對服務器的請求。 對於API一致性,默認狀況下Knockout確保加載過程做爲異步回調完成,即便組件已經加載並緩存在內存中也是如此。 有關此內容以及如何容許同步加載的更多信息,請參閱控制同步/異步加載
2.組件模板被克隆並注入容器元素
3.若是組件有一個視圖模型 viewmodel ,則實例化它
4.視圖模型 viewmodel 綁定到視圖
5.組件被激活
6.組件被拆除,視圖模型被放置

注意1:僅有模板組件

組件一般有視圖模型,但不必定非得有。組件能夠只指定模板。
在本例中,綁定組件視圖的對象是傳遞給組件綁定的params對象。例子

ko.components.register('special-offer', {
    template: '<div class="offer-box" data-bind="text: productName"></div>'
});

能夠注入:

<div data-bind='component: {
     name: "special-offer-callout",
     params: { productName: someProduct.name }
}'></div>

或者,更方便地,做爲自定義元素

<special-offer params='productName: someProduct.name'></special-offer>

使用無容器

<!-- ko component: {
    name: "message-editor",
    params: { initialText: "Hello, world!", otherParam: 123 }
} -->
<!-- /ko -->

標記傳遞給組件

附加組件綁定的元素可能包含進一步的標記。例如

<div data-bind="component: { name: 'my-special-list', params: { items: someArrayOfPeople } }">
    <!-- Look, here's some arbitrary markup. By default it gets stripped out
         and is replaced by the component output. -->
    The person <em data-bind="text: name"></em>
    is <em data-bind="text: age"></em> years old.
</div>

儘管該元素中的DOM節點將被刪除,而且默認狀況下不進行綁定,但它們不會丟失。相反,它們被提供給組件(在本例中是my-special-list),組件能夠按照本身的意願將它們包含在輸出中。
若是您想要構建表示容器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>

    <script type="text/javascript">
      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 }
        ])
      });
    </script>

相關文章
相關標籤/搜索