Skip to content
Mithril.js 2
Main Navigation ガイドAPI

日本語

English
简体中文
繁體中文
Español
Français
Русский
Português – Brasil
Deutsch
한국어
Italiano
Polski
Türkçe
čeština
magyar

日本語

English
简体中文
繁體中文
Español
Français
Русский
Português – Brasil
Deutsch
한국어
Italiano
Polski
Türkçe
čeština
magyar

外観

Sidebar Navigation

はじめに

インストール方法

シンプルなアプリケーション

リソース

JSX

レガシーブラウザでの ES6+ の利用

アニメーション

テスト

例

サードパーティとの統合

パスの取り扱い

主要な概念

Virtual DOM ノード

コンポーネント

ライフサイクルメソッド

Key(キー)

自動再描画システム

その他

フレームワークの比較

v1.x からの移行

v0.2.x からの移行

API

このページの内容

シンプルなアプリケーション ​

Mithril を使用する際に必要となる主要な機能をどのように使うかを示す、シンプルなアプリケーションを開発してみましょう。

最終結果のインタラクティブな例は、こちら でご覧いただけます。

まず、アプリケーションのエントリポイントを作成しましょう。index.html ファイルを作成します。

html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>My Application</title>
  </head>
  <body>
    <script src="bin/app.js"></script>
  </body>
</html>

<!doctype html> は、HTML 5 ドキュメントであることを示します。最初の charset meta タグはドキュメントのエンコーディングを、viewport meta タグはモバイルブラウザでのページの拡大縮小方法を指定します。title タグには、このアプリケーションのブラウザタブに表示されるテキストが含まれており、script タグはアプリケーションを制御する JavaScript ファイルへのパスを示しています。

アプリケーション全体を 1 つの JavaScript ファイルで作成することも可能ですが、後でコードベースをナビゲートするのが難しくなる可能性があります。代わりに、コードを_モジュール_に分割し、それらのモジュールを_バンドル_して bin/app.js を作成しましょう。

バンドラツールを設定する方法はたくさんありますが、そのほとんどは npm 経由で配布されています。実際、Mithril を含む最新の JavaScript ライブラリやツールのほとんどがそのように配布されています。npm をダウンロードするには、Node.js をインストールしてください。npm は Node.js と一緒に自動的にインストールされます。Node.js と npm をインストールしたら、コマンドラインを開き、次のコマンドを実行します。

bash
npm init -y

npm が正しくインストールされている場合、package.json ファイルが作成されます。このファイルには、スケルトンプロジェクトのメタ記述が含まれています。このファイルでプロジェクトと作成者の情報を編集してください。


Mithril.js をインストールするには、インストール ページの指示に従ってください。Mithril.js がインストールされたプロジェクトのスケルトンができたら、アプリケーションを作成する準備が完了です。

まず、状態を保存するモジュールを作成します。src/models/User.js というファイルを作成しましょう。

javascript
// src/models/User.js
var User = {
  list: [],
};

module.exports = User;

次に、サーバーからデータをロードするコードを追加します。サーバーと通信するには、Mithril.js の XHR ユーティリティである m.request を使用できます。まず、Mithril.js をモジュールに含めましょう。

javascript
// src/models/User.js
var m = require('mithril');

var User = {
  list: [],
};

module.exports = User;

次に、XHR 呼び出しをトリガーする関数を作成します。loadList と名付けましょう。

javascript
// src/models/User.js
var m = require('mithril');

var User = {
  list: [],
  loadList: function () {
    // TODO: make XHR call
  },
};

module.exports = User;

