Ember 互動性:事件、類和狀態

在這一點上,我們將開始為我們的應用程式新增一些互動性,提供新增和顯示新待辦事項的功能。在此過程中,我們將看看如何在 Ember 中使用事件,建立元件類來包含控制互動功能的 JavaScript 程式碼,以及設定服務來跟蹤應用程式的資料狀態。

先決條件

至少,建議您熟悉核心 HTMLCSSJavaScript 語言,並瞭解 終端/命令列

更深入地瞭解現代 JavaScript 特性(如類、模組等)將非常有利,因為 Ember 大量使用它們。

目標 學習如何建立元件類並使用事件來控制互動性,並使用服務來跟蹤應用程式狀態。

新增互動性

現在我們已經有了我們待辦事項應用程式的重構元件化版本,讓我們逐步瞭解如何新增使應用程式正常工作的互動性。

在開始考慮互動性時,最好宣告每個元件的目標和職責是什麼。在以下部分中,我們將對每個元件執行此操作,然後逐步引導您完成如何實現該功能。

建立待辦事項

對於我們的卡片標題/待辦事項輸入,我們希望能夠在按下 Enter 鍵時提交我們鍵入的待辦事項任務,並使其顯示在待辦事項列表中。

我們希望能夠捕獲鍵入到輸入框中的文字。我們這樣做是為了讓我們的 JavaScript 程式碼知道我們鍵入了什麼,我們可以儲存我們的待辦事項並將該文字傳遞給待辦事項列表元件以顯示。

我們可以透過 keydown 事件透過 on 修飾符 來捕獲該事件,這只是 Ember 對 addEventListenerremoveEventListener 的語法糖(如有必要,請參閱 事件簡介)。

將以下顯示的新行新增到您的 header.hbs 檔案中

hbs
<input
  class='new-todo'
  aria-label='What needs to be done?'
  placeholder='What needs to be done?'
  autofocus
  {{on 'keydown' this.onKeyDown}}
>

此新屬性位於雙大括號內,這告訴您它是 Ember 動態模板語法的部分。傳遞給 on 的第一個引數是要響應的事件型別(keydown),最後一個引數是事件處理程式 - 響應 keydown 事件觸發而執行的程式碼。正如您可能期望的那樣,從處理 普通 JavaScript 物件 開始,this 關鍵字指的是元件的“上下文”或“範圍”。一個元件的 this 將與另一個元件的 this 不同。

我們可以透過為您的元件生成一個元件類來定義 this 中可用的內容。這是一個普通 JavaScript 類,對 Ember 沒有任何特殊意義,除了擴充套件Component 超類。

要建立與您的標題元件一起使用的標題類,請在您的終端中鍵入以下內容

bash
ember generate component-class header

這將建立以下空類檔案 - todomvc/app/components/header.js

js
import Component from "@glimmer/component";

export default class HeaderComponent extends Component {}

在此檔案中,我們將實現事件處理程式程式碼。將內容更新為以下內容

js
import Component from "@glimmer/component";
import { action } from "@ember/object";

export default class HeaderComponent extends Component {
  @action
  onKeyDown({ target, key }) {
    let text = target.value.trim();
    let hasValue = Boolean(text);

    if (key === "Enter" && hasValue) {
      alert(text);

      target.value = "";
    }
  }
}

@action 裝飾器是這裡唯一的 Ember 特定程式碼(除了擴充套件自 Component 超類,以及我們使用 JavaScript 模組語法 匯入的 Ember 特定專案) - 檔案的其餘部分是普通的 JavaScript,可以在任何應用程式中使用。@action 裝飾器宣告該函式是一個“操作”,這意味著它是一種從模板中發生的事件呼叫的函式型別。@action 還將函式的 this 繫結到類例項。

注意:裝飾器基本上是一個包裝函式,它包裝並呼叫其他函式或屬性,在此過程中提供額外的功能。例如,@tracked 裝飾器(稍後將看到)執行它應用到的程式碼,但還會跟蹤它並自動更新應用程式的值發生變化時。 閱讀 JavaScript 裝飾器:它們是什麼以及何時使用它們,以獲取有關裝飾器的更多一般資訊。

