React、Redux、Immutable.jsは現在最も人気のあるJavaScriptライブラリのひとつであり、開発者が最初に選択するものになりつつあります。 フロントエンド開発 。私が取り組んできたいくつかのReactおよびReduxプロジェクトで、Reactを使い始めた多くの開発者が、Reactと、その可能性を最大限に活用するための効率的なコードの記述方法を完全に理解していないことに気付きました。
これで Immutable.js チュートリアルでは、を使用して簡単なアプリを作成します React そして 戻ってきた 、Reactの最も一般的な誤用のいくつかとそれらを回避する方法を特定します。
React パフォーマンスがすべてです。これは、非常にパフォーマンスが高く、新しいデータの変更に対応するためにDOMの最小限の部分のみを再レンダリングするようにゼロから構築されました。 Reactアプリは、ほとんどが小さな単純な(またはステートレス関数)コンポーネントで構成されている必要があります。それらは推論するのが簡単で、それらのほとんどは持つことができます shouldComponentUpdate 関数を返す false 。
shouldComponentUpdate(nextProps, nextState) { return false; }
パフォーマンスに関して、最も重要なコンポーネントのライフサイクル機能は shouldComponentUpdate 可能であれば、常に戻る必要があります false 。これにより、このコンポーネントが(最初のレンダリングを除いて)再レンダリングされることがなくなり、Reactアプリが非常に高速に感じられるようになります。
そうでない場合、私たちの目標は、古い小道具/状態と新しい小道具/状態の安価な同等性チェックを行い、データが変更されていない場合は再レンダリングをスキップすることです。
少し前に戻って、JavaScriptがさまざまなデータ型の等価性チェックを実行する方法を確認しましょう。
次のようなプリミティブデータ型の等価性チェック ブール値 、 ストリング そして 整数 それらは常に実際の値によって比較されるため、非常に単純です。
1 === 1 ’string’ === ’string’ true === true
一方、次のような複雑なタイプの等価性チェック オブジェクト 、 配列 そして 機能 完全に異なります。 2つのオブジェクトが同じ参照(メモリ内の同じオブジェクトを指している)を持っている場合、それらは同じです。
const obj1 = { prop: ’someValue’ }; const obj2 = { prop: ’someValue’ }; console.log(obj1 === obj2); // false
obj1とobj2は同じように見えますが、参照は異なります。それらは異なるので、内で素朴に比較します shouldComponentUpdate 関数により、コンポーネントが不必要に再レンダリングされます。
注意すべき重要なことは、Reduxレデューサーからのデータは、正しく設定されていない場合、常に異なる参照で提供されるため、コンポーネントが毎回再レンダリングされることに注意してください。
これは、コンポーネントの再レンダリングを回避するための私たちの探求における中心的な問題です。
深くネストされたオブジェクトがあり、それを以前のバージョンと比較したい例を見てみましょう。ネストされたオブジェクトの小道具を再帰的にループしてそれぞれを比較することもできますが、それは明らかに非常にコストがかかり、問題外です。
それは私たちに唯一の解決策を残します、そしてそれは参照をチェックすることです、しかし新しい問題はすぐに現れます:
これは、クリーンでパフォーマンスが最適化された方法で実行したい場合、簡単な作業ではありません。 Facebookはずっと前にこの問題に気づき、Immutable.jsを助けに呼びました。
import { Map } from ‘immutable’; // transform object into immutable map let obj1 = Map({ prop: ’someValue’ }); const obj2 = obj1; console.log(obj1 === obj2); // true obj1 = obj1.set(‘prop’, ’someValue’); // set same old value console.log(obj1 === obj2); // true | does not break reference because nothing has changed obj1 = obj1.set(‘prop’, ’someNewValue’); // set new value console.log(obj1 === obj2); // false | breaks reference
Immutable.js関数はいずれも、指定されたデータに対して直接ミューテーションを実行しません。代わりに、データは内部で複製され、変更され、変更があった場合は新しい参照が返されます。それ以外の場合は、初期参照を返します。 obj1 = obj1.set(...);
のように、新しい参照を明示的に設定する必要があります。
の力を実証するための最良の方法 これらのライブラリ シンプルなアプリを作ることです。そして、todoアプリよりも簡単なことは何ですか?
Linuxはどのプログラミング言語で書かれていますか
簡潔にするために、この記事では、これらの概念にとって重要なアプリの部分のみを説明します。アプリコードのソースコード全体は GitHubで見つかりました 。
アプリを起動すると、 console.log DOMの再レンダリングの量を明確に示すために、重要な領域に便利に配置されています。これは最小限です。
他のtodoアプリと同様に、todoアイテムのリストを表示したいと思います。ユーザーがToDoアイテムをクリックすると、完了としてマークされます。また、新しいToDoを追加するために上部に小さな入力フィールドが必要であり、下部に3つのフィルターが必要です。これにより、ユーザーは以下を切り替えることができます。
Reduxアプリケーションのすべてのデータは単一のストアオブジェクト内に存在し、レデューサーは、ストアをより簡単に推論できる小さな部分に分割する便利な方法と見なすことができます。レデューサーも機能しているので、さらに小さなパーツに分割することもできます。
レデューサーは2つの小さな部品で構成されます。
// reducers/todos.js import * as types from 'constants/ActionTypes'; // we can look at List/Map as immutable representation of JS Array/Object import { List, Map } from 'immutable'; import { combineReducers } from 'redux'; function todoList(state = List(), action) { // default state is empty List() switch (action.type) { case types.ADD_TODO: return state.push(Map({ // Every switch/case must always return either immutable id: action.id, // or primitive (like in activeFilter) state data text: action.text, // We let Immutable decide if data has changed or not isCompleted: false, })); // other cases... default: return state; } } function activeFilter(state = 'all', action) { switch (action.type) { case types.CHANGE_FILTER: return action.filter; // This is primitive data so there’s no need to worry default: return state; } } // combineReducers combines reducers into a single object // it lets us create any number or combination of reducers to fit our case export default combineReducers({ activeFilter, todoList, });
Immutable.jsデータを使用してReduxレデューサーをセットアップしたので、Reactコンポーネントに接続してデータを渡します。
// components/App.js import { connect } from 'react-redux'; // ….component code const mapStateToProps = state => ({ activeFilter: state.todos.activeFilter, todoList: state.todos.todoList, }); export default connect(mapStateToProps)(App);
完璧な世界では、接続はトップレベルのルートコンポーネントでのみ実行する必要があり、mapStateToPropsでデータを抽出し、残りは子に小道具を渡す基本的なReactです。大規模なアプリケーションでは、すべての接続を追跡するのが難しくなる傾向があるため、接続を最小限に抑えたいと考えています。
state.todosはReduxから返される通常のJavaScriptオブジェクトであることに注意することが非常に重要です CombineReducers 関数(todosはレデューサーの名前です)が、state.todos.todoListは不変リストであり、通過するまでそのような形式のままであることが重要です。 shouldComponentUpdate 小切手。
深く掘り下げる前に、コンポーネントに提供する必要のあるデータのタイプを理解することが重要です。
これらのタイプのデータがあると、Reactコンポーネントに含まれる小道具を浅く比較できます。
次の例は、可能な限り簡単な方法で小道具を比較する方法を示しています。
$ npm install react-pure-render
import shallowEqual from 'react-pure-render/shallowEqual'; shouldComponentUpdate(nextProps, nextState) !shallowEqual(this.state, nextState);
関数 浅い等しい 小道具/状態の差分を1レベルだけ深くチェックします。それは非常に高速に動作し、不変のデータと完全に相乗効果があります。これを書かなければならない shouldComponentUpdate すべてのコンポーネントで非常に不便ですが、幸いなことに簡単な解決策があります。
エキス shouldComponentUpdate 特別な別のコンポーネントに:
// components/PureComponent.js import React from 'react'; import shallowEqual from 'react-pure-render/shallowEqual'; export default class PureComponent extends React.Component { shouldComponentUpdate(nextProps, nextState) return !shallowEqual(this.props, nextProps) }
次に、このshouldComponentUpdateロジックが必要なコンポーネントを拡張します。
// components/Todo.js export default class Todo extends PureComponent { // Component code }
これは、ほとんどの場合、コンポーネントの再レンダリングを回避するための非常にクリーンで効率的な方法です。後でアプリがより複雑になり、突然カスタムソリューションが必要になった場合は、簡単に変更できます。
使用時に若干の問題があります PureComponent 小道具として機能を渡している間。 React以降、ES6で クラス 、自動的にバインドされません この 関数に対しては、手動で行う必要があります。これは、次のいずれかを実行することで実現できます。
this.handleClick()} />
どちらのアプローチでも 成分 別の参照がに渡されたため、再レンダリングする onClick 毎回。
製品のバックログはどのように整理されますか
この問題を回避するために、関数を事前にバインドできます。 ビルダー そのような方法:
constructor() { super(); this.handleClick = this.handleClick.bind(this); } // Then simply pass the function render() { return }
ほとんどの場合、複数の関数を事前にバインドしていることに気付いた場合は、小さなヘルパー関数をエクスポートして再利用できます。
// utils/bind-functions.js export default function bindFunctions(functions) { functions.forEach(f => this[f] = this[f].bind(this)); } // some component constructor() { super(); bindFunctions.call(this, ['handleClick']); // Second argument is array of function names }
どの解決策もうまくいかない場合は、いつでも書くことができます shouldComponentUpdate 手動で条件を設定します。
現在の不変のデータ設定では、再レンダリングが回避され、コンポーネントの小道具内に不変のデータが残されています。この不変データを使用する方法はいくつかありますが、最も一般的な間違いは、不変を使用してデータをすぐにプレーンJSに変換することです。 toJS 関数。
使用する toJS 不変のデータをプレーンなJSに深く変換すると、予想どおり非常に遅いため、再レンダリングを回避するという目的全体が無効になります。では、不変のデータをどのように処理するのでしょうか。
そのまま使用する必要があるため、ImmutableAPIはさまざまな機能を提供します。 地図 そして 取得する Reactコンポーネント内で最も一般的に使用されています。 todoList Redux Reducerからのデータ構造は、不変形式のオブジェクトの配列であり、各オブジェクトは単一のToDoアイテムを表します。
[{ id: 1, text: 'todo1', isCompleted: false, }, { id: 2, text: 'todo2', isCompleted: false, }]
Immutable.js APIは通常のJavaScriptと非常に似ているため、他のオブジェクトの配列と同じようにtodoListを使用します。ほとんどの場合、マップ関数が最適です。
マップコールバック内で取得します すべて 、これはまだ不変の形式のオブジェクトであり、安全に渡すことができます すべて 成分。
// components/TodoList.js render() { return ( // …. {todoList.map(todo => { return ( ); })} // …. ); }
次のような不変データに対して複数の連鎖反復を実行することを計画している場合:
myMap.filter(somePred).sort(someComp)
…次に、最初にそれをに変換することが非常に重要です シーケンス を使用して toSeq 繰り返した後、次のような目的の形式に戻します。
myMap.toSeq().filter(somePred).sort(someComp).toOrderedMap()
Immutable.jsは特定のデータを直接変更することはないため、常にそのデータの別のコピーを作成する必要があり、このような複数の反復を実行すると非常にコストがかかる可能性があります。 Seqは、怠惰な不変のデータシーケンスです。つまり、中間コピーの作成をスキップしながら、タスクを実行するために実行する操作をできるだけ少なくします。 Seqは、このように使用するために構築されました。
内部 すべて コンポーネントの使用 取得する または 入れ 小道具を入手します。
簡単でしょ?
さて、私が気付いたのは、多くの場合、get()
が多数あると非常に読みにくくなる可能性があるということです。特にgetIn()
。そこで、パフォーマンスと読みやすさの間にスイートスポットを見つけることにしました。いくつかの簡単な実験の結果、Immutable.jsが見つかりました。 物申す そして toArray 関数は非常にうまく機能します。
これらの関数は、(1レベルの深さの)Immutable.jsオブジェクト/配列をプレーンなJavaScriptオブジェクト/配列に浅く変換します。内部に深くネストされたデータがある場合、それらはコンポーネントの子に渡される準備ができた不変の形式のままになり、まさにそれが必要です。
get()
より遅いほんのわずかなマージンですが、かなりきれいに見えます:
設計において比率が重要な理由
// components/Todo.js render() { const { id, text, isCompleted } = this.props.todo.toObject(); // ….. }
クローンを作成していない場合 GitHubのコード それでも、今はそれを行う絶好の機会です。
git clone https://github.com/rogic89/ToDo-react-redux-immutable.git cd ToDo-react-redux-immutable
サーバーの起動は次のように簡単です(Node.jsとNPMがインストールされていることを確認してください)。
npm install npm start
Webブラウザでhttp:// localhost:3000に移動します。開発者コンソールを開いた状態で、いくつかのToDoアイテムを追加するときにログを監視し、それらを完了としてマークして、フィルターを変更します。
React、Redux、Immutable.jsの相乗効果を正しく使用すると、大規模なWebアプリケーションで頻繁に発生する多くのパフォーマンスの問題に対するいくつかのエレガントなソリューションが提供されます。
Immutable.jsを使用すると、深い等価性チェックの非効率性に頼ることなくJavaScriptオブジェクト/配列の変更を検出できます。これにより、Reactは、必要のないときにコストのかかる再レンダリング操作を回避できます。これは、Immutable.jsのパフォーマンスがほとんどのシナリオで良好になる傾向があることを意味します。
この記事が気に入って、作成に役立つことを願っています 革新的なソリューションに反応する あなたの将来のプロジェクトで。
関連: ReactコンポーネントがUIテストを容易にする方法Reduxは、オープンソースのJavaScriptで予測可能な状態コンテナです。 Reduxは通常、ReactやAngularなどのライブラリと組み合わせてユーザーインターフェイスを作成するために使用されます。
Immutable.jsは、不変のデータコレクションを作成するために設計されたライブラリです。 React / Reduxの開発で一般的に使用されています。 Immutable.jsはFacebookによって作成されました。
作成後に変更できないオブジェクトは、インスタンス化されると状態とプロパティを変更できないため、不変オブジェクトと見なされます。これは、名前が示すように変更可能な可変オブジェクトとは対照的です。