Disqus、LiveFyre、Facebookなどのリソースによって提供されるリアルタイムコメントモジュールの単純化された類似物である、シンプルだが現実的なブログコメントモジュールを作成します。
私達は提供します:
- 表示してすべてのコメントを表示する
- コメントを入力および送信するためのフォーム
- 将来に依存して、実際のバックエンドを接続する
また実装されます:
- 楽観的なコメント:サーバーに保存される前にコメントがページに表示されるため、視覚的にモジュールを高速化できます
- ライブ更新:他のユーザーからのコメントがリアルタイムでページに表示されます
- マークダウン書式設定:ユーザーは、マークダウン書式設定を使用してテキストを書式設定できます
最終版
サーバー起動
マニュアルを開始する前に、サーバーを起動する必要があります。 これは、データの受信と保存に使用する単純なAPIです。 すでにいくつかのインタプリタ言語で作成しており、必要最小限の機能を備えています。 ソースコードに慣れるか、必要なものがすべて含まれているzipアーカイブをダウンロードできます。
はじめに
このガイドでは、できる限りシンプルにすべてを実装しようとします。 前述のアーカイブには、作業を継続するHTMLファイルがあります。 コードエディタでpublic / index.htmlファイルを開きます。 次のようになります。
<!-- index.html --> <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>React Tutorial</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.2/react.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.2/react-dom.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/0.3.5/marked.min.js"></script> </head> <body> <div id="content"></div> <script type="text/babel" src="scripts/example.js"></script> <!-- --> <script type="text/babel"> // script, scripts/example.js // . </script> </body> </html>
マニュアルのすべてのJavaScriptコードは、scriptタグに記述します。 ライブリロードがないため、変更を保存するたびにブラウザでプロジェクトページを更新する必要があります。 ブラウザでリンクhttp:// localhost:3000を開くと、結果を追跡できます(サーバーの起動後)。 コードを変更せずに初めてリンクを開くと、コメントモジュールの最終バージョンが表示されます。 開始するには、プロジェクトの最終バージョン「scripts / example.js」のコードをロードする最初のスクリプトタグを削除する必要があります 。
注:プロジェクトでjQueryを使用して、今後のAjaxリクエストのコードを簡素化しますが、これはReactライブラリではありません 。
最初のコンポーネント
Reactは、モジュラーで構成可能なフレームワークです。 このプロジェクトは、次の構造にあるいくつかのコンポーネントで構成されています。
- コメントボックス
- コメントリスト
- コメント
- コメントフォーム
- コメントリスト
CommentBoxコンポーネントを作成します 。これは、出力の通常の<div>
タグになります。
// tutorial1.js var CommentBox = React.createClass({ render: function() { return ( <div className="commentBox"> Hello, world! I am a CommentBox. </div> ); } }); ReactDOM.render( <CommentBox />, document.getElementById('content') );
Reactクラスの名前が大文字になっている場合、HTML要素の名前は小文字で始まることに注意してください。
JSX構文
最初に目を引くのは、提供されているJavaScriptコードのXMLに似た構文です。 出力で純粋なJavaScriptを生成する単純なプリコンパイラを使用します。
// tutorial1-raw.js var CommentBox = React.createClass({displayName: 'CommentBox', render: function() { return ( React.createElement('div', {className: "commentBox"}, "Hello, world! I am a CommentBox." ) ); } }); ReactDOM.render( React.createElement(CommentBox, null), document.getElementById('content') );
プリコンパイラの使用はオプションであり、純粋なJavaScriptで記述できますが、ガイドではJSX構文を使用します。 詳細については、 JSX構文の記事を参照してください 。
コードで何が起こるか
いくつかのメソッドを持つJavaScriptオブジェクトをReact.createClass()に渡して、新しいReactコンポーネントを作成します。 渡される最も重要なメソッドはrenderと呼ばれ、最終的にHTMLに変換されるReactコンポーネントのツリーを返します。
<div>
タグは実際のDOMノードではなく、これはReact <div>
コンポーネントの実装です。 Reactが処理方法を知っているマーカーまたはデータの断片を考慮することができます。 ReactはXSSの脆弱性に関して安全です。
HTMLコードを返す必要はありません。 自分(または他の誰か)が作成したコンポーネントツリーを返すことができます。 このアプローチにより、Reactは構成可能になります。これは、よく維持され、適切に設計されたフロントエンドアーキテクチャの重要な機能です。
ReactDOM.render()は、ルートコンポーネントのインスタンスを作成し、フレームワークを起動し、2番目の引数で渡されたDOM要素にマークアップを挿入します。
ReactDomオブジェクトには、DOMを操作するためのメソッドが含まれていますが、 Reactオブジェクトには、 React Nativeなどの他のライブラリで使用されるルートメソッドが含まれています。
ReactDOM.renderの呼び出しは、すべてのコンポーネントが宣言された後に行う必要があります。 これは重要です。
組み合わせたコンポーネント
通常の<div>
なるCommentListおよびCommentFormのスケルトンを作成します。 これらの2つのコンポーネントをファイルに追加し、前の例のCommentBoxとReactDOM.renderをそれぞれの場所に残します 。
// tutorial2.js var CommentList = React.createClass({ render: function() { return ( <div className="commentList"> Hello, world! I am a CommentList. </div> ); } }); var CommentForm = React.createClass({ render: function() { return ( <div className="commentForm"> Hello, world! I am a CommentForm. </div> ); } });
次に、 CommentBoxコンポーネントを変更して、新しいコンポーネント(「// new」とマークされた行)を使用します。
// tutorial3.js var CommentBox = React.createClass({ render: function() { return ( <div className="commentBox"> //new start <h1>Comments</h1> <CommentList /> <CommentForm /> //new end </div> ); } });
作成したHTMLタグとコンポーネントの組み合わせに注意してください。 HTMLコンポーネントは、私たちが発表したもののような標準のReactコンポーネントですが、1つだけ違いがあります。 JSXプリプロセッサは、 React.createElement(tagName)式のHTMLタグを自動的に書き換え、他のすべてをそのままにします。 これは、グローバル名前空間の目詰まりを防ぐために必要です。
詳細の使用
親コンポーネントによって送信されるデータに依存するコメントコンポーネントを作成します。 親から渡されたデータは、子コンポーネントのプロパティとして利用できます。 プロパティへのアクセスはthis.propsを介して行われます。 詳細を使用して、 CommentListからCommentに渡されたデータを読み取り、マークアップを表示できます。
// tutorial4.js var Comment = React.createClass({ render: function() { return ( <div className="comment"> <h2 className="commentAuthor"> {this.props.author} </h2> {this.props.children} </div> ); } });
JavaScript式をJSX内で中括弧で囲むことにより、テキストまたはReactコンポーネントをツリーに追加できます。 this.propsのキーとしてコンポーネントに渡された名前付き属性、およびthis.props.childrenなどのネストされた要素にアクセスします。
コンポーネントのプロパティ
Commentsの宣言されたコンポーネントができたので、著者の名前とコメントのテキストを渡します。 これにより、各コメントに同じコードを再利用できます。 次に、 CommentListコンポーネントにいくつかのコメントを追加します。
// tutorial5.js var CommentList = React.createClass({ render: function() { return ( <div className="commentList"> //new start <Comment author="Pete Hunt">This is one comment</Comment> <Comment author="Jordan Walke">This is *another* comment</Comment> //new end </div> ); } });
CommentListの親コンポーネントからCommentの子コンポーネントにデータを渡す方法に注目してください。 たとえば、最初のCommentに Pete Hunt (属性を介して)を渡し、 これは (XMLに似た子ノードを介して)1つの要素です。 前述のように、 Commentコンポーネントはthis.props.authorおよびthis.props.childrenを介してこれらのプロパティにアクセスします。
マークダウンマークアップを追加
マークダウンは、テキストをフォーマットする便利な方法です。 たとえば、アスタリスクでラップされたテキストには、出力に下線が引かれます。
このチュートリアルでは、マークダウンされたサードパーティライブラリを使用します。これは 、Markdownマークアップを純粋なHTMLに変換します。 このライブラリを既にHTMLファイルに含めているため、使用を開始できます。 Markdownマークアップに基づいてコメントテキストを変換して出力します。
// tutorial6.js var Comment = React.createClass({ render: function() { return ( <div className="comment"> <h2 className="commentAuthor"> {this.props.author} </h2> {marked(this.props.children.toString())} // new </div> ); } });
ここで行ったのは、 マークされたライブラリを呼び出すことだけです。 これで、 this.props.childrenをReactのようなテキストから、 マークされた理解できる通常の行に変換する必要があるため、具体的にtoString()関数を呼び出します。
しかし、問題があります! ブラウザで処理されたコンポーネントは、「 <p>
これは<em>
別の</em>
コメント</p>
」のようになります。 すべてのタグをHTMLテキストのマークアップに変換する必要があります。
したがって、ReactはXSS攻撃からユーザーを保護します。 これを回避する方法は次のとおりです。
// tutorial7.js //new start var Comment = React.createClass({ rawMarkup: function() { var rawMarkup = marked(this.props.children.toString(), {sanitize: true}); return { __html: rawMarkup }; }, //new end render: function() { return ( <div className="comment"> <h2 className="commentAuthor"> {this.props.author} </h2> <span dangerouslySetInnerHTML={this.rawMarkup()} /> //new </div> ); } });
これは純粋なHTMLでの作業を意図的に複雑にする特別なAPIですが、 markedについては例外を作成します。
注意!:このような例外を使用すると、 マークされたライブラリのセキュリティに完全に依存します。 これを行うには、2番目の引数をsenitize:trueに渡します。これには、HTMLタグのクリアが含まれます。
データモデルを接続する
ここまで、コードから直接コメントを挿入しました。 ここで、JSONオブジェクトをコメントリストに変換してみましょう。 次に、サーバーからそれらを取得しますが、ここではコードに次の行を追加します。
// tutorial8.js var data = [ {id: 1, author: "Pete Hunt", text: "This is one comment"}, {id: 2, author: "Jordan Walke", text: "This is *another* comment"} ];
次に、このオブジェクトをCommentListに渡して、モジュール性を観察する必要があります。 propsメソッドを使用して、 CommentBoxとRenderDOM.render()を変更して、 CommentListコンポーネントにデータを転送します。
// tutorial9.js var CommentBox = React.createClass({ render: function() { return ( <div className="commentBox"> <h1>Comments</h1> <CommentList data={this.props.data} /> //new <CommentForm /> </div> ); } }); ReactDOM.render( <CommentBox data={data} />, //new document.getElementById('content') );
CommentListコンポーネントでデータが利用可能になったので、コメントを動的に表示してみましょう。
// tutorial10.js var CommentList = React.createClass({ render: function() { //new start var commentNodes = this.props.data.map(function(comment) { return ( <Comment author={comment.author} key={comment.id}> {comment.text} </Comment> ); }); //new end return ( <div className="commentList"> {commentNodes} // new </div> ); } });
できた!
サーバーからコメントを受け取る
コードに含まれるコメントをサーバーからのデータに置き換えます。 これを行うには、以下に示すように、 データ属性をurlに置き換えます。
// tutorial11.js ReactDOM.render( <CommentBox url="/api/comments" />, // new document.getElementById('content') );
ご注意 この時点で、コードは機能しません。
反応状態
これまで、各コンポーネントはパラメータに基づいて一度描画され、 小道具は変更されません。つまり、親から送信され、所有者のままになります。 相互作用を整理するために、コンポーネントに可変プロパティを追加します。 this.stateはコンポーネントに対してプライベートであり、 this.setState()を呼び出して変更できます。 プロパティを更新すると、コンポーネントは再描画されます。
render()メソッドは、関数this.propsやthis.stateのように宣言的に記述されます。
Reactは、サーバーとユーザーインターフェイスでデータの一貫性を確保します。
サーバーがデータを送信するとき、インターフェイスのコメントを変更する必要があります。 CommentBoxコンポーネントに別個のパラメーターを使用してコメント配列を追加します。
// tutorial12.js var CommentBox = React.createClass({ //new start getInitialState: function() { return {data: []}; }, //new end render: function() { return ( <div className="commentBox"> <h1>Comments</h1> <CommentList data={this.state.data} /> // new <CommentForm /> </div> ); } });
getInitialState()は、コンポーネントのライフサイクル中に1回実行され、コンポーネントの初期状態を設定します。
更新ステータス
コンポーネントを作成した後、サーバーからJSONを取得し、コンポーネントのデータを更新してインターフェースに表示します。 非同期サーバーリクエストの場合、jQueryを使用します。 データは、最初に起動したサーバー(comments.jsonに保存)に既にあります。 サーバーからデータを受信すると、 this.state.dataには以下が含まれます。
[ {"id": "1", "author": "Pete Hunt", "text": "This is one comment"}, {"id": "2", "author": "Jordan Walke", "text": "This is *another* comment"} ]
// tutorial13.js var CommentBox = React.createClass({ getInitialState: function() { return {data: []}; }, //new start componentDidMount: function() { $.ajax({ url: this.props.url, dataType: 'json', cache: false, success: function(data) { this.setState({data: data}); }.bind(this), error: function(xhr, status, err) { console.error(this.props.url, status, err.toString()); }.bind(this) }); }, //new end render: function() { return ( <div className="commentBox"> <h1>Comments</h1> <CommentList data={this.state.data} /> <CommentForm /> </div> ); } });
componentDidMountメソッドは、コンポーネントの最初のレンダリング後にReactによって自動的に呼び出されます。 this.setState()メソッドは、動的に更新を行います。 古いコメント配列をサーバーからの新しい配列に置き換え、インターフェースは自動的に更新されます。 このため、リアルタイムで更新を追加するには、マイナーな編集が必要です。 簡単にするために、 ポーリングテクノロジー(Frequent Requests)を使用しますが、将来はWebSocketsまたはその他のテクノロジーを簡単に使用できます。
// tutorial14.js var CommentBox = React.createClass({ loadCommentsFromServer: function() { // new $.ajax({ url: this.props.url, dataType: 'json', cache: false, success: function(data) { this.setState({data: data}); }.bind(this), error: function(xhr, status, err) { console.error(this.props.url, status, err.toString()); }.bind(this) }); }, // new getInitialState: function() { return {data: []}; }, componentDidMount: function() { //new start this.loadCommentsFromServer(); setInterval(this.loadCommentsFromServer, this.props.pollInterval); //new end }, render: function() { return ( <div className="commentBox"> <h1>Comments</h1> <CommentList data={this.state.data} /> <CommentForm /> </div> ); } }); ReactDOM.render( <CommentBox url="/api/comments" pollInterval={2000} />, // new document.getElementById('content') );
ここでは、AJAXリクエストを別のメソッドに移動し、コンポーネントの最初の読み込み後と2秒ごとに呼び出しを開始します。 ここで、ブラウザでコメントページを開き、 comments.jsonファイル(サーバーのルートディレクトリ内)に変更を加えます。 2秒以内にページに変更が表示されます。
新しいコメントを追加
次に、コメントフォームを作成します。 CommentFormコンポーネントは、ユーザーにコメントの名前とテキストを要求し、さらにコメントを保存するためにサーバーにリクエストを送信する必要があります。
// tutorial15.js var CommentForm = React.createClass({ render: function() { return ( //new start <form className="commentForm"> <input type="text" placeholder="Your name" /> <input type="text" placeholder="Say something..." /> <input type="submit" value="Post" /> </form> //new end ); } });
制御されたコンポーネント
従来のDOMでは、 入力要素が描画されてから、ブラウザがその値を設定します。 その結果、DOM値はコンポーネント値とは異なります。 ビューの値がコンポーネントの値と異なる場合、それは悪いです。 Reactでは、コンポーネントは、その初期化時だけでなく、常にビューに対応する必要があります。
したがって、 this.stateを使用してユーザー入力を保存します。 作成者とテキストの 2つのプロパティを使用して初期状態の状態を宣言し、空の文字列の値を割り当てます。 <input>
要素で、 値の 状態を valueパラメーターに割り当て、 onChangeハンドラーをその上に配置します。 value属性値が設定されたこの<input>
要素は、制御コンポーネントと呼ばれます。 コントロールされたコンポーネントの詳細については、 フォームの記事をご覧ください 。
// tutorial16.js var CommentForm = React.createClass({ //new start getInitialState: function() { return {author: '', text: ''}; }, handleAuthorChange: function(e) { this.setState({author: e.target.value}); }, handleTextChange: function(e) { this.setState({text: e.target.value}); }, //new end render: function() { return ( <form className="commentForm"> //new start <input type="text" placeholder="Your name" value={this.state.author} onChange={this.handleAuthorChange} /> <input type="text" placeholder="Say something..." value={this.state.text} onChange={this.handleTextChange} /> //new end <input type="submit" value="Post" /> </form> ); } });
イベント
ReactイベントハンドラーはcamelCase命名規則を使用します。 2つの<input>
要素にonChangeハンドラーを掛けました 。 ユーザーが<input>
フィールドにデータを<input>
ので、イベントハンドラーはコールバックを行い、コンポーネントの値を変更します。 その後、コンポーネントの現在の値を反映するために、 入力値が更新されます。
フォーム送信
フォームをインタラクティブにしましょう。 ユーザーがフォームを送信した後、フォームをクリアし、サーバーにリクエストを送信し、コメントのリストを更新する必要があります。 最初に、フォームデータを取得してクリアします。
// tutorial17.js var CommentForm = React.createClass({ getInitialState: function() { return {author: '', text: ''}; }, handleAuthorChange: function(e) { this.setState({author: e.target.value}); }, handleTextChange: function(e) { this.setState({text: e.target.value}); }, //new start handleSubmit: function(e) { e.preventDefault(); var author = this.state.author.trim(); var text = this.state.text.trim(); if (!text || !author) { return; } // TODO: this.setState({author: '', text: ''}); }, //new end render: function() { return ( <form className="commentForm" onSubmit={this.handleSubmit}> // new <input type="text" placeholder="Your name" value={this.state.author} onChange={this.handleAuthorChange} /> <input type="text" placeholder="Say something..." value={this.state.text} onChange={this.handleTextChange} /> <input type="submit" value="Post" /> </form> ); } });
フォームにonSubmitハンドラーをハングアップします。これにより、フォームに正しいデータが入力されて送信されたときにクリアされます。
preventDefault()を呼び出して 、ブラウザがデフォルトでフォームを送信しないようにします。
パラメーターとしてのコールバック
ユーザーがコメントを送信したら、コメントシートを更新して新しいものを追加する必要があります。 CommentBoxはコメントのリストを管理するため、このすべてのロジックをCommentBoxに実装することは理にかなっています。
子コンポーネントから親に渡す必要があります。 これは、親renderメソッドを介して行い、新しいコールバック( handleCommentSubmit )を子に渡し 、子コンポーネントのonCommentSubmitイベントに関連付けます。 イベントが発生するたびに、コールバック関数が呼び出されます:
// tutorial18.js var CommentBox = React.createClass({ loadCommentsFromServer: function() { $.ajax({ url: this.props.url, dataType: 'json', cache: false, success: function(data) { this.setState({data: data}); }.bind(this), error: function(xhr, status, err) { console.error(this.props.url, status, err.toString()); }.bind(this) }); }, //new start handleCommentSubmit: function(comment) { // TODO: }, //new end getInitialState: function() { return {data: []}; }, componentDidMount: function() { this.loadCommentsFromServer(); setInterval(this.loadCommentsFromServer, this.props.pollInterval); }, render: function() { return ( <div className="commentBox"> <h1>Comments</h1> <CommentList data={this.state.data} /> <CommentForm onCommentSubmit={this.handleCommentSubmit} /> // new </div> ); } });
CommentBoxコンポーネントが onCommentSubmitパラメーターを介してCommentFormコンポーネントへのコールバック関数へのアクセスを許可したので 、ユーザーがフォームを送信すると、 CommentFormコンポーネントはコールバック関数を呼び出すことができます。
// tutorial19.js var CommentForm = React.createClass({ getInitialState: function() { return {author: '', text: ''}; }, handleAuthorChange: function(e) { this.setState({author: e.target.value}); }, handleTextChange: function(e) { this.setState({text: e.target.value}); }, handleSubmit: function(e) { e.preventDefault(); var author = this.state.author.trim(); var text = this.state.text.trim(); if (!text || !author) { return; } this.props.onCommentSubmit({author: author, text: text}); // new this.setState({author: '', text: ''}); }, render: function() { return ( <form className="commentForm" onSubmit={this.handleSubmit}> <input type="text" placeholder="Your name" value={this.state.author} onChange={this.handleAuthorChange} /> <input type="text" placeholder="Say something..." value={this.state.text} onChange={this.handleTextChange} /> <input type="submit" value="Post" /> </form> ); } });
コールバック関数が用意できたので、サーバーにデータを送信し、コメントシートを更新するだけです。
// tutorial20.js var CommentBox = React.createClass({ loadCommentsFromServer: function() { $.ajax({ url: this.props.url, dataType: 'json', cache: false, success: function(data) { this.setState({data: data}); }.bind(this), error: function(xhr, status, err) { console.error(this.props.url, status, err.toString()); }.bind(this) }); }, handleCommentSubmit: function(comment) { //new start $.ajax({ url: this.props.url, dataType: 'json', type: 'POST', data: comment, success: function(data) { this.setState({data: data}); }.bind(this), error: function(xhr, status, err) { console.error(this.props.url, status, err.toString()); }.bind(this) }); //new end }, getInitialState: function() { return {data: []}; }, componentDidMount: function() { this.loadCommentsFromServer(); setInterval(this.loadCommentsFromServer, this.props.pollInterval); }, render: function() { return ( <div className="commentBox"> <h1>Comments</h1> <CommentList data={this.state.data} /> <CommentForm onCommentSubmit={this.handleCommentSubmit} /> </div> ); } });
最適化:楽観的な更新
アプリケーションの準備は整っていますが、サーバーへのリクエストが完了するのを待っており、ページにコメントが表示されると視覚的に遅くなります。 サーバーへのリクエストが完了するのを待たずに、すぐにコメントをリストに追加できます。これはほぼ瞬時に行われます。
// tutorial21.js var CommentBox = React.createClass({ loadCommentsFromServer: function() { $.ajax({ url: this.props.url, dataType: 'json', cache: false, success: function(data) { this.setState({data: data}); }.bind(this), error: function(xhr, status, err) { console.error(this.props.url, status, err.toString()); }.bind(this) }); }, handleCommentSubmit: function(comment) { //new start var comments = this.state.data; // Optimistically set an id on the new comment. It will be replaced by an // id generated by the server. In a production application you would likely // not use Date.now() for this and would have a more robust system in place. comment.id = Date.now(); var newComments = comments.concat([comment]); this.setState({data: newComments}); //new end $.ajax({ url: this.props.url, dataType: 'json', type: 'POST', data: comment, success: function(data) { this.setState({data: data}); }.bind(this), error: function(xhr, status, err) { this.setState({data: comments}); // new console.error(this.props.url, status, err.toString()); }.bind(this) }); }, getInitialState: function() { return {data: []}; }, componentDidMount: function() { this.loadCommentsFromServer(); setInterval(this.loadCommentsFromServer, this.props.pollInterval); }, render: function() { return ( <div className="commentBox"> <h1>Comments</h1> <CommentList data={this.state.data} /> <CommentForm onCommentSubmit={this.handleCommentSubmit} /> </div> ); } });
おめでとうございます!
いくつかの簡単な手順でコメントモジュールを作成しました。 Reactを使用する理由の詳細をご覧になるか 、 APIに直接進んでコードの記述を開始してください! 頑張って