回到我們的應用程式正在執行的瀏覽器選項卡,我們可以鍵入任何我們想要的內容,當我們按下 Enter 時,我們會收到一個警報訊息,告訴我們我們鍵入了什麼。

the initial placeholder state of the add function, showing the text entered into the input elements being alerted back to you.

在標題輸入的互動性完成後,我們需要一個地方來儲存待辦事項,以便其他元件可以訪問它們。

使用服務儲存待辦事項

Ember 具有內建的應用程式級狀態管理,我們可以使用它來管理待辦事項的儲存並允許我們的每個元件訪問來自該應用程式級狀態的資料。Ember 將這些構造稱為 服務,並且它們在頁面的整個生命週期中都存在(頁面重新整理將清除它們;將資料持久儲存更長的時間超出了本教程的範圍)。

執行以下終端命令為我們生成一個服務,以將我們的待辦事項列表資料儲存在其中

bash
ember generate service todo-data

這應該給你一個類似這樣的終端輸出

installing service
  create app/services/todo-data.js
installing service-test
  create tests/unit/services/todo-data-test.js

這會在 todomvc/app/services 目錄中建立一個 todo-data.js 檔案來包含我們的服務,該服務最初包含一個匯入語句和一個空類

js
import Service from "@ember/service";

export default class TodoDataService extends Service {}

首先,我們要定義什麼是待辦事項。我們知道我們想要跟蹤待辦事項的文字以及它是否已完成。

在現有匯入語句下方新增以下匯入語句

js
import { tracked } from "@glimmer/tracking";

現在在您新增的上一行下方新增以下類

js
class Todo {
  @tracked text = "";
  @tracked isCompleted = false;

  constructor(text) {
    this.text = text;
  }
}

此類表示一個待辦事項 - 它包含一個@tracked text 屬性,該屬性包含待辦事項的文字,以及一個@tracked isCompleted 屬性,該屬性指定待辦事項是否已完成。例項化時,Todo 物件的初始text 值將等於建立時賦予它的文字(見下文),而isCompleted 值將為false。此類中唯一與 Ember 相關的部分是@tracked 裝飾器 - 這與響應式系統掛鉤,並允許 Ember 自動更新您在應用程式中看到的內容,如果跟蹤的屬性發生變化。 此處可以找到有關跟蹤的更多資訊

現在是時候新增服務的正文了。

首先在前面的匯入語句下方新增另一個import 語句,以使操作在服務內部可用

js
import { action } from "@ember/object";

將現有的export default class TodoDataService extends Service { } 塊更新為以下內容

js
export default class TodoDataService extends Service {
  @tracked todos = [];

  @action
  add(text) {
    let newTodo = new Todo(text);

    this.todos.pushObject(newTodo);
  }
}

在這裡,服務上的todos 屬性將維護我們的待辦事項列表,這些待辦事項包含在陣列中,我們將在其上標記@tracked,因為當todos 的值更新時,我們希望 UI 也更新。

就像之前一樣,將從模板呼叫的add() 函式用@action 裝飾器進行註釋,以將其繫結到類例項。雖然這看起來可能像熟悉的 JavaScript,但您可能會注意到我們正在對我們的todos 陣列呼叫方法 pushObject()。這是因為 Ember 預設情況下擴充套件了 JavaScript 的 Array 原型,為我們提供了方便的方法來確保 Ember 的跟蹤系統瞭解這些更改。有數十種這樣的方法,包括pushObjects()insertAt()popObject(),這些方法可以與任何型別一起使用(不僅僅是物件)。Ember 的 ArrayProxy 還為我們提供了更多方便的方法,例如isAny()findBy()filterBy(),以使生活更輕鬆。

從我們的標題元件中使用服務

現在我們已經定義了一種新增待辦事項的方法,我們可以從header.js 輸入元件與該服務進行互動,以實際開始新增它們。

首先,需要透過@inject 裝飾器將服務注入模板,我們將將其重新命名為@service 以獲得語義清晰度。為此,將以下import 行新增到header.js,位於兩個現有的import 行下方

js
import { inject as service } from "@ember/service";

