React.js状態管理チュートリアルを表示

公開: 2022-03-11

フロントエンドWeb開発における最大かつ最も一般的な問題の1つは、状態管理です。 私のようなフリーランスのフロントエンド開発者は、状態オブジェクトをそのビューとDOM表現と同期させることに常に注力しています。 ユーザーはさまざまな方法でアプリケーションを操作できます。ある状態から別の状態へのクリーンな移行を提供することは大きなタスクです。

このReact.jsチュートリアルでは、ビューの状態管理について詳しく学びます。

ちょっとした歴史

それほど昔のことではありませんが、Webアプリケーションのデータフローははるかに単純でした。 ブラウザはサーバーにリクエストを送信し、すべてのアプリケーションロジックはサーバー上で実行され、完全なHTMLビューがブラウザに返送されてユーザーに表示されます。 後続のユーザーアクション(クリック、フォーム送信など)により、同じフローが再度トリガーされます。 アプリケーションはユーザーの状態を気にする必要がなく、サーバーに新しいリクエストを送信することで各ビューを再生成できます。

ただし、Webアプリケーションは複雑さを増し、UI/UXに対するユーザーの要求も進んでいました。 ページの一部だけが変更されたときにページ全体をリロードすると、効果がなく、時間がかかりました。 UIにすぐに影響を与える、迅速で迅速かつ応答性の高い対話が必要でした。

JavaScriptが助けになりました。 開発者は、リクエストがサーバーに送信される前にブラウザで実行される大量のコードを書き始めました。 jQueryは、クライアント側の検証、モーダルウィンドウ、アラートメッセージ、アニメーション、さらにはAjaxベースの部分的なページ更新など、すぐに使用できるシンプルで効果的な機能を提供するため、フロントエンドWeb開発にも大きな進歩をもたらしました。

複雑さを理解する

パスワードの強度を評価する簡単な例を見てみましょう。 パスワードに問題がない場合は、入力ボックスに緑色の境界線が表示され、適切なメッセージが表示されます。 パスワードが弱い場合は、入力ボックスに赤い境界線が表示され、警告メッセージが表示されます。 パスワードが十分に強力な場合は、スマイリーフェイスを表示することもあります。

次のコードは、これをDOM操作で実行する方法を示しています。 ここにはたくさんの「if」があり、コードはあまり読みやすいものではありません。

 if (hasInputBorder()) { removeInputBorder(); } if (text.length === 0) { if (hasMessage()) { removeMessage(); } if (hasSmiley()) { removeSmiley(); } } else { var strength = getPasswordStrength(text); if (!hasInputBorder()) { addInputBorder(); } var color = (strength == 'weak' ? 'red' : 'green'); setInputBorderColor(color); var message = (strength == 'weak' ? "Password is weak" : "That's what I call a password!"); if (hasMessage()) { setMessageText(message); } else { addMessageWithText(message); } if (strength == 'weak') { if (hasSmiley()) { removeSmiley(); } } else { if (!hasSmiley()) { addSmiley(); } } }

上記のように、最初にユーザーがパスワードを入力したかどうかを確認し、パスワードフィールドが空の場合に対処する必要があります。 また、すべての場合において、関連するすべてのDOM要素が適切に更新されていることを確認する必要があります。 これには、メッセージ、境界線、およびスマイリーフェイスが含まれます。

パスワードフィールドは、空、弱い、または強いの3つの状態のいずれかになります。 また、前述のように、パスワードフィールドの状態の影響を受ける3つの異なるDOM要素があります。 すべての組み合わせを処理し、ビューが適切に表示されるようにすることで、このような単純なコードでも循環的複雑度が高まります。

DOMは保持モードで動作します。つまり、DOMは現在の状態のみを記憶します。 ビューを変更するには、各DOM要素の指示を提供し、遷移をプログラムする必要があります。

状態の代わりに遷移をコーディングすることは複雑になる可能性があります。 コードで実行する必要のあるブランチとチェックの数は、管理するビューステートの数とともに指数関数的に増加します。

