Reactを考える

画像







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つのコンポーネントがあります。 各コンポーネントをマルチカラーのボックスで強調表示しました。







  1. FilterableProductTable



    (オレンジ):
    テーブル全体が含まれています
  2. SearchBar



    (青):
    すべてのユーザー入力を受け入れます
  3. ProductTable



    (緑色):
    ユーザー入力に基づいてデータセットを表示およびフィルター ます。
  4. ProductCategoryRow



    (ターコイズ):
    カテゴリーのタイトルを表示します
  5. ProductRow



    (赤):
    アイテムの行を表示します


ProductTable



を見ると、テーブルタイトル(ラベル「Name」と「Price」を含む)が別のコンポーネントで選択されていないことがわかります。 これは好みの問題であり、このオプションまたはそのオプションには引数があります。 この例では、 ProductTable



の責任であるデータセットの視覚化一部であるため、 ProductTable



一部として見出しを残しました。 ただし、タイトルがより複雑な場合(たとえば、列で並べ替える機能を追加する必要がある場合)、もちろん、別のProductTableHeader



コンポーネントに分離します。







レイアウトでコンポーネントを定義したので、コンポーネントを階層でフォーマットしましょう。 簡単です。 レイアウト内の別のコンポーネントの一部であるコンポーネントは、階層内の子孫のように見える必要があります。









ステップ2:Reactで静的バージョンを作成する





HTML
 <div id="container"> <!--       . --> </div>
      
      





CSS
 body { padding: 5px }
      
      





Javascript
 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つの質問をする必要があります。







  1. このデータは祖先から小道具を介して送信されますか? もしそうなら、これはおそらく国家ではありません。
  2. このデータは時間とともに変化しませんか? もしそうなら、これはおそらく国家ではありません。
  3. コンポーネントで利用可能な(小道具と状態で)に基づいてこのデータを計算できますか? その場合、これは州ではありません。


製品の元のリストは小道具を介して送信されるため、ステータスではありません。 検索テキストとチェックボックスの値は時間とともに変化する可能性があり、利用可能なデータから計算することはできません-これらは状態です。 そして最後:元の製品リスト、検索テキスト、チェックボックスの値を組み合わせて、フィルターされた製品リストを計算できます-これは州ではありません。







その結果、私たちの州は:









ステップ4:ステータスの場所を決定する





HTML
 <div id="container"> <!--       . --> </div>
      
      





CSS
 body { padding: 5px }
      
      





Javascript
 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は、コンポーネント階層で一方向のデータ転送を行います。 おそらく、どのコンポーネントが国の所有者である必要があるかは、これからすぐには明らかではありません。 この部分は初心者にとっては難しい場合が多いので、次の手順に従ってこの質問を見つけてください。







アプリケーションのステータスユニットごとに:









それでは、この戦略をアプリケーションに適用しましょう。









さて、StateをFilterableProductTable



コンポーネントに配置する必要があると判断しました。 最初に、インスタンスプロパティthis.state = {filterText: '', inStockOnly: false}



FilterableProductTable



コンポーネントのconstructor



FilterableProductTable



、アプリケーションの初期状態を決定します。 次に、 inStockOnly



を使用してfilterText



およびinStockOnly



ProductTable



およびSearchBar



コンポーネントにSearchBar



ます。 最後のステップは、 小道具を使用してProductTable



文字列をフィルター処理し、 SearchBar



フォームフィールド値を設定することSearchBar









アプリケーションを起動して、その動作を確認できますfilterText



コンポーネントFilterableProductTable



filterText



値を'ball'



し、アプリケーションをリロードします。 データテーブルが正しく更新されていることがわかります。







ステップ5:データフローを追加する





HTML
 <div id="container"> <!--       . --> </div>
      
      





CSS
 body { padding: 5px }
      
      





Javascript
 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のコンポーネントとアプリケーションを構築する際の考え方を理解していただければ幸いです。 以前よりも少し多くコードを入力する必要がありましたが、コードは書かれているよりも何倍も頻繁に読み取られ、透明性とモジュール性のためにコードが読みやすいことを覚えておいてください。 大規模なライブラリまたはアプリケーションの作成を開始するとすぐに、この透明性とモジュール性を真に評価します。再利用の可能性により、コードの入力が少なくなるという事実が必ず生じます。







出典: React-クイックスタート-Reactで考える








All Articles