使用此匯入,我們現在可以透過todos 物件(使用@service 裝飾器)在HeaderComponent 類中使用todo-data 服務。在開啟的export… 行下方新增以下行

js
@service('todo-data') todos;

現在可以將佔位符alert(text); 行替換為對我們新的add() 函式的呼叫。用以下內容替換它

js
this.todos.add(text);

如果我們在瀏覽器(npm start,轉到localhost:4200)中嘗試使用待辦事項應用程式,在按下 Enter 鍵後,看起來什麼也沒發生(雖然應用程式在沒有錯誤的情況下構建是一個好兆頭)。但是,使用 Ember Inspector,我們可以看到我們的待辦事項已新增

The app being shown in the Ember inspector, to prove that added todos are being stored by the service, even if they are not being displayed in the UI yet

顯示我們的待辦事項

現在我們知道我們可以建立待辦事項,需要一種方法來用我們實際建立的待辦事項替換我們靜態的“購買電影票”待辦事項。在TodoList 元件中,我們希望從服務中獲取待辦事項,併為每個待辦事項渲染一個Todo 元件。

為了從服務中檢索待辦事項,我們的TodoList 元件首先需要一個支援元件類來包含此功能。按 Ctrl + C 停止開發伺服器,然後輸入以下終端命令

bash
ember generate component-class todo-list

這將生成新的元件類todomvc/app/components/todo-list.js

使用以下程式碼填充此檔案,該程式碼透過todos 屬性將todo-data 服務公開給我們的模板。這使得它在類和模板內部都可以透過this.todos 訪問

js
import Component from "@glimmer/component";
import { inject as service } from "@ember/service";

export default class TodoListComponent extends Component {
  @service("todo-data") todos;
}

這裡的一個問題是我們的服務名為todos,但待辦事項列表也名為todos,因此目前我們將使用this.todos.todos 訪問資料。這並不直觀,因此我們將為todos 服務新增一個 getter,名為all,它將表示所有待辦事項。

為此,請返回您的todo-data.js 檔案,並在@tracked todos = []; 行下方新增以下內容

js
get all() {
  return this.todos;
}

現在我們可以使用this.todos.all 訪問資料,這更加直觀。為了使其生效,請轉到您的todo-list.hbs 元件,並將靜態元件呼叫替換為以下內容

hbs
<Todo />
<Todo />

使用動態的 #each 塊(本質上是對 JavaScript 的 forEach() 的語法糖),為服務 all() 獲取器返回的待辦事項列表中的每個待辦事項建立一個 <Todo /> 元件。

hbs
{{#each this.todos.all as |todo|}}
<Todo @todo={{todo}} />
{{/each}}

另一種看待方式

  • this - 渲染上下文 / 元件例項。
  • todos - this 上的屬性,我們在 todo-list.js 元件中使用 @service('todo-data') todos; 定義。這是一個對 todo-data 服務的引用,允許我們直接與服務例項互動。
  • all - todo-data 服務上的獲取器,返回所有待辦事項。

嘗試再次啟動伺服器並導航到我們的應用程式,你會發現它有效!好吧,有點。每當你輸入一個新的待辦事項時,在文字輸入框下方會出現一個新的列表項,但不幸的是,它總是顯示“購買電影票”。

這是因為每個列表項中的文字標籤是硬編碼的,如 todo.hbs 中所示。

hbs
<label>Buy Movie Tickets</label>

更新這一行以使用引數 @todo - 它將代表我們在 todo-list.hbs 中呼叫此元件時傳入的待辦事項,在行 <Todo @todo={{todo}} /> 中。

hbs
<label>{{@todo.text}}</label>

好的,再試一次。你應該發現現在在 <input> 中提交的文字在 UI 中正確地反映出來。

The app being shown in its final state of this article, with entered todo items being shown in the UI

總結

好的,所以現在進展很大。我們現在可以將待辦事項新增到我們的應用程式中,資料的狀態使用我們的服務跟蹤。接下來,我們將繼續讓我們的頁尾功能起作用,包括待辦事項計數器,並檢視條件渲染,包括在待辦事項被選中時正確地對其進行樣式化。我們還將連線我們的“清除已完成”按鈕。