React.jsライブラリの公式ドキュメントチュートリアルの翻訳。
Reactを考える
私たちの意見では、ReactはJavaScriptを使用して大規模で高速なWebアプリケーションを構築する最良の方法です。 FacebookとInstagramでの経験では、Reactアプリも非常にうまく拡張できます。
Reactの多くの優れた機能の1つは、「アプリケーションをどのように設計し、作成するか」という原則です。 このチュートリアルでは、製品を見つけるためのデータの表を表示するReactアプリケーションを設計および作成する思考プロセス全体を実行します。
レイアウトから始めましょう。
特定のJSON APIとデザイナーからのレイアウトがあると想像してください。 レイアウトは次のとおりです。
JSON APIは次のようなデータを返します。
[ {category: "Sporting Goods", price: "$49.99", stocked: true, name: "Football"}, {category: "Sporting Goods", price: "$9.99", stocked: true, name: "Baseball"}, {category: "Sporting Goods", price: "$29.99", stocked: false, name: "Basketball"}, {category: "Electronics", price: "$99.99", stocked: true, name: "iPod Touch"}, {category: "Electronics", price: "$399.99", stocked: false, name: "iPhone 5"}, {category: "Electronics", price: "$199.99", stocked: true, name: "Nexus 7"} ];
手順1:ユーザーインターフェイスをコンポーネント階層に移動する
最初に行うことは、レイアウト上の各コンポーネント(および従属コンポーネント)の周りにボックスを描画し、それらに名前を付けることです。 あなたがデザイナーと仕事をする場合、彼はすでにこれを行っているかもしれないので、彼と話をする必要があります! Photoshopのレイヤー名は、Reactコンポーネントの命名に非常に適していることが判明する場合があります。
しかし、最終的な別個のコンポーネントがどうあるべきかをどのようにして知るのでしょうか? 新しい関数またはオブジェクトを作成するときと同じメソッドを使用して決定します。 これらの方法の1つは、唯一の責任の原則です 。つまり、コンポーネントは、理想的には、1つのもの/エンティティのみを作成する必要があります。 コンポーネントが他のコンポーネントで繰り返される複数のエンティティを作成する場合は、より小さなコンポーネントに分解する必要があります。
JSONデータモデルをユーザーインターフェースとして表示する頻度が高いほど、データモデルが正しく構築されていれば、ユーザーインターフェース(したがってコンポーネントの構造)が美しく見えるという結論にすばやく達します。 その理由は、ユーザーインターフェースとデータモデルが原則として同じ情報アーキテクチャに準拠しているためです。つまり、ユーザーインターフェースをコンポーネントに分割するのは簡単な作業であることが多いということです。データモデル。」
レイアウトからわかるように、単純なアプリケーションには5つのコンポーネントがあります。 各コンポーネントをマルチカラーのボックスで強調表示しました。
-
FilterableProductTable
(オレンジ):テーブル全体が含まれています -
SearchBar
(青):すべてのユーザー入力を受け入れます -
ProductTable
(緑色): ユーザー入力に基づいてデータセットを表示およびフィルターし ます。 -
ProductCategoryRow
(ターコイズ):各カテゴリーのタイトルを表示します -
ProductRow
(赤):各アイテムの行を表示します
ProductTable
を見ると、テーブルタイトル(ラベル「Name」と「Price」を含む)が別のコンポーネントで選択されていないことがわかります。 これは好みの問題であり、このオプションまたはそのオプションには引数があります。 この例では、 ProductTable
の責任であるデータセットの視覚化の一部であるため、 ProductTable
一部として見出しを残しました。 ただし、タイトルがより複雑な場合(たとえば、列で並べ替える機能を追加する必要がある場合)、もちろん、別のProductTableHeader
コンポーネントに分離します。
レイアウトでコンポーネントを定義したので、コンポーネントを階層でフォーマットしましょう。 簡単です。 レイアウト内の別のコンポーネントの一部であるコンポーネントは、階層内の子孫のように見える必要があります。
-
FilterableProductTable
-
SearchBar
-
ProductTable
-
ProductCategoryRow
-
ProductRow
-
-
ステップ2:Reactで静的バージョンを作成する
<div id="container"> <!-- . --> </div>
body { padding: 5px }
class ProductCategoryRow extends React.Component { render() { return <tr><th colSpan="2">{this.props.category}</th></tr>; } } class ProductRow extends React.Component { render() { var name = this.props.product.stocked ? this.props.product.name : <span style={{color: 'red'}}> {this.props.product.name} </span>; return ( <tr> <td>{name}</td> <td>{this.props.product.price}</td> </tr> ); } } class ProductTable extends React.Component { render() { var rows = []; var lastCategory = null; this.props.products.forEach(function(product) { if (product.category !== lastCategory) { rows.push(<ProductCategoryRow category={product.category} key={product.category} />); } rows.push(<ProductRow product={product} key={product.name} />); lastCategory = product.category; }); return ( <table> <thead> <tr> <th>Name</th> <th>Price</th> </tr> </thead> <tbody>{rows}</tbody> </table> ); } } class SearchBar extends React.Component { render() { return ( <form> <input type="text" placeholder="Search..." /> <p> <input type="checkbox" /> {' '} Only show products in stock </p> </form> ); } } class FilterableProductTable extends React.Component { render() { return ( <div> <SearchBar /> <ProductTable products={this.props.products} /> </div> ); } } var PRODUCTS = [ {category: 'Sporting Goods', price: '$49.99', stocked: true, name: 'Football'}, {category: 'Sporting Goods', price: '$9.99', stocked: true, name: 'Baseball'}, {category: 'Sporting Goods', price: '$29.99', stocked: false, name: 'Basketball'}, {category: 'Electronics', price: '$99.99', stocked: true, name: 'iPod Touch'}, {category: 'Electronics', price: '$399.99', stocked: false, name: 'iPhone 5'}, {category: 'Electronics', price: '$199.99', stocked: true, name: 'Nexus 7'} ]; ReactDOM.render( <FilterableProductTable products={PRODUCTS} />, document.getElementById('container') );
コンポーネント階層ができたので、今度はアプリケーションを実装します。 最も簡単な方法は、データモデルとユーザーインターフェイスを表示するが、インタラクティブ機能がない静的バージョンから始めることです。 静的部分と対話型部分の分離は、次のような優れたソリューションです。 静的バージョンを実装するタスクは、思考プロセスを最小限に抑えてキーボードから大量のテキストを入力することです。一方、インタラクティブ機能を実装するには、思考プロセスとキーボードからの小さな入力が必要です。 次に、なぜそうなのかを見ていきます。
静的バージョンの実装は、データモデルの表示(レンダリング)です。その間、他のコンポーネントを使用するコンポーネントを作成し、 props(プロパティ)を介してデータを送信します 。 小道具は、Reactコンポーネント階層の親から子にデータを転送するためのツールです。 Reactには状態のような概念があります -静的バージョンを作成するときに状態を 使用しないでください。 州の主な目的は対話性であり、時間とともに変化するデータの送信と記録に必要です。 現時点では、アプリケーションの静的バージョンを作成しているため、Stateを使用する必要はありません。
アプリケーションをトップダウンまたはボトムアップで実装できます。 つまり、上位の階層レベルのコンポーネント( FilterableProductTable
始まる)を構築することから開始するか、その逆に下位レベル( ProductRow
)から構築することができます。 単純なアプリケーションでは、通常、上から下へ、そしてより大きなアプリケーションでは下から上へと移動し、コンポーネントの実装時に並行してテストを作成する方が簡単です。
この手順の最後に、データモデルを表示する再利用可能なコンポーネントのライブラリが作成されます。 これは静的バージョンであるrender()
、コンポーネントにはrender()
メソッドのみがありrender()
。 階層の最上位にあるコンポーネント( FilterableProductTable
)は、 propsを介してデータモデルを受け取ります。 基になるデータモデルに変更を加え、 ReactDOM.render()
再度呼び出すと、ユーザーインターフェイスが更新されます。 それは正しくありません-それは本当に非常に単純なので、複雑なものは何もありませんか? React one - way データフロー ( one - wayバインディングとも呼ばれます)は、モジュール性と速度を提供します。
小さな余談:プロパティ(props)と状態(state)
Reactには、データの2つの「モデル」、小道具と状態があります。 2つの違いを理解することが重要です。 違いがわからない場合は、公式ドキュメントの対応するセクションを再度お読みください。
手順3:ユーザーインターフェイスの状態の最小(ただし十分な)表現を決定する
ユーザーインターフェイスをインタラクティブにするには、基になるデータモデルの変更を登録できる必要があります。 Reactでは、これは単にstateで行われます。
正しいアプリケーションを構築するには、まず、アプリケーションに必要な可変状態の最小セットを考慮する必要があります。 DRYの原則に従う: 自分自身を繰り返さないでください 。 理想的には、アプリケーションの状態の最小表現には、必要なときに利用可能なデータ(小道具と状態)に基づいて計算できるものを含めないでください。 例:To Doリストを表示するアプリケーションを構築する場合、To Doレコードを含む配列内にとどまります-To Doアイテムの数を表示する別のStatus変数を作成しないでください。 代わりに、ケースの数を表示する必要があるその瞬間に、既存の配列の長さを取得してください。
アプリケーションのすべてのデータユニットについて考えます。 あります
- オリジナル製品リスト
- ユーザーが入力した検索テキスト
- チェックボックス値
- フィルターされた製品リスト
各項目を調べて、それが状態かどうかを判断しましょう。 各データユニットについて、3つの質問をする必要があります。
- このデータは祖先から小道具を介して送信されますか? もしそうなら、これはおそらく国家ではありません。
- このデータは時間とともに変化しませんか? もしそうなら、これはおそらく国家ではありません。
- コンポーネントで利用可能な(小道具と状態で)に基づいてこのデータを計算できますか? その場合、これは州ではありません。
製品の元のリストは小道具を介して送信されるため、ステータスではありません。 検索テキストとチェックボックスの値は時間とともに変化する可能性があり、利用可能なデータから計算することはできません-これらは状態です。 そして最後:元の製品リスト、検索テキスト、チェックボックスの値を組み合わせて、フィルターされた製品リストを計算できます-これは州ではありません。
その結果、私たちの州は:
- ユーザーが入力した検索テキスト
- チェックボックス値
ステップ4:ステータスの場所を決定する
<div id="container"> <!-- . --> </div>
body { padding: 5px }
class ProductCategoryRow extends React.Component { render() { return (<tr><th colSpan="2">{this.props.category}</th></tr>); } } class ProductRow extends React.Component { render() { var name = this.props.product.stocked ? this.props.product.name : <span style={{color: 'red'}}> {this.props.product.name} </span>; return ( <tr> <td>{name}</td> <td>{this.props.product.price}</td> </tr> ); } } class ProductTable extends React.Component { render() { var rows = []; var lastCategory = null; this.props.products.forEach((product) => { if (product.name.indexOf(this.props.filterText) === -1 || (!product.stocked && this.props.inStockOnly)) { return; } if (product.category !== lastCategory) { rows.push(<ProductCategoryRow category={product.category} key={product.category} />); } rows.push(<ProductRow product={product} key={product.name} />); lastCategory = product.category; }); return ( <table> <thead> <tr> <th>Name</th> <th>Price</th> </tr> </thead> <tbody>{rows}</tbody> </table> ); } } class SearchBar extends React.Component { render() { return ( <form> <input type="text" placeholder="Search..." value={this.props.filterText} /> <p> <input type="checkbox" checked={this.props.inStockOnly} /> {' '} Only show products in stock </p> </form> ); } } class FilterableProductTable extends React.Component { constructor(props) { super(props); this.state = { filterText: '', inStockOnly: false }; } render() { return ( <div> <SearchBar filterText={this.state.filterText} inStockOnly={this.state.inStockOnly} /> <ProductTable products={this.props.products} filterText={this.state.filterText} inStockOnly={this.state.inStockOnly} /> </div> ); } } var PRODUCTS = [ {category: 'Sporting Goods', price: '$49.99', stocked: true, name: 'Football'}, {category: 'Sporting Goods', price: '$9.99', stocked: true, name: 'Baseball'}, {category: 'Sporting Goods', price: '$29.99', stocked: false, name: 'Basketball'}, {category: 'Electronics', price: '$99.99', stocked: true, name: 'iPod Touch'}, {category: 'Electronics', price: '$399.99', stocked: false, name: 'iPhone 5'}, {category: 'Electronics', price: '$199.99', stocked: true, name: 'Nexus 7'} ]; ReactDOM.render( <FilterableProductTable products={PRODUCTS} />, document.getElementById('container') );
そのため、アプリケーションステータスの最小セットを定義しました。 次のステップでは、どのコンポーネントがこの状態を変更または所有するかを決定します。
要確認:Reactは、コンポーネント階層で一方向のデータ転送を行います。 おそらく、どのコンポーネントが国の所有者である必要があるかは、これからすぐには明らかではありません。 この部分は初心者にとっては難しい場合が多いので、次の手順に従ってこの質問を見つけてください。
アプリケーションのステータスユニットごとに:
- この状態に基づいて何かを表示(レンダリング)するすべてのコンポーネントを識別します。
- これらのコンポーネントの共通の祖先(このステータスを必要とするすべてのコンポーネントの上の階層内の単一のコンポーネント)を見つけます。
- 見つかった共通の祖先または上位の階層のコンポーネントは、国家の所有者に任命できます。
- 共通の祖先がコンポーネント階層にない場合は、それを国家の所有者にするためだけに、より高いレベルのコンポーネントを作成する必要があります。
それでは、この戦略をアプリケーションに適用しましょう。
-
ProductTable
コンポーネントには、製品のリストをフィルター処理するためのStateが必要ですが、SearchBar
コンポーネントには、検索クエリとチェックボックスのステータスを表示するためのStateが必要です。 - それらの共通の祖先は
FilterableProductTable
コンポーネントです。 - したがって、概念的には、リストと選択された値のフィルタリングの共通点は
FilterableProductTable
コンポーネントにあります
さて、StateをFilterableProductTable
コンポーネントに配置する必要があると判断しました。 最初に、インスタンスプロパティthis.state = {filterText: '', inStockOnly: false}
をFilterableProductTable
コンポーネントのconstructor
FilterableProductTable
、アプリケーションの初期状態を決定します。 次に、 inStockOnly
を使用してfilterText
およびinStockOnly
をProductTable
およびSearchBar
コンポーネントにSearchBar
ます。 最後のステップは、 小道具を使用してProductTable
文字列をフィルター処理し、 SearchBar
フォームフィールド値を設定することSearchBar
。
アプリケーションを起動して、その動作を確認できますfilterText
コンポーネントFilterableProductTable
でfilterText
値を'ball'
し、アプリケーションをリロードします。 データテーブルが正しく更新されていることがわかります。
ステップ5:データフローを追加する
<div id="container"> <!-- . --> </div>
body { padding: 5px }
class ProductCategoryRow extends React.Component { render() { return (<tr><th colSpan="2">{this.props.category}</th></tr>); } } class ProductRow extends React.Component { render() { var name = this.props.product.stocked ? this.props.product.name : <span style={{color: 'red'}}> {this.props.product.name} </span>; return ( <tr> <td>{name}</td> <td>{this.props.product.price}</td> </tr> ); } } class ProductTable extends React.Component { render() { var rows = []; var lastCategory = null; this.props.products.forEach((product) => { if (product.name.indexOf(this.props.filterText) === -1 || (!product.stocked && this.props.inStockOnly)) { return; } if (product.category !== lastCategory) { rows.push(<ProductCategoryRow category={product.category} key={product.category} />); } rows.push(<ProductRow product={product} key={product.name} />); lastCategory = product.category; }); return ( <table> <thead> <tr> <th>Name</th> <th>Price</th> </tr> </thead> <tbody>{rows}</tbody> </table> ); } } class SearchBar extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); } handleChange() { this.props.onUserInput( this.filterTextInput.value, this.inStockOnlyInput.checked ); } render() { return ( <form> <input type="text" placeholder="Search..." value={this.props.filterText} ref={(input) => this.filterTextInput = input} onChange={this.handleChange} /> <p> <input type="checkbox" checked={this.props.inStockOnly} ref={(input) => this.inStockOnlyInput = input} onChange={this.handleChange} /> {' '} Only show products in stock </p> </form> ); } } class FilterableProductTable extends React.Component { constructor(props) { super(props); this.state = { filterText: '', inStockOnly: false }; this.handleUserInput = this.handleUserInput.bind(this); } handleUserInput(filterText, inStockOnly) { this.setState({ filterText: filterText, inStockOnly: inStockOnly }); } render() { return ( <div> <SearchBar filterText={this.state.filterText} inStockOnly={this.state.inStockOnly} onUserInput={this.handleUserInput} /> <ProductTable products={this.props.products} filterText={this.state.filterText} inStockOnly={this.state.inStockOnly} /> </div> ); } } var PRODUCTS = [ {category: 'Sporting Goods', price: '$49.99', stocked: true, name: 'Football'}, {category: 'Sporting Goods', price: '$9.99', stocked: true, name: 'Baseball'}, {category: 'Sporting Goods', price: '$29.99', stocked: false, name: 'Basketball'}, {category: 'Electronics', price: '$99.99', stocked: true, name: 'iPod Touch'}, {category: 'Electronics', price: '$399.99', stocked: false, name: 'iPhone 5'}, {category: 'Electronics', price: '$199.99', stocked: true, name: 'Nexus 7'} ]; ReactDOM.render( <FilterableProductTable products={PRODUCTS} />, document.getElementById('container') );
現在の手順では、コンポーネントの階層を下に移動する小道具と状態を正しく転送および使用するアプリケーションを作成しました。 反対方向のデータ転送のサポートを追加するときが来ました。階層の下位にあるフォームのコンポーネントは、何らかのFilterableProductTable
コンポーネントのステータスを更新する必要があります。
このデータストリームを作成するためのReactメカニズムは、プログラムの動作を簡単に理解できるように設計されていますが、従来の双方向データバインディングよりも少し多くのキーボード入力が必要です。
サンプルの現在のバージョンでテキストを入力するか、ボックスをチェックしようとすると、Reactは入力を無視することがわかります。 これは、私たちが意図的に確立したものです。 値プロパティの値を、 FilterableProductTable
コンポーネントから渡されるstate
常に等しいinput
に設定します。
私たちが何をしたいのか考えてみましょう。 ユーザーがフォームを変更するたびに、ステータスが更新されてユーザー入力が表示されます。 コンポーネントは独自の状態のみを更新するFilterableProductTable
ため、 FilterableProductTable
コンポーネントFilterableProductTable
コールバックメカニズムをSearchBar
コンポーネントに渡すFilterableProductTable
があります。これは、状態を更新する必要があるたびに通知します。 これを報告するには、フォームのコンポーネントでonChange
イベントを使用します。 次に、 FilterableProductTable
コンポーネントによって渡されるコールバックがsetState()
を呼び出し、アプリケーションが更新されます。
複雑に見えますが、実際にはほんの数行のコードです。 そして、アプリケーション全体でデータがどのように送信されるかは本当に透過的に見えます。
それだけです
このチュートリアルで、Reactのコンポーネントとアプリケーションを構築する際の考え方を理解していただければ幸いです。 以前よりも少し多くコードを入力する必要がありましたが、コードは書かれているよりも何倍も頻繁に読み取られ、透明性とモジュール性のためにコードが読みやすいことを覚えておいてください。 大規模なライブラリまたはアプリケーションの作成を開始するとすぐに、この透明性とモジュール性を真に評価します。再利用の可能性により、コードの入力が少なくなるという事実が必ず生じます。