この例では、3つのビューステートを定義しました。これにより、 3 * 2 = 6の遷移が得られました。 一般に、N個の状態が与えられると、モデル化する必要のあるN * (N - 1) = N^2 - Nの遷移があります。 例に4番目の状態を追加した場合の複雑さの増加について考えてみてください。

通常、遷移のモデリングに関連するコードが多すぎます。 ビューの状態を定義するだけで、ある状態から別の状態への移行の詳細について心配する必要がない場合は、はるかに優れています。

複雑さの軽減

ある状態から別の状態への遷移を明示的にコーディングする代わりに、モデルの状態に基づいてビューの状態を宣言できると仮定すると、次のようになります。

 var strength = getPasswordStrength(text); if (text.length == 0) { return div(input({type: 'password', value: text})); } else if (strength == 'weak') { return div( input({type: 'password', value: text, borderColor: 'red'}), span({}, "Weak") ); } else { return div( input({type: 'password', value: text, borderColor: 'green'}), span({}, "That's what I call a password!"), img({class: 'icon-smiley'}) ); }

ここに、アプリの3つの可能な状態を表す3つの単純なコードブランチがあります。 モデルの状態に応じて、各ブランチのビューの仕様を返すだけです。 すべてのDOM操作コードが削除されます。 必要情報を提供するだけで、そこに到達する方法は提供しません。

このアプローチはコードの複雑さを大幅に軽減しますが、私たちに代わって実際のDOM操作を処理する誰かまたは何か他のものがあることも前提としています。

そこでReactが登場します。Reactは、基になるデータモデルの状態に基づいて、ビューステートが即座に管理および更新されるようにします。

反応

ReactはFacebookによって作成されたJavaScriptライブラリです。 これは、WebアプリケーションのUI部分を処理するように設計されています。 これは、MVCアーキテクチャのVと考えることができます。 それは非常に焦点を当てています。 テクノロジースタックの残りの部分については何も想定せず、レンダリングコンポーネント以外は処理しません。 通常、より大きなフレームワークにバンドルされているルーティングメカニズム、モデル、またはその他の機能は提供されません。 したがって、それを組み合わせて、他の任意のライブラリまたはフレームワークと使用することができます。

Reactを使用すると、UIを複合コンポーネントのツリーとして定義できます。 React開発者は、入力状態を指定して、コンポーネントを説明するレンダリング関数を指定することにより、これらのコンポーネントを定義します。 その関数は純粋である必要があります(つまり、副作用がないか、明示的な入力以外のものに依存してはなりません)。

レンダリング関数は、Reactが仮想DOMを呼び出すビューの説明を返します。 レンダリングされたDOM要素に対応するJavaScriptオブジェクトと考えてください。

コンポーネントの状態を変更すると、コンポーネントはそれ自体とそのすべての子要素を再レンダリングし、新しい仮想DOMを返します。

さらに、ある状態から別の状態に移行するときに、Reactは単純なHTML置換を行いません。 以前の状態と新しい状態の違いを見つけ、遷移を実行するための最も効果的なDOM操作のセットを計算します。

パフォーマンスを考慮しなくても、コードの複雑さを軽減すること自体が重要であり、アプリケーションのよりユニークで複雑な部分に注力することができます。

もう少し具体的に言うと、これがReactを使用してビューステートを管理するチュートリアルの例を作成する方法です。

