これはサイコロローラープログラムで、サイコロを「ロール」し、パラメーターを事前に設定し、以前の「ロール」の履歴を表示できます。 彼女のコードはGithubにあります。
Ember.jsフレームワークには、JavaScriptの世界の多くの最新のコンセプトとテクノロジーが含まれています。 その機能の中で、私は次のことに注意したいと思います。
- Babelトランスポーターを使用して、ES2016サポートを提供します。
- テストツールTestemおよびQTestのサポートにより、単体テスト、統合テスト、受け入れテストの機会が広がります。
- Broccoli.jsコレクターのサポート。
- Webページのインタラクティブな再読み込み。開発プロセスを高速化します。
- ハンドルバーテンプレートエンジンのサポート。
- 主にURLルートを作成する開発モデルを使用します。 これにより、ディープリンクが完全にサポートされます。
- JSON APIに基づいてデータを操作するためのレイヤーが存在しますが、使用する必要があるAPIへの接続をサポートします。
Ember.jsの使用を開始するには、Node.jsとnpmの最新バージョンが必要です。 これはすべてNode.jsからダウンロードできます。
さらに、Ember.jsはフロントエンド指向のフレームワークであると言う価値があります。 さまざまなバックエンドと対話する多くの方法をサポートしていますが、サーバーコードに関連するすべてのことはEmberの責任ではありません。
ember-cliの紹介
Ember.jsコマンドラインインターフェース
ember-cli
は、このフレームワークの多くの機能へのアクセスを提供します。
Ember-cli
は、作業のすべての段階でプログラマーをサポートします。 アプリケーションの作成、機能の拡張、テストおよび開発モードでのプロジェクトの起動を簡素化します。
Emberアプリケーションの作成中に行うことのほとんどすべてに、ある程度まで
ember-cli
使用が含まれます。 したがって、このツールを研究することが重要です。 私たちは、教育プロジェクトの作業中、常にそれを使用します。
作業の最初のステップは、
ember-cli
をインストールすることです。すでにインストールされている場合は、既存のバージョンの関連性を確認します。 次のコマンドを使用して、
npm
レジストリから
ember-cli
をインストールできます。
$ npm install -g ember-cli
インストールが成功したかどうかを確認し、同時にインストールされている
ember-cli
バージョンを確認するには、次のコマンドを使用できます。
$ ember --version ember-cli: 2.15.0-beta.1 node: 8.2.1 os: darwin x64
Ember.jsでの最初のアプリケーションの作成
ember-cli
インストール後、アプリケーションの作成を開始できます。 これは、
ember-cli
を使用する最初の状況です。 彼は、アプリケーションの構造を作成し、構成し、作業プロジェクトを提供します。 次のコマンドを使用して、
dice-roller
アプリケーションを作成します。
$ ember new dice-roller installing app create .editorconfig create .ember-cli create .eslintrc.js create .travis.yml create .watchmanconfig create README.md create app/app.js create app/components/.gitkeep create app/controllers/.gitkeep create app/helpers/.gitkeep create app/index.html create app/models/.gitkeep create app/resolver.js create app/router.js create app/routes/.gitkeep create app/styles/app.css create app/templates/application.hbs create app/templates/components/.gitkeep create config/environment.js create config/targets.js create ember-cli-build.js create .gitignore create package.json create public/crossdomain.xml create public/robots.txt create testem.js create tests/.eslintrc.js create tests/helpers/destroy-app.js create tests/helpers/module-for-acceptance.js create tests/helpers/resolver.js create tests/helpers/start-app.js create tests/index.html create tests/integration/.gitkeep create tests/test-helper.js create tests/unit/.gitkeep create vendor/.gitkeep NPM: Installed dependencies Successfully initialized git. $
上記のコマンドを実行すると、実行可能なアプリケーションレイアウトが作成されます。 彼女はGitバージョン管理も設定しました。 Gitとの統合をオフにすることができ、さらに、
npm
パッケージマネージャーの代わりに
yarn
を使用できることに注意してください。 詳細については、Emberのドキュメントをご覧ください。
では、得られたものを見てみましょう。 すでに述べたように、開発用のEmberアプリケーションの起動は、
ember-cli
を使用して実行されます。 これは次のように行われます。
$ cd dice-roller $ ember serve Livereload server on http://localhost:49153 'instrument' is imported from external module 'ember-data/-debug' but never used Warning: ignoring input sourcemap for vendor/ember/ember.debug.js because ENOENT: no such file or directory, open '/Users/coxg/source/me/writing/repos/dice-roller/tmp/source_map_concat-input_base_path-2fXNPqjl.tmp/vendor/ember/ember.debug.map' Warning: ignoring input sourcemap for vendor/ember/ember-testing.js because ENOENT: no such file or directory, open '/Users/coxg/source/me/writing/repos/dice-roller/tmp/source_map_concat-input_base_path-Xwpjztar.tmp/vendor/ember/ember-testing.map' Build successful (5835ms) – Serving on http://localhost:4200/ Slowest Nodes (totalTime => 5% ) | Total (avg) ----------------------------------------------+--------------------- Babel (16) | 4625ms (289 ms) Rollup (1) | 445ms
これですべての準備が整いました。アプリケーションはhttp:// localhost:4200で利用でき、次のようになります。
アプリケーション自体に加えて、 LiveReloadサービスが起動され 、プロジェクトファイルへの変更を監視し、ブラウザーのページを自動的にリロードします。 これは、サイトの変更中に、変更の結果が非常にすばやく表示されることを意味します。
最初のページでは、次に何ができるか、これらのメッセージを聞いて、メインページを変更し、何が起こるかを確認します。 これを行うには、ファイル
app/templates/application.hbs
を次のフォームに
app/templates/application.hbs
します。
This is my new application. {{outlet}}
{{outlet}}
タグは、Emberでのルーティングプロセスの仕組みの一部であることに注意してください。 これについては、以下で詳しく説明します。
ファイルを変更した後、すぐにコンソールに表示される
ember-cli
確認する必要があります。 次のようになります。
file changed templates/application.hbs Build successful (67ms) – Serving on http://localhost:4200/ Slowest Nodes (totalTime => 5% ) | Total (avg) ----------------------------------------------+--------------------- SourceMapConcat: Concat: App (1) | 9ms SourceMapConcat: Concat: Vendor /asset... (1) | 8ms SimpleConcatConcat: Concat: Vendor Sty... (1) | 4ms Funnel (7) | 4ms (0 ms)
ここで、システムがテンプレートの変更を検出し、プロジェクトを再構築して再起動したことが通知されます。 これは完全に自動化されたプロセスであり、参加していません。
次に、ブラウザを見てみましょう。
LiveReload
インストールして実行している場合、変更を確認するためにページを手動でリロードする必要はありません。 それ以外の場合、アプリケーションページはそれ自体でリロードする必要があります。
これは、ページの外観が驚くべきものであると言うことではありませんが、それを使って行ったことは、私たちの側で最小限の努力を必要としました。
さらに、完全にテストされたテストサブシステムがあります。 それを実行するには、適切なEmberツールを使用する必要があります。
$ ember test ⠸ Building'instrument' is imported from external module 'ember-data/-debug' but never used ⠴ BuildingWarning: ignoring input sourcemap for vendor/ember/ember.debug.js because ENOENT: no such file or directory, open '/Users/coxg/source/me/writing/repos/dice-roller/tmp/source_map_concat-input_base_path-S8aQFGaz.tmp/vendor/ember/ember.debug.map' ⠇ BuildingWarning: ignoring input sourcemap for vendor/ember/ember-testing.js because ENOENT: no such file or directory, open '/Users/coxg/source/me/writing/repos/dice-roller/tmp/source_map_concat-input_base_path-wO8OLEE2.tmp/vendor/ember/ember-testing.map' cleaning up... Built project successfully. Stored in "/Users/coxg/source/me/writing/repos/dice-roller/tmp/class-tests_dist-PUnMT5zL.tmp". ok 1 PhantomJS 2.1 - ESLint | app: app.js ok 2 PhantomJS 2.1 - ESLint | app: resolver.js ok 3 PhantomJS 2.1 - ESLint | app: router.js ok 4 PhantomJS 2.1 - ESLint | tests: helpers/destroy-app.js ok 5 PhantomJS 2.1 - ESLint | tests: helpers/module-for-acceptance.js ok 6 PhantomJS 2.1 - ESLint | tests: helpers/resolver.js ok 7 PhantomJS 2.1 - ESLint | tests: helpers/start-app.js ok 8 PhantomJS 2.1 - ESLint | tests: test-helper.js 1..8 # tests 8 # pass 8 # skip 0 # fail 0 # ok
出力の大部分はPhantom.jsからのものであることに注意してください。 これは、統合テストが完全にサポートされているためです。デフォルトでは、グラフィカルインターフェイスなしでPhantomJSブラウザーで実行されます。 必要に応じて、他のブラウザーでテストを実行する機能もあります。 継続的インテグレーションシステム(CI、継続的インテグレーション)をセットアップする場合、この機会を利用して、サポートされているすべてのブラウザーでアプリケーションが正しく動作することを確認する必要があります。
Ember.jsアプリケーションの構造
アプリケーションの作業を開始する前に、その基になるフォルダーとファイルの構造を理解します。 上記の
ember new
コマンドは、アプリケーションに必要なフォルダーとファイルを作成しますが、その中にはかなりの数があります。 Emberを使用して効果的な作業を編成し、あらゆる規模のプロジェクトを作成するには、すべてがどのように機能するかを理解することが重要です。
アプリケーション構造の最高レベルでは、次のファイルとフォルダーに注意を払うことができます。
- README.md-アプリケーションの説明が記載された標準のreadmeファイル。
- package.jsonは、アプリケーションを記述する
npm
構成ファイルです。 主に、依存関係が正しくインストールされるようにするために使用されます。
- ember-cli-build.js-アプリケーションのビルドを担当する
ember-cli
構成ファイル。
- testem.js-これは、テストサブシステムの構成ファイルです。 特に、さまざまな環境でアプリケーションのテストを整理するために使用するブラウザーを指定できます。
- app /-アプリケーションロジックはここに保存されます。 このフォルダでは多くの興味深いことが行われています。以下で説明します。
- config /-アプリケーション設定です。
- public /-ここには、アプリケーションに含める必要のある静的リソースが格納されています。 たとえば、これらは画像とフォントです。
- ベンダー/-これには、ビルドシステムが制御しないフロントエンドの依存関係が含まれます。
- テスト/-ここにテストがあります。
ページ構造
続行する前に、ページの構造を見てみましょう。 この場合、 Materialize CSSフレームワークを使用します。これにより、ページの見栄えが良くなり、エンドユーザーにとってより便利に作業できるようになります。
アプリケーションに追加のツールのサポートを追加するには、次のいずれかの方法を使用できます。
- 何らかのCDNなどの外部サービスを使用して、必要なものを直接接続します。
-
npm
やBower
などのパッケージマネージャーを使用して、必要なパッケージをインストールします。
- 必要なリソースをアプリケーションに直接含めます。
- Emberに適したアドオンを使用してください。
残念ながら、マテリアライズのアドオンはEmber.jsの最新バージョンではまだ動作しないため、CDNリソースへのリンクを使用して、アプリケーションのメインページでこのフレームワークを接続します。 これを行うには、
app/index.html
ファイルを編集する必要があります。このファイルには、アプリケーションが表示されるメインページの構造が記述されています。 CDNリンクをjQuery、Googleアイコン付きフォント、およびMaterializeに追加します。
<!-- Inside the Head section --> <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.1/css/materialize.min.css"> <!-- Inside the Body section --> <script type="text/javascript" src="https://code.jquery.com/jquery-3.2.1.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.1/js/materialize.min.js"></script>
メインページを変更して、アプリケーションの基本テンプレートを表示できるようになりました。 これは、ファイル
app/templates/application.hbs
を次の形式に変換することにより行われます。
<nav> <div class="nav-wrapper"> <a href="#" class="brand-logo"> <i class="material-icons">filter_6</i> Dice Roller </a> <ul id="nav-mobile" class="right hide-on-med-and-down"> </ul> </div> </nav> <div class="container"> {{outlet}} </div>
このコードのおかげで、マテリアライズツールによって準備されたナビゲーションバーがページの上部に表示されます。 上記の
{{outlet}}
タグが配置されているページにもコンテナーがあります。
これで、ブラウザを見ると、ページが下の図に示すようになっていることがわかります。
{{outlet}}
タグについて説明します。 Emberの作業はルートベースです。 各ルートは、他のルートの子孫と見なされます。 ルートルートは自動的に処理され、テンプレート
app/templates/application.hbs
表示され
app/templates/application.hbs
。
{{outlet}}
タグは、Emberが現在の階層の次のルートに対応するコンテンツを表示する場所を設定します。その結果、第1レベルのルートは
application.hbs
このタグに表示され、第2レベルのルートは第1レベルテンプレートの同じタグに表示され、さらに。
新しいルートを作成する
Ember.js上のアプリケーションの各ページへのアクセスは、ルート(ルート)で整理されています。 ブラウザが開くURLと、アプリケーションが表示するルートに関連する資料との間には直接的な対応があります。
この概念を理解する最も簡単な方法は、例を使用することです。 ユーザーがサイコロを「転がす」ことを許可する新しいルートをアプリケーションに追加します。 このステップも、
ember-cli
を使用して実行されます。
$ ember generate route roll installing route create app/routes/roll.js create app/templates/roll.hbs updating router add route roll installing route-test create tests/unit/routes/roll-test.js
このコマンドを呼び出して作成されたものは次のとおりです。
- ルートのハンドラーは
app/routes/roll.js
- ルートのテンプレートは
app/templates/roll.hbs
です。
- ルートの
tests/unit/routes/roll-test.js
- ルーター設定を更新して、この新しいルートに関する情報を提供します
app/router.js
仕組みを見てみましょう。 これまでのところ、サイコロとボタンを説明する要素を含む非常にシンプルなページがあり、少し後に「投げる」ことができます。 このページを作成するには、テンプレートファイル
app/templates/roll.hbs
次のコードを挿入します。
<div class="row"> <form class="col s12"> <div class="row"> <div class="input-field col s12"> <input placeholder="Name" id="roll_name" type="text" class="validate"> <label for="roll_name">Name of Roll</label> </div> </div> <div class="row"> <div class="input-field col s6"> <input placeholder="Number of dice" id="number_of_dice" type="number" class="validate" value="1"> <label for="number_of_dice">Number of Dice</label> </div> <div class="input-field col s6"> <input placeholder="Number of sides" id="number_of_sides" type="number" class="validate" value="6"> <label for="number_of_sides">Number of Sides</label> </div> </div> <div class="row"> <button class="btn waves-effect waves-light" type="submit" name="action"> Roll Dice <i class="material-icons right">send</i> </button> </div> </form> </div> {{outlet}}
その後、 http:// localhost:4200 / rollページにアクセスして、何が起こるかを確認します。
ここで、アプリケーションインターフェイスを介してこのページへの移行を整理する方法が必要です。 Emberは、
link-to
タグを使用してこのタスクを簡素化します。 特に、ユーザーに送信するルートの名前を受け入れ、ユーザーがこのルートをたどることができる要素を表示します。
app/templates/application.hbs
ファイルに次を含めます。
<ul id="nav-mobile" class="right hide-on-med-and-down"> {{#link-to 'roll' tagName="li"}} <a href="roll">Roll Dice</a> {{/link-to}} </ul>
これにより、ページ上部のナビゲーションバーが次の図に示す形式になります。
新しいリンク
Roll Dice
がパネルの右側に表示され、クリックすると、ユーザーは
/roll
ルートをたどります。 それが私たちが達成しようとしたことです。
モジュラーコンポーネント開発
アプリケーションで作業しようとすると、試してみてください、問題に気づくでしょう。 ホームページは正常に開き、
/roll
リンクは機能しますが、フォームのフィールドラベルが正しく配置されていません。 これは、Materializeが要素を適切に配置するために特定のJSコードを呼び出す必要があるためですが、動的ルーティングの機能により、ページはリロードされません。 今それを修正します。
コンポーネントを操作しましょう。 コンポーネントは、ユーザーインターフェースのフラグメントであり、相互作用できる完全なライフサイクルを持っています。 さらに、コンポーネントを使用すると、必要に応じて、再利用に適したユーザーインターフェイス要素を作成できます。 これについては後で説明します。
次に、
Roll Dice
フォームである唯一のコンポーネントを作成します。 そのような状況ではいつものように、コンポーネントを作成するために、
ember-cli
を見てみましょう。
$ ember generate component roll-dice installing component create app/components/roll-dice.js create app/templates/components/roll-dice.hbs installing component-test create tests/integration/components/roll-dice-test.js
その結果、システムは以下を作成します。
- コンポーネントロジックを実装するコード
app/components/roll-dice.js
- >コンポーネントの外観を決定するテンプレート
app/templates/components/roll-dice.hbs
- コンポーネントの正しい動作をチェックするための
tests/integration/components/roll-dice-test.js
次に、すべてのマークアップをコンポーネントに移動します。 これは、アプリケーションの動作に直接影響を与えることはありませんが、将来、必要な方法で構成するのに役立ちます。
ファイル
app/templates/components/roll-dice.hbs
をこの状態にします:
<form class="col s12"> <div class="row"> <div class="input-field col s12"> <input placeholder="Name" id="roll_name" type="text" class="validate"> <label for="roll_name">Name of Roll</label> </div> </div> <div class="row"> <div class="input-field col s6"> <input placeholder="Number of dice" id="number_of_dice" type="number" class="validate" value="1"> <label for="number_of_dice">Number of Dice</label> </div> <div class="input-field col s6"> <input placeholder="Number of sides" id="number_of_sides" type="number" class="validate" value="6"> <label for="number_of_sides">Number of Sides</label> </div> </div> <div class="row"> <button class="btn waves-effect waves-light" type="submit" name="action"> Roll Dice <i class="material-icons right">send</i> </button> </div> </form>
app/templates/roll.hbs
次のコードを
app/templates/roll.hbs
:
<div class="row"> {{roll-dice}} </div> {{outlet}}
以前にルートテンプレートファイルに配置されていた同じマークアップが、コンポーネントテンプレートに分類されました。 ルートテンプレートファイルがはるかに単純になりました。
roll-dice
タグは、Emberにコンポーネントを配置する場所を伝えます。
ブラウザでアプリケーションがどのように見えるかを今見ても、違いは見られません。 ただし、コードデバイスは変更され、モジュール化されています。 間違ったページレイアウトを修正し、アプリケーションにいくつかの新機能を追加するために、作成したコンポーネントを使用します。
コンポーネントのライフサイクル
Emberのコンポーネントには特別なライフサイクルがあり、ライフサイクルのさまざまな段階でフックが呼び出されます。 マテリアライズが署名を正しく表示できるようにするには、コンポーネントが表示された後に呼び出される
didRender
フックを使用します。 さらに、これはコンポーネントの最初のショーとその後のディスプレイの両方で行われます。
これを行うには、ファイル
app/components/roll-dice.js
あるコンポーネントコードを編集します。
/* global Materialize:false */ import Ember from 'ember'; export default Ember.Component.extend({ didRender() { Materialize.updateTextFields(); } });
これで、ダイレクトリンクまたはナビゲーションパネルからのリンクを使用して
/roll
ルートにアクセスするたびに、このコードが実行され、マテリアライズはテキストフィールドの署名を正しく表示します。
データバインディング
コンポーネントを使用して、データをユーザーインターフェイスにロードし、そこからアンロードします。 これは非常に簡単ですが、興味深いことに、Emberマニュアルにはこれについて何もありません。 その結果、Emberのデータバインディング手順は実際よりも複雑に見えます。
処理するデータの各部分は、
Component
クラスのフィールドの形式で存在します。 これを知って、コンポーネントの入力フィールドを表示し、これらのフィールドをコンポーネント変数にバインドできる補助ツールを使用できます。 その結果、DOMを使用することを考えずに、データを直接操作できます。
この場合、3つのフィールドがあるため、コンポーネント定義ブロック内の
app/components/roll-dice.js
に3行のコードを追加する必要があります。
rollName: '', numberOfDice: 1, numberOfSides: 6,
次に、通常のHTMLマークアップの代わりに補助メカニズムを使用してこのデータを出力するようにテンプレートを構成します。 これを行うには、
<input>
を次のコードに置き換える必要があります。
<div class="row"> <div class="input-field col s12"> <!-- This replaces the <input> tag for "roll_name" --> {{input placeholder="Name" id="roll_name" class="validate" value=(mut rollName)}} <label for="roll_name">Name of Roll</label> </div> </div> <div class="row"> <div class="input-field col s6"> <!-- This replaces the <input> tag for "number_of_dice" --> {{input placeholder="Number of dice" id="number_of_dice" type="number" class="validate" value=(mut numberOfDice)}} <label for="number_of_dice">Number of Dice</label> </div> <div class="input-field col s6"> <!-- This replaces the <input> tag for "number_of_sides" --> {{input placeholder="Number of sides" id="number_of_sides" type="number" class="validate" value=(mut numberOfSides)}} <label for="number_of_sides">Number of Sides</label> </div> </div>
value
属性の構文は異常に見えることに注意してください。 同様の構成は、
value
だけでなく、任意のタグ属性に使用でき
value
。 それらを使用する3つの方法を次に示します。
- 引用符で囲まれた文字列として。 このアプローチでは、値はコードで表示される形式で表示されます。
- 引用符なしの文字列として。 この場合、値はコンポーネントの対応するデータから取得されますが、データを出力する要素はコンポーネントに影響しません。
- コンストラクションの形式
(mut <name>)
。 このため、対応する値はコンポーネントから取得され、ブラウザで値が変更されるとコンポーネントが変更(変更)されます。
, , , Ember.
. ,
Roll Dice
. Ember (Actions). — , . — , , , ,
actions
.
, , ( ).
OnSubmit
. , ,
Enter
.
, ,
app/components/roll-dice.hbs
:
actions: { triggerRoll() { alert(`Rolling ${this.numberOfDice}D${this.numberOfSides} as "${this.rollName}"`); return false; } }
false
. — , HTML-. .
, , . DOM — , — JS-.
. ,
onsubmit
. Ember .
app/templates/components/roll-dice.hbs
:
<form class="col s12" onsubmit={{action 'triggerRoll'}}>
, , , , .
, - «» . . «» . :
- , «».
- Roll Dice.
- , , .
- «» , .
- «».
, , Ember .
Ember (Store), (Models). — , (Model) , . , , .
. ( , ), . .
, , — . , , , , , . , DOM Ember , - .
, , , , . , , .
, , ,
app/routes/roll.js
, , , .
actions: { saveRoll: function(rollName, numberOfDice, numberOfSides) { alert(`Rolling ${numberOfDice}D${numberOfSides} as "${rollName}"`); } }
,
app/components/roll-dice.js
, .
sendAction
:
triggerRoll() { this.sendAction('roll', this.rollName, this.numberOfDice, this.numberOfSides); return false; }
, , , . —
app/templates/roll.hbs
. , :
{{roll-dice roll="saveRoll" }}
,
roll
saveRoll
. ,
roll
, , , «» . , , «» , , .
, , , , , , .
, , .
ember-cli
.
, , :
$ ember generate model roll installing model create app/models/roll.js installing model-test create tests/unit/models/roll-test.js
, .
app/models/roll.js
:
import DS from 'ember-data'; export default DS.Model.extend({ rollName: DS.attr('string'), numberOfDice: DS.attr('number'), numberOfSides: DS.attr('number'), result: DS.attr('number') });
DS.attr
. —
string
,
number
,
date
boolean
, , .
, «» . ,
app/routes/roll.js
:
saveRoll: function(rollName, numberOfDice, numberOfSides) { let result = 0; for (let i = 0; i < numberOfDice; ++i) { result += 1 + (parseInt(Math.random() * numberOfSides)); } const store = this.get('store'); // "roll" . const roll = store.createRecord('roll', { rollName, numberOfDice, numberOfSides, result }); // , . roll.save(); }
, ,
Roll Dice
. , , , , , — .
Ember.js, . Ember.js , , , ember-localstorage-adapter , . . , .
, - , . ,
index
— .
Ember ,
index
, . , , , . «» .
index
,
ember-cli
. .
app/routes/index.js
, :
import Ember from 'ember'; export default Ember.Route.extend({ model() { return this.get('store').findAll('roll'); } });
findAll
«» . ,
model
.
index
app/templates/index.hbs
:
<table> <thead> <tr> <th>Name</th> <th>Dice Rolled</th> <th>Result</th> </tr> </thead> <tbody> {{#each model as |roll|}} <tr> <td>{{roll.rollName}}</td> <td>{{roll.numberOfDice}}D{{roll.numberOfSides}}</td> <td>{{roll.result}}</td> </tr> {{/each}} </tbody> </table> {{outlet}}
, . «»:
まとめ
, , , , «» . , , , . .
Ember . , , React, Ember , - .
ember-cli
— , . , , Ember , .
Ember . , Ember . , , « », Ember . Ember, , , ( ) , , .
Ember — , -. , , , , , .
親愛なる読者! Ember.js?