次に、m.request 呼び出しを追加して XHR リクエストを行います。このチュートリアルでは、REM (DEAD LINK, FIXME: https //rem-rest-api.herokuapp.com/) API (迅速なプロトタイピング用に設計されたモック REST API) に対して XHR 呼び出しを行うことにします。この API は、GET https://mithril-rem.fly.dev/api/users エンドポイントからユーザーのリストを返します。m.request を使用して XHR リクエストを行い、そのエンドポイントからのレスポンスでデータを設定しましょう。

注: REM エンドポイントを機能させるには、サードパーティ Cookie を有効にする必要がある場合があります。

javascript
// src/models/User.js
var m = require('mithril');

var User = {
  list: [],
  loadList: function () {
    return m
      .request({
        method: 'GET',
        url: 'https://mithril-rem.fly.dev/api/users',
        withCredentials: true,
      })
      .then(function (result) {
        User.list = result.data;
      });
  },
};

module.exports = User;

method オプションは、HTTP メソッド を指定します。サーバーに副作用を与えることなくデータを取得するには、GET メソッドを使用する必要があります。url は API エンドポイントのアドレスです。withCredentials: true は、Cookie を使用することを示します (REM API の要件)。

m.request 呼び出しは、エンドポイントからのデータで解決される Promise を返します。デフォルトでは、Mithril.js は HTTP レスポンスのボディが JSON 形式であるとみなし、自動的に JavaScript オブジェクトまたは配列にパースします。.then コールバックは、XHR リクエストの完了後に実行されます。この例では、コールバックで result.data 配列を User.list に代入しています。

loadList に return ステートメントがあることにも注意してください。これは Promise を扱う際の一般的な良い習慣であり、XHR リクエストの完了後に実行するコールバックをさらに登録することを可能にします。

このシンプルなモデルは、User.list (ユーザーオブジェクトの配列) と User.loadList (サーバーデータで User.list を設定するメソッド) の 2 つのメンバーを公開します。


次に、User モデルモジュールからデータを表示するためのビューモジュールを作成しましょう。

src/views/UserList.js というファイルを作成します。まず、Mithril.js とモデルをインポートしましょう。両方とも必要になります。

javascript
// src/views/UserList.js
var m = require('mithril');
var User = require('../models/User');

次に、Mithril.js コンポーネントを作成しましょう。コンポーネントは、view メソッドを持つオブジェクトです。

javascript
// src/views/UserList.js
var m = require('mithril');
var User = require('../models/User');

module.exports = {
  view: function () {
    // TODO add code here
  },
};

デフォルトでは、Mithril.js ビューは hyperscript を使用して記述されます。Hyperscript は、複雑なタグに対して HTML よりも自然にインデントできる簡潔な構文を提供します。また、JavaScript で記述されているため、JavaScript のツール群を最大限に活用できます。例えば:

  • Babel を使用して、ES6+ を IE 用に ES5 にトランスパイルしたり、JSX (インライン HTML のような構文拡張) を適切な hyperscript 呼び出しにトランスパイルしたりできます。
  • ESLint を使用して、特別なプラグインなしで簡単に linting できます。
  • Terser または UglifyJS (ES5 のみ) を使用して、コードを簡単に minify できます。
  • Istanbul を使用して、コードカバレッジを取得できます。
  • TypeScript を使用して、コードを簡単に分析できます。(コミュニティがサポートする型定義 が利用可能であるため、独自にロールする必要はありません。)

hyperscript から始めて、アイテムのリストを作成してみましょう。Hyperscript は Mithril.js で推奨される記述方法ですが、JSX もよく似ています。

javascript
// src/views/UserList.js
var m = require('mithril');
var User = require('../models/User');

module.exports = {
  view: function () {
    return m('.user-list');
  },
};

".user-list" 文字列は CSS セレクターであり、予想どおり、.user-list はクラスを表します。タグが指定されていない場合、div がデフォルトです。したがって、このビューは <div class="user-list"></div> と同じ意味になります。

次に、以前に作成したモデル (User.list) からユーザーのリストを参照し、データを動的に表示してみましょう。

javascript
// src/views/UserList.js
var m = require('mithril');
var User = require('../models/User');

module.exports = {
  view: function () {
    return m(
      '.user-list',
      User.list.map(function (user) {
        return m('.user-list-item', user.firstName + ' ' + user.lastName);
      })
    );
  },
};

User.list は JavaScript 配列であり、hyperscript ビューは単なる JavaScript であるため、.map メソッドを使用して配列をループ処理できます。これにより、div のリストを表す vnode の配列が作成され、それぞれにユーザーの名前が含まれます。

もちろん、User.loadList 関数を呼び出していないため、User.list はまだ空の配列であり、このビューは空白のページを表示することになります。このコンポーネントのレンダリング時に User.loadList を呼び出すには、コンポーネントの ライフサイクルメソッド を利用します。

javascript
// src/views/UserList.js
var m = require('mithril');
var User = require('../models/User');

module.exports = {
  oninit: User.loadList,
  view: function () {
    return m(
      '.user-list',
      User.list.map(function (user) {
        return m('.user-list-item', user.firstName + ' ' + user.lastName);
      })
    );
  },
};

コンポーネントに oninit メソッドを追加し、User.loadList を参照させていることに注意してください。これは、コンポーネントが初期化されると、User.loadList が呼び出され、XHR リクエストがトリガーされることを意味します。サーバーが応答を返すと、User.list が設定されます。

また、oninit: User.loadList() (最後に括弧付き) を実行しなかったことにも注意してください。oninit: User.loadList() は関数を 1 回だけすぐに呼び出すのに対し、oninit: User.loadList はコンポーネントがレンダリングされるときにのみその関数を呼び出す点が異なります。これは重要な違いであり、JavaScript 初心者が陥りやすい点です。関数をすぐに呼び出すと、コンポーネントがレンダリングされる前に XHR リクエストが実行されてしまいます。また、コンポーネントが再作成された場合 (例えば、アプリケーション内を移動した場合) 、関数は期待どおりに再度呼び出されません。


以前に作成したエントリポイントファイルである src/index.js からビューをレンダリングしましょう。

javascript
// src/index.js
var m = require('mithril');

var UserList = require('./views/UserList');

m.mount(document.body, UserList);

m.mount 呼び出しは、指定されたコンポーネント (UserList) を DOM 要素 (document.body) にレンダリングし、以前にあった DOM をすべて消去します。ブラウザで HTML ファイルを開くと、人の名前のリストが表示されるはずです。


現在、スタイルを定義していないため、リストはかなりプレーンに見えます。そこで、スタイルをいくつか追加しましょう。まず、styles.css というファイルを作成し、index.html ファイルに含めます。

html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>My Application</title>
    <link href="styles.css" rel="stylesheet" />
  </head>
  <body>
    <script src="bin/app.js"></script>
  </body>
</html>

これで、UserList コンポーネントをスタイルできます。

css
.user-list {
  list-style: none;
  margin: 0 0 10px;
  padding: 0;
}

.user-list-item {
  background: #fafafa;
  border: 1px solid #ddd;
  color: #333;
  display: block;
  margin: 0 0 1px;
  padding: 8px 15px;
  text-decoration: none;
}

.user-list-item:hover {
  text-decoration: underline;
}

ブラウザウィンドウをリロードすると、スタイルが設定された要素が表示されるはずです。


アプリケーションにルーティングを追加しましょう。

ルーティングとは、画面と一意な URL を紐付け、「ページ」間の移動を可能にすることです。Mithril.js はシングルページアプリケーション向けに設計されているため、ここで言う「ページ」は、従来の HTML ファイルとは異なります。シングルページアプリケーションにおけるルーティングでは、同じ HTML ファイルを使用し続け、JavaScript によってアプリケーションの状態を変化させます。クライアントサイドルーティングには、ページ遷移時の画面のちらつきを抑えることができるという利点があります。また、Web サービス指向アーキテクチャ (例えば、事前にレンダリングされた HTML ではなく JSON 形式でデータを取得するアプリケーション) と組み合わせることで、サーバーから送信されるデータ量を削減できます。

m.mount 呼び出しを m.route 呼び出しに変更することで、ルーティングを追加できます。

javascript
// src/index.js
var m = require('mithril');

var UserList = require('./views/UserList');

m.route(document.body, '/list', {
  '/list': UserList,
});

m.route は、アプリケーションを document.body にレンダリングすることを指定します。"/list" はデフォルトルートであり、存在しないルートにアクセスした場合にリダイレクトされるルートです。{"/list": UserList} は、既存のルートと、それぞれのルートに対応するコンポーネントを定義しています。

ブラウザでページをリロードすると、ルーティングが機能していることを示すために、URL に #!/list が追加されるはずです。このルートは UserList をレンダリングするため、以前と同様に画面に人のリストが表示されるはずです。

この文字列は、m.route.prefix で設定できます。サーバー側の設定が必要になる場合もあるため、このチュートリアルではハッシュバンを使用することにします。


ユーザーを編集するためのルートをアプリケーションに追加しましょう。まず、views/UserForm.js というモジュールを作成します。

javascript
// src/views/UserForm.js

module.exports = {
  view: function () {
    // TODO implement view
  },
};

次に、src/index.js からこのモジュールを require します。

javascript
// src/index.js
var m = require('mithril');

var UserList = require('./views/UserList');
var UserForm = require('./views/UserForm');

m.route(document.body, '/list', {
  '/list': UserList,
});

最後に、それを参照するルートを作成できます。

javascript
// src/index.js
var m = require('mithril');

var UserList = require('./views/UserList');
var UserForm = require('./views/UserForm');

m.route(document.body, '/list', {
  '/list': UserList,
  '/edit/:id': UserForm,
});

新しいルートに :id が含まれていることに注意してください。これはルートパラメータで、ワイルドカードのように考えることができます。例えば、/edit/1 は id が "1" である UserForm に対応し、/edit/2 は id が "2" である UserForm に対応します。

これらのルートパラメータに対応するように、UserForm コンポーネントを実装しましょう。

javascript
// src/views/UserForm.js
var m = require('mithril');

module.exports = {
  view: function () {
    return m('form', [
      m('label.label', 'First name'),
      m('input.input[type=text][placeholder=First name]'),
      m('label.label', 'Last name'),
      m('input.input[placeholder=Last name]'),
      m('button.button[type=submit]', 'Save'),
    ]);
  },
};

styles.css にさらにスタイルを追加しましょう。

css
/* styles.css */
body,
.input,
.button {
  font: normal 16px Verdana;
  margin: 0;
}

.user-list {
  list-style: none;
  margin: 0 0 10px;
  padding: 0;
}

.user-list-item {
  background: #fafafa;
  border: 1px solid #ddd;
  color: #333;
  display: block;
  margin: 0 0 1px;
  padding: 8px 15px;
  text-decoration: none;
}

.user-list-item:hover {
  text-decoration: underline;
}

.label {
  display: block;
  margin: 0 0 5px;
}

.input {
  border: 1px solid #ddd;
  border-radius: 3px;
  box-sizing: border-box;
  display: block;
  margin: 0 0 10px;
  padding: 10px 15px;
  width: 100%;
}

.button {
  background: #eee;
  border: 1px solid #ddd;
  border-radius: 3px;
  color: #333;
  display: inline-block;
  margin: 0 0 10px;
  padding: 10px 15px;
  text-decoration: none;
}

.button:hover {
  background: #e8e8e8;
}

現在、このコンポーネントはユーザーイベントに応答するために何も行いません。src/models/User.js の User モデルにコードを追加しましょう。現在のコードは次のとおりです。

javascript
// src/models/User.js
var m = require('mithril');

var User = {
  list: [],
  loadList: function () {
    return m
      .request({
        method: 'GET',
        url: 'https://mithril-rem.fly.dev/api/users',
        withCredentials: true,
      })
      .then(function (result) {
        User.list = result.data;
      });
  },
};

module.exports = User;

単一のユーザーをロードできるようにするコードを追加しましょう。

javascript
// src/models/User.js
var m = require('mithril');

var User = {
  list: [],
  loadList: function () {
    return m
      .request({
        method: 'GET',
        url: 'https://mithril-rem.fly.dev/api/users',
        withCredentials: true,
      })
      .then(function (result) {
        User.list = result.data;
      });
  },

  current: {},
  load: function (id) {
    return m
      .request({
        method: 'GET',
        url: 'https://mithril-rem.fly.dev/api/users/' + id,
        withCredentials: true,
      })
      .then(function (result) {
        User.current = result;
      });
  },
};

module.exports = User;

User.current プロパティと、そのプロパティを設定する User.load(id) メソッドを追加したことに注意してください。この新しいメソッドを使用して UserForm ビューを設定できるようになりました。

javascript
// src/views/UserForm.js
var m = require('mithril');
var User = require('../models/User');

module.exports = {
  oninit: function (vnode) {
    User.load(vnode.attrs.id);
  },
  view: function () {
    return m('form', [
      m('label.label', 'First name'),
      m('input.input[type=text][placeholder=First name]', {
        value: User.current.firstName,
      }),
      m('label.label', 'Last name'),
      m('input.input[placeholder=Last name]', { value: User.current.lastName }),
      m('button.button[type=submit]', 'Save'),
    ]);
  },
};

UserList コンポーネントと同様に、oninit で User.load() を呼び出しています。"/edit/:id": UserForm ルートの :id がルートパラメータであったことを思い出してください。このルートパラメータは UserForm コンポーネントの vnode の属性となるため、/edit/1 にルーティングすると vnode.attrs.id の値は "1" になります。

次に、UserList ビューを修正して、そこから UserForm に遷移できるようにしましょう。

javascript
// src/views/UserList.js
var m = require('mithril');
var User = require('../models/User');

module.exports = {
  oninit: User.loadList,
  view: function () {
    return m(
      '.user-list',
      User.list.map(function (user) {
        return m(
          m.route.Link,
          {
            class: 'user-list-item',
            href: '/edit/' + user.id,
          },
          user.firstName + ' ' + user.lastName
        );
      })
    );
  },
};

ここでは、.user-list-item vnode を、同じクラスと子を持つ m.route.Link に置き換えました。必要なルートを参照する href を追加しています。これは、リンクをクリックすると、ハッシュバン #! 以降の URL が変更される (つまり、現在の HTML ページをアンロードせずにルートが変更される) ことを意味します。内部的には、<a> タグを使ってリンクを実装しており、すべて問題なく動作します。

ブラウザでページをリロードすると、人をクリックしてフォームに移動できるようになるはずです。また、ブラウザの [戻る] ボタンを使って、フォームから人のリストに戻ることも可能です。


フォーム自体は、"Save" を押しても自動的に保存されません。このフォームを実際に機能させてみましょう。

javascript
// src/views/UserForm.js
var m = require('mithril');
var User = require('../models/User');

module.exports = {
  oninit: function (vnode) {
    User.load(vnode.attrs.id);
  },
  view: function () {
    return m(
      'form',
      {
        onsubmit: function (e) {
          e.preventDefault();
          User.save();
        },
      },
      [
        m('label.label', 'First name'),
        m('input.input[type=text][placeholder=First name]', {
          oninput: function (e) {
            User.current.firstName = e.target.value;
          },
          value: User.current.firstName,
        }),
        m('label.label', 'Last name'),
        m('input.input[placeholder=Last name]', {
          oninput: function (e) {
            User.current.lastName = e.target.value;
          },
          value: User.current.lastName,
        }),
        m('button.button[type=submit]', 'Save'),
      ]
    );
  },
};

ユーザーが入力した内容を反映させるため、両方の入力フィールドに oninput イベントを追加し、User.current.firstName と User.current.lastName プロパティを更新するようにしました。

さらに、"Save" ボタンがクリックされたときに User.save メソッドが実行されるように設定しました。次に、このメソッドを実装します。

javascript
// src/models/User.js
var m = require('mithril');

var User = {
  list: [],
  loadList: function () {
    return m
      .request({
        method: 'GET',
        url: 'https://mithril-rem.fly.dev/api/users',
        withCredentials: true,
      })
      .then(function (result) {
        User.list = result.data;
      });
  },

  current: {},
  load: function (id) {
    return m
      .request({
        method: 'GET',
        url: 'https://mithril-rem.fly.dev/api/users/' + id,
        withCredentials: true,
      })
      .then(function (result) {
        User.current = result;
      });
  },

  save: function () {
    return m.request({
      method: 'PUT',
      url: 'https://mithril-rem.fly.dev/api/users/' + User.current.id,
      body: User.current,
      withCredentials: true,
    });
  },
};

module.exports = User;

一番下の save メソッドでは、PUT HTTP メソッドを使用して、サーバー上のデータを更新(存在しない場合は挿入)しています。

アプリケーションでユーザーの名前を編集し、変更を保存してみましょう。ユーザーリストに戻ると、変更が反映されているはずです。


現状では、ブラウザの戻るボタンでしかユーザーリストに戻れません。メニュー、あるいはより汎用的なグローバル UI 要素を配置できるレイアウトがあると便利です。

src/views/Layout.js ファイルを作成します。

javascript
// src/views/Layout.js
var m = require('mithril');

module.exports = {
  view: function (vnode) {
    return m('main.layout', [
      m('nav.menu', [m(m.route.Link, { href: '/list' }, 'Users')]),
      m('section', vnode.children),
    ]);
  },
};

このコンポーネントはシンプルで、ユーザーリストへのリンクを持つ <nav> 要素があります。/edit リンクと同様に、m.route.Link を使用してルーティング可能なリンクを作成しています。

また、vnode.children を子要素として持つ <section> 要素もあります。vnode は Layout コンポーネントのインスタンス(つまり、m(Layout) の呼び出しで返される vnode)への参照であり、vnode.children はその vnode のすべての子要素を指します。

次に、スタイルを更新します。

css
/* styles.css */
body,
.input,
.button {
  font: normal 16px Verdana;
  margin: 0;
}

.layout {
  margin: 10px auto;
  max-width: 1000px;
}

.menu {
  margin: 0 0 30px;
}

.user-list {
  list-style: none;
  margin: 0 0 10px;
  padding: 0;
}

.user-list-item {
  background: #fafafa;
  border: 1px solid #ddd;
  color: #333;
  display: block;
  margin: 0 0 1px;
  padding: 8px 15px;
  text-decoration: none;
}

.user-list-item:hover {
  text-decoration: underline;
}

.label {
  display: block;
  margin: 0 0 5px;
}

.input {
  border: 1px solid #ddd;
  border-radius: 3px;
  box-sizing: border-box;
  display: block;
  margin: 0 0 10px;
  padding: 10px 15px;
  width: 100%;
}

.button {
  background: #eee;
  border: 1px solid #ddd;
  border-radius: 3px;
  color: #333;
  display: inline-block;
  margin: 0 0 10px;
  padding: 10px 15px;
  text-decoration: none;
}

.button:hover {
  background: #e8e8e8;
}

src/index.js のルーターを修正して、レイアウトを適用します。

javascript
// src/index.js
var m = require('mithril');

var UserList = require('./views/UserList');
var UserForm = require('./views/UserForm');
var Layout = require('./views/Layout');

m.route(document.body, '/list', {
  '/list': {
    render: function () {
      return m(Layout, m(UserList));
    },
  },
  '/edit/:id': {
    render: function (vnode) {
      return m(Layout, m(UserForm, vnode.attrs));
    },
  },
});

各ルートを RouteResolver(基本的には render メソッドを持つオブジェクト)に置き換えました。render メソッドは、m() 呼び出しをネストすることで、通常のコンポーネントの view と同じように記述できます。

注目すべき点として、m() 呼び出しでは、セレクタ文字列の代わりにコンポーネントを使用できます。ここでは、/list ルートで m(Layout, m(UserList)) と記述しています。これは、Layout のインスタンスを表すルート vnode があり、その唯一の子として UserList vnode が存在することを意味します。

/edit/:id ルートでは、ルートパラメータを UserForm コンポーネントに渡す vnode 引数も利用できます。したがって、URL が /edit/1 の場合、vnode.attrs は {id: 1} となり、m(UserForm, vnode.attrs) は m(UserForm, {id: 1}) と同等です。JSX で記述すると <UserForm id={vnode.attrs.id} /> となります。

ブラウザでページをリフレッシュすると、アプリケーションのすべてのページにグローバルナビゲーションが表示されるようになります。


これでチュートリアルは完了です。

このチュートリアルでは、サーバーからユーザーをリスト表示し、個別に編集できるシンプルなアプリケーションを作成する手順を説明しました。追加の課題として、ユーザーの作成と削除機能を実装してみてください。

Mithril.js コードのより多くの例については、examples ページを参照してください。ご質問がある場合は、Mithril.js チャットルーム までお気軽にお越しください。

Pager
前のページインストール方法
次のページJSX

MITライセンス の下で公開されています。

Copyright (c) 2024 Mithril Contributors

https://mithril.js.org/simple-application.html

MITライセンス の下で公開されています。

Copyright (c) 2024 Mithril Contributors