MeteorJSとReactを勉強してみる その4: フォームからタスクを追加してみよう【公式翻訳】
MeteorJSを勉強してみるシリーズ第4弾です。
やっとアプリらしい機能を追加するところまで来ましたね。 やっぱり、ユーザーの行動と表示するコンテンツが何かしらの形で連動するものを作ってこそ「アプリ」っていう感じがしますからね。
このステップでは、ユーザーがリストにタスクを追加するための記入欄を追加していきます。 まず、Appコンポーネントにフォームを追加しましょう。
imports/ui/App.js
before
import React, { Component } from 'react'; import { withTracker } from 'meteor/react-meteor-data'; import { Tasks } from '../api/tasks.js'; import Task from './Task.js'; // App Component - represnts the whole app class App extends Component { renderTasks() { return this.props.tasks.map((task) => ( <Task key={task._id} task={task} /> )); } render() { return ( <div className="container"> <header> <h1>Todo List</h1> </header> <ul> {this.renderTasks()} </ul> </div> ); } } export default withTracker(() => { return { tasks: Tasks.find({}).fetch() }; })(App);
after
import React, { Component } from 'react'; import { withTracker } from 'meteor/react-meteor-data'; import { Tasks } from '../api/tasks.js'; import Task from './Task.js'; // App Component - represnts the whole app class App extends Component { renderTasks() { return this.props.tasks.map((task) => ( <Task key={task._id} task={task} /> )); } render() { return ( <div className="container"> <header> <h1>Todo List</h1> <form className="new-task" onSubmit={this.handleSubmit.bind(this)}> <input type="text" ref="textInput" placeholder="Type to add new Tasks." /> </form> </header> <ul> {this.renderTasks()} </ul> </div> ); } } export default withTracker(() => { return { tasks: Tasks.find({}).fetch() }; })(App);
Tip: JSXのコードには、{/ ... /}と書くことで...部分をコメントにすることができます
上記のコードを見ると、formエレメントがonSubmit
という属性を持っていることが分かりますね。
この属性は、AppコンポーネントのhandleSubmit
というメソッドを呼び出しています。
Reactでは、これがブラウザ上で起こったイベント(この場合は「ユーザーが新しいタスクを送信した」というイベント)を検知する仕組みになっています。
また、inputエレメントはref属性を持っていますね。 これにより、後々このエレメントにアクセスしやすくなります。
では次に、handleSubmit
メソッドをAppコンポーネントに追加しましょう。
imports/ui/App.js
before
import React, { Component } from 'react'; import { withTracker } from 'meteor/react-meteor-data'; import { Tasks } from '../api/tasks.js'; import Task from './Task.js'; // App Component - represnts the whole app class App extends Component { renderTasks() { return this.props.tasks.map((task) => ( <Task key={task._id} task={task} /> )); } render() { return ( <div className="container"> <header> <h1>Todo List</h1> <form className="new-task" onSubmit={this.handleSubmit.bind(this)}> <input type="text" ref="textInput" placeholder="Type to add new Tasks." /> </form> </header> <ul> {this.renderTasks()} </ul> </div> ); } } export default withTracker(() => { return { tasks: Tasks.find({}).fetch() }; })(App);
after
import React, { Component } from 'react'; import ReactDOM from 'react-dom'; import { withTracker } from 'meteor/react-meteor-data'; import { Tasks } from '../api/tasks.js'; import Task from './Task.js'; // App Component - represnts the whole app class App extends Component { handleSubmit(event) { event.preventDefault(); // find the text field via the React ref const text = ReactDOM.findDOMNode(this.refs.textInput).value.trim(); Tasks.insert({ text, createdAt: new Date() }); // clear form ReactDOM.findDOMNode(this.refs.textInput).value = ''; } renderTasks() { return this.props.tasks.map((task) => ( <Task key={task._id} task={task} /> )); } render() { return ( <div className="container"> <header> <h1>Todo List</h1> <form className="new-task" onSubmit={this.handleSubmit.bind(this)}> <input type="text" ref="textInput" placeholder="Type to add new Tasks." /> </form> </header> <ul> {this.renderTasks()} </ul> </div> ); } } export default withTracker(() => { return { tasks: Tasks.find({}).fetch() }; })(App);
これでアプリに入力欄が追加されました。 タスクを追加するには、ただ入力欄に何か入力してエンターキーを押すだけです。
既に開いているブラウザとは別のウィンドウを開き、そこでアプリにアクセスすれば、全てのクライアントでリストが同期されていることが分かるでしょう。
Reactでイベントを検知する
上記で分かる通り、Reactではコンポーネント上でメソッドを呼ぶことで直接DOMイベントを操作することができます。
イベントハンドラの中では、エレメントにref属性を与えることによってReactDOM.findDOMNode
を使ってコンポーネントからエレメントを呼び出すことができます。
Reactがサポートするその他のイベントや、イベントのシステムがどのように動いているのかについてはReact docsを参照してください。
コレクションにデータを入れる
イベントハンドラの中で、Tasks.insert()
を使ってTasksコレクションにタスクを追加しています。
コレクションにおいてはスキーマ(データベースの構造定義書)を定義する必要がないので、上記でも追加しているcreatedAt(そのデータが追加された日時)のように、追加するデータに対してどんな値でも付与することができます。
クライアント側からどんなデータでも追加できるというのはセキュリティ的に甘い状態なのですが、一旦良しとしましょう。 ステップ10では、どのようにアプリをセキュアにし、どのようにデータベースにデータが追加される方法を制限するかを学びます。
タスクをソートする
現状では、追加した新しいタスクは全てリストの下に並んでいます。 これはタスクリストとしては機能的ではないですね。新しいタスクは上に並んでいてほしいものです。
これは先程追加したcreatedAtにより解決できます。 createdAtはタスクを追加した時間なので、それを昇順に並べれば良いわけですね。
これを実装するには、Appコンポーネントを包んでいるデータコンテナ内のfind
メソッドに並び替えのオプションを追加してやればOKです。
imports/ui/App.js before
import React, { Component } from 'react'; import ReactDOM from 'react-dom'; import { withTracker } from 'meteor/react-meteor-data'; import { Tasks } from '../api/tasks.js'; import Task from './Task.js'; // App Component - represnts the whole app class App extends Component { handleSubmit(event) { event.preventDefault(); // find the text field via the React ref const text = ReactDOM.findDOMNode(this.refs.textInput).value.trim(); Tasks.insert({ text, createdAt: new Date() }); // clear form ReactDOM.findDOMNode(this.refs.textInput).value = ''; } renderTasks() { return this.props.tasks.map((task) => ( <Task key={task._id} task={task} /> )); } render() { return ( <div className="container"> <header> <h1>Todo List</h1> <form className="new-task" onSubmit={this.handleSubmit.bind(this)}> <input type="text" ref="textInput" placeholder="Type to add new Tasks." /> </form> </header> <ul> {this.renderTasks()} </ul> </div> ); } } export default withTracker(() => { return { tasks: Tasks.find({}).fetch() }; })(App);
after
import React, { Component } from 'react'; import ReactDOM from 'react-dom'; import { withTracker } from 'meteor/react-meteor-data'; import { Tasks } from '../api/tasks.js'; import Task from './Task.js'; // App Component - represnts the whole app class App extends Component { handleSubmit(event) { event.preventDefault(); // find the text field via the React ref const text = ReactDOM.findDOMNode(this.refs.textInput).value.trim(); Tasks.insert({ text, createdAt: new Date() }); // clear form ReactDOM.findDOMNode(this.refs.textInput).value = ''; } renderTasks() { return this.props.tasks.map((task) => ( <Task key={task._id} task={task} /> )); } render() { return ( <div className="container"> <header> <h1>Todo List</h1> <form className="new-task" onSubmit={this.handleSubmit.bind(this)}> <input type="text" ref="textInput" placeholder="Type to add new Tasks." /> </form> </header> <ul> {this.renderTasks()} </ul> </div> ); } } export default withTracker(() => { return { tasks: Tasks.find({}, { sort: { createdAt: -1 } }).fetch() }; })(App);
再びブラウザを確認し、うまく動いていることを確認しましょう。 新しくタスクを追加すると、リストの一番上に表示されることが確認出来るはずです。
第四弾はここまでです。 いい感じになってきましたね。
完全に余談ですが、個人的にはプログラムの勉強はしっかり理解しながら書くことが大切だと思っています。
もちろん知識ゼロから上記のコードを理解して書くのはかなり難しいです。 ただ、だからといって単純にコードをコピペ・写経するのではいつまでたっても自分でコードを書けるようになりません。
プログラムは簡単だ、という人も巷にはいますが、それは嘘です。 専門職として成り立っているくらいなので、それなりに難しいです。
形から入ることも時には大切ですが、中身の伴わない外面だけではすぐに自分にごまかしが効かなくなります。
本当にプログラムを書けるようになりたい方は、しっかり理解しながら書く、ということを念頭に置いてくださいね!
第五弾も近いうちに。