注:次のコードサンプルはJSXプリプロセッサで記述されています。これは、ReactベースのUIを記述する一般的な方法です。

 function getPasswordStrength(text) { // Some code that calculates the strength given the password text. } var PasswordWithStrength = React.createClass({ getInitialState: function() { return {value: ''}; }, render: function() { var strength = getPasswordStrength(this.state.value); if (this.state.value.length == 0) { return <div> <input type="password" value={this.state.value} onChange={this.handleInputChange} /> </div>; } else if (strength == 'weak') { return <div> <input type="password" value={this.state.value} onChange={this.handleInputChange} style={ {border: '1px solid red'} } /> <span style={{color: 'red'}}>Weak!</span> </div>; } else { return <div> <input type="password" value={this.state.value} onChange={this.handleInputChange} style={ {border: '1px solid green'} } /> <span style={{color: 'green'}}>That's what I call a password!</span> <Emoji value="smiley" /> </div>; } }, handleInputChange: function(ev) { this.setState({value: ev.target.value}); } }); React.render(<PasswordWithStrength />, document.body);

<Emoji value="smiley" />でパスワード強度に問題がない場合にレンダリングされるEmojiコンポーネントは、別のカスタムコンポーネントです( PasswordWithStrengthと同様)。 これは次のように定義されます。

 var Emoji = React.createClass({ render: function() { var emojiSrc = this.props.value + '.png'; return <img src={emojiSrc}></img>; } });

React.jsとその他

ただし、公平を期すために、ビューステート管理の問題を解決し、さらに多くの機能を追加した他のクライアント側JavaScriptフレームワーク(Ember、Angular、Knockoutなど)があります。 では、なぜ他のフレームワークの代わりにReactを使用したいのでしょうか?

他のほとんどのライブラリと比較して、Reactには2つの重要な利点があります。

データバインディングなし

他の代替フレームワークのいくつかは、データバインディングを使用して、DOM要素を状態プロパティにマップし、プロパティの変更を監視することによってそれらの同期を維持します。 このアプローチでは、ビューを1回レンダリングし、変更するたびに、影響を受けるDOM要素の変更のみをトリガーできます。 他の選択肢はダーティチェックを使用します; つまり、個々のプロパティの変更を監視する代わりに、前の状態と新しい状態の間で差分を実行するだけです。 Reactは後者のアプローチに似ていますが、状態を比較する代わりに、ビュー表現を比較します。

Reactにはデータバインディングがありません。 開発者は、状態が変更されたときに、 setStateメソッドを呼び出すか、最上位のコンポーネントを再レンダリングする必要があります。 状態からビューへの一方向の流れを包含します。

開発者は一般にデータバインディングについて考えないため、この概念は簡単に採用できます。 焦点は、データの視覚的表現にあります。 したがって、依存するプロパティ、フォーマット、特別なHTMLタグのバインドなどについて考える必要はありません。Reactを使用すると、モデルが変更されたときにコンポーネントを再レンダリングするだけです。

ここでのビュー状態管理の違いを理解するために、 EmberとReactを比較してみましょう。 フルネームを大文字で出力するオブジェクトpersonを作成します。 2秒後、変更をシミュレートしてビューを更新します。

 // EXAMPLE USING EMBER App = Ember.Application.create(); App.Person = Ember.Object.extend({ firstName: null, lastName: null, fullName: function() { return this.get('firstName') + ' ' + this.get('lastName'); }.property('firstName', 'lastName') }); var person = App.Person.create({ firstName: "John", lastName: "Doe" }); Ember.Handlebars.helper('upcase', function(value) { return value.toUpperCase(); }); App.IndexRoute = Ember.Route.extend({ model: function () { return person; } }); setTimeout(function() { person.set('firstName', 'Harry'); }, 2000); // Templates: <script type="text/x-handlebars"> <h2>Welcome to Ember.js</h2> {{outlet}} </script> <script type="text/x-handlebars" data-template-name="index"> The current user is: {{upcase model.fullName}} </script>

firstNamelastName 、およびfullNameプロパティを使用してオブジェクトを作成しました。 Emberはプロパティの変更を監視しているため、 fullNamefirstNamelastNameに依存することを指定する必要がありました。 これを行うために、 fullNameを定義したときに.property('firstName', 'lastName')を追加しました。

2秒後、 person.set('firstName', 'Harry'); 実行されます。 これにより、ビューとそのバインディングが更新されました。

それでは、Reactでも同じことをしましょう。

 // EXAMPLE USING REACT var CurrentUser = React.createClass({ render: function() { return <div>The current user is: {this.props.user.fullName().toUpperCase()}</div>; } }); var person = { firstName: 'John', lastName: 'Doe', fullName: function() { return this.firstName + ' ' + this.lastName; } }; var currentUser = React.render(<CurrentUser user={person}/>, document.body); setTimeout(function() { person.firstName = 'Harry'; currentUser.setProps({user: person}); }, 2000);

Emberコードはシンプルで読みやすいものですが、Reactがシンプルさで勝っていることは明らかです。 personはプレーンなJavaScriptオブジェクトであり、 fullNameは単に関数です。

テンプレートなし

代替フレームワークごとに、テンプレートの処理方法が異なります。 JavaScriptにコンパイルされた文字列を使用するものもあれば、DOM要素を直接使用するものもあります。 それらのほとんどは、カスタムHTML属性とタグを使用し、それらはHTMLに「コンパイル」されます。

テンプレートはJavaScriptコードの一部ではありません。 このため、各選択肢には、一般的な操作、条件、反復、関数の呼び出しなどを表すカスタムの方法が必要です。これらはすべて、開発者が学習する必要のある新しい疑似言語を作成することになります。

Reactにはテンプレートはありません。すべてが単なる古いJavaScriptです。

Reactは、ビューを生成するためにJavaScriptの全機能を使用します。 コンポーネントのrenderメソッドはJavaScript関数です。

JSXは、「HTMLのような構文」を通常のJavaScriptに変換するプリプロセッサとして利用できますが、JSXはオプションであり、プリプロセッサなしで標準のJavaScriptを自由に使用できます。 既存のJavaScriptツールを利用することもできます。 リンター、プリプロセッサー、型注釈、縮小、デッドコード除去など。

もう一度具体的な例を使用して、Reactをビュー状態管理の代替フレームワークの1つと比較してみましょう。

次のチュートリアルは、 AngularJSを使用してハッシュタグと各ハッシュタグのツイート数を一覧表示する例です。 リストはカウントでソートされ、ハッシュタグがない場合はメッセージが表示されます。

 <!-- EXAMPLE USING ANGULAR --> <div ng-controller="MyCtrl"> <ul ng-show="hashTags.length > 0"> <li ng-repeat="hashTag in hashTags | orderBy:'tweetCount':true"> {{hashTag.name}} - {{hashTag.tweetCount}} tweets </li> </ul> <span ng-show="hashTags.length == 0">No hashtags found!</span> </div>

このリストを作成できるようにするには、開発者はAngularJS directivesng-show 、およびng-repeatについて学習する必要があります。 次に、 orderByを理解するためにAngularJS filtersについて学ぶ必要があります。 リストを出力するような簡単なことのためにたくさんの仕事があります!

次に、同じことを行うReactの例を考えてみましょう。

 // EXAMPLE USING REACT function byTweetCountDesc(h1, h2) { return h2.tweetCount - h1.tweetCount; } //... render: function() { if (this.state.hashTags.length > 0) { var comps = this.state.hashTags.sort(byTweetCountDesc).map(function(hashTag, index) { return <li key={index}> {hashTag.name} - {hashTag.tweetCount} tweets </li>; }); return <ul>{comps}</ul>; } else { return <span>No hashtags found!</span> } }

「より高度な」アプローチとJSXを使用した場合でも、JavaScriptの基本を理解しているすべてのWeb開発者は、上記のコードを簡単に読んで、その機能を理解できます。 ifを使用した標準の条件付きチェック、 map()を使用した反復、および標準の'sort()'は、すべての開発者に自然に提供されるため、追加の構文やその他の概念を学ぶ必要はありません。

結論

このReact.jsチュートリアルの主なポイントは、Reactを使用すると、遷移ではなく実際のビューステート管理に集中できるため、作業とアプリケーションが簡素化されるという事実です。

Reactを採用するための学習曲線はかなり些細なものです。 マスターするカスタムテンプレート言語も、心配するデータバインディングもありません。すべては、UI要素を記述するJavaScript関数に帰着します。

Reactを使用してアプリケーションコードを単純化する方法の詳細については、Reactを使用したコードの複雑化解除に関するStevenLuscherによるこの講演をご覧ください。

次のステップに進んでReactの使用を開始したい人のための追加の読み物は次のとおりです。

  • http://jlong​​ster.com/Removing-User-Interface-Complexity,-or-Why-React-is-Awesome