初心者ガイド:回想


この記事はもともと投稿されたmalikbrowne.com .
先週、私は反応V 16の新しいライフサイクル方法に関するガイダンスのために異なる記事を閲覧していました.3 .私は出会ったthis article それは多くの開発者がどのように使用するかについて話すgetDerivedStateFromProps 悪い.
あなたが反応に精通していないならば、その小道具の変化の結果として、手段は単に内部の状態を更新するためにコンポーネントを許します.しかし、この記事は私のコードでは何もしないように勧めました.
用途getDerivedStateFromProps or componentWillReceiveProps コンポーネントが入力を変更するときに、コンポーネントが再レンダリングのための高価な計算を実行することを確認します.
しかし、これを実行するより簡単で簡潔な方法は、memoization .
パフォーマンスに興味のあるプログラマーとして、私は、私が1日単位で書くコードをスピードアップするのに役立つ新しい機能的プログラミング技術に遭遇するのが大好きです.memoizationは、他のエンジニアが異なるアルゴリズムの問題について話したことを聞いたことでした.しかし、私はすべての誇大宣伝が何であったかについて見るために、時間がかかりませんでした.
このポストでは、私は純粋な機能がどのようなものであるかを説明するつもりです、そして、どのようにあなたはあなたのコードをより高性能にするために反応成分で彼らを結合することができますか?
純粋な機能について話すことから始めましょう.


何が純粋な機能ですか?
定義では、pure関数は以下の基準を満たす関数です.
  • 同じ引数が渡された場合、常に同じ結果を返す関数です.
  • 以下のようなアプリケーションに対して観測可能な副作用を生成しない関数です.
  • ネットワーク要求
  • データ突然変異
  • ファイルへのログ記録
  • アプリケーションの状態を変更
  • それはあなたがそれに渡すデータだけをアクセスする機能です.
  • このアイデアをクリックするのに役立つかもしれない何かthis article これはコーヒーのグラインダーに純粋な関数を比較します.

    A pure function is like a coffee grinder: beans go in, the powder comes out, end of story.



    利益
    純粋な関数にはいくつかの利点があります.
  • 彼らは、異なる入力がどのように出力に関連するかについて説明するより宣言的なプログラムにつながることができます.
  • コードのテスト容易性を高めることができ、悪夢のコードをデバッグできません.
  • しかし、一般的に、副作用は悪いことではないということに注意してください.

    純粋関数の例
    を返しますfactorial of a number :
    const factorial = n => {
         if (n === 1) {
              return n;
          }
        return n * factorial(n - 1)
    }
    
    // factorial(4)
    // 4! === 4 * 3 * 2 * 1 === 24
    
    我々が通るならばfactorial(4) , 私たちの計算が行われると、結果、24、すべての単一の時間を返します.
    純粋な関数が毎回同じ値を返すのを知っているので、我々の機能が我々の結果を覚えていることができたならば、それが便利でないでしょうか?次の誰かが計算したい方法factorial(100) , 我々は時間と資源を節約することができるだけでそれらを既に格納されて答えを与える.
    それは、私の友人は、回想です.

    何が実際には何ですか?
    定義によって

    Memoization is an optimization technique used primarily to speed up programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again.


    あなたが同じ質問をするならば、それは機能が問題への解決を記憶することを意味します.memoizationの単純な解決を達成するために、私たちは、私たちの機能がそれから参照することができた地図の形でいくつかのタイプのキャッシュを実装することができます.
    ここでは、私たちの要因解決がmemoizedされた機能でどのように見えるかです.
    // our original factorial function
    const factorial = n => {
        if (n === 1) {
             return n;
         }
       return n * factorial(n - 1)
    }
    // a memoized function used to calculate our factorial
    const scopedMemoizedFactorial = () => {
      const fakeCache = {};
      return (value) => {
        if (value in fakeCache) {
          // return the value from our fake cache
          return fakeCache[value];
        }
        else {
          // calculate our factorial
          const result = factorial(value);
          fakeCache[value] = result;
          return result;
        }
      }
    }
    

    気付くべきこと
  • scopedMemoizedFactorial 後で呼び出される関数を返します.関数は最初のクラスオブジェクトであるため、JavaScriptでこれを行うことができます.これは、関数を高次の関数として使用し、別の関数を返すことを意味します.
  • The fakeCache の値は、
  • 前に話したように、我々が働いている機能は純粋です.それが同じ値を返さなかったならば、我々のキャッシュは出力のために正しい値を返さないでしょう!
  • あなたが一般的なmemoizedされた機能の例を見たいならば、チェックしてくださいthis gist これは、JavaScript Patterns Stoyan Stefanovによって.

    反応における回想の使用
    この例では、アプリケーションのユーザーのすべてについてJSONを返すサードパーティAPIをふりましょう.データ構造は以下のようになります.
    [
        {
            name: "Malik",
            age: 24,
            company: "Meetup",
            // ...and a bunch of other fields like this
        },
        // ...and 996 other entries just like this
    ]
    
    あなたが全体のデータセットがどのように見えるかを見たいと思うならば、チェックアウトしてくださいthis link . ありがとうJSON Generator これは!
    我々のアプリケーションの要件は、ユーザーのリストを介してフィルタリングされ、名前がクエリに一致するすべてのユーザーのソートリストを返す検索ボックスを作成することです.
    memoizationのないコードは次のようになります.
    class App extends React.PureComponent{
      state = {
        searchValue: ""
      };
    
      filterList = (list, searchValue) =>
        list.filter(member => member.name.toLowerCase().startsWith(searchValue));
    
      sortList = list =>
        list.sort((a, b) => {
          if (a.name < b.name) return -1;
          if (a.name > b.name) return 1;
          return 0;
        });
    
      handleInputChange = searchValue => {
        this.setState({ searchValue, inputChanged: true });
      };
    
      render() {
        const { searchValue, inputChanged } = this.state;
        const filteredMembers = this.filterList(data, searchValue);
        const members = this.sortList(filteredMembers);
    
        return (
          <div className="App">
            <h1>No Memoization Example</h1>
            <Search
              searchValue={searchValue}
              onInputChange={e => this.handleInputChange(e.target.value)}
              placeholder="Search for a member"
            />
            <div className="members">
              {members.map(member => {
                return <Member member={member} key={member._id} />;
              })}
            </div>
          </div>
        );
      }
    }
    
    アクションコードをチェックアウトhere .
    このソリューションは、ほとんどの状況では、完全に細かい仕事をするが、データの大規模なセットでは、アプリケーションの多くが遅くなります.
    これは2つの理由で起こります.
  • 大量のデータのフィルタリングは高価な操作である
  • アプリケーションの他の再レンダリングは、機能が再び高価な操作を呼び出すようになります.
  • ヘルパーの使用memoize-one この例に簡単にメモを追加できます.
    import memoize from 'memoize-one';
    
    class App extends React.PureComponent {
      state = {
        searchValue: ""
      };
    
      filterList = memoize((list, searchValue) =>
        list.filter(member => member.name.toLowerCase().startsWith(searchValue))
      );
    
      sortList = memoize(list =>
        list.sort((a, b) => {
          if (a.name < b.name) return -1;
          if (a.name > b.name) return 1;
          return 0;
        })
      );
    
      handleInputChange = searchValue => {
        this.setState({ searchValue });
      };
    
      render() {
        const { searchValue } = this.state;
        const filteredMembers = this.filterList(data.slice(0, 50), searchValue);
        const members = this.sortList(filteredMembers);
    
        return (
          <div className="App">
            <h1>With Memoization Example</h1>
            <Search
              searchValue={searchValue}
              onInputChange={e => this.handleInputChange(e.target.value)}
              placeholder="Search for a member"
            />
            <div className="members">
              {members.map(member => {
                return <Member member={member} key={member._id} />;
              })}
            </div>
          </div>
        );
      }
    }
    
    memoize-one それは最後の関数コールの結果を格納するので、素晴らしいですので、あなたは約心配する必要はありませんcache busting issues .

    重要な注意事項
    回想のアイデアは、すべてのですが、念頭に置いて念頭に置いて主な利点を維持する:高価な関数呼び出しの結果を格納します.
    私たちの要因の解決策を取ってPerformance Timeline API 関数呼び出しがどれほどの時間を要したのか(マイクロ秒まで)
    // we use performance.now() to keep track of how long each call takes
    const tick = () => performance.now();
    const t0 = tick()
    
    optimizedFactorial(5000); // calculated
    const t1 = tick();
    console.log(`The first call took ${t1 - t0}ms.`);
    // The first call took 0.3999999971711077ms.
    
    optimizedFactorial(5000); // cached
    const t2 = tick();
    console.log(`Our memoized call took ${t2 - t1}ms.`);
    // Our memoized call took 2.2000000026309863ms.
    
    optimizedFactorial(4999); // calculated again with different param
    const t3 = tick();
    console.log(`A call that wasn't stored in our cache took ${t3 - t2}ms.`);
    // A call that wasn't stored in our cache took 0.3999999971711077ms
    
    ご存知のように、私のコンピュータ上では、同じ結果を得るために、5回以上長い時間をかけました.これは、我々のmemoization技術が働くために、コンピュータが新しい変数のためのメモリーを割り当てる必要があり、それをインスタンス化する必要がある.そして、それは計算を実行できる前に各々の時間の塊を取る.
    結果として、我々は、この解決においてmemoize技術を使用することが早熟な最適化であることを見ることができて、否定的に我々のアプリケーションのパフォーマンスに影響を及ぼします.
    注意すべきもう一つのことは、この解決策は、以下のようなキャッシュを「bustingする」ことに関連した多くの痛みを処理しないことです.
  • 最大年齢またはサイズを設定する
  • キャッシュの除外
  • これらの痛みの両方は、悪夢をデバッグすることができます我々のアプリケーションのメモリリークにつながることができます.このため、多くのエンジニアは、それらの一般的な問題を扱うために痛みにすでに実装されているmemoizationヘルパーを使用する傾向があります.これらのいくつかは以下を含みます:
  • memoize-one
  • Lodash's memoize function
  • 反応の回想に関してReact blog post カバーの主な制約の一部.同様の例を使っているので、以下のように共有します.
    1. In most cases, you’ll want to attach the memoized function to a component instance. This prevents multiple instances of a component from resetting each other’s memoized keys.
    2. You will also want to use a memoization helper with a limited cache size in order to prevent memory leaks over time.
    3. None of the implementations shown in this section will work if props.members is recreated each time the parent component renders. But in most cases, this setup is appropriate.


    結論
    Memoryizationは素晴らしいテクニックですが、正しく使用する場合は、アプリケーションを過充電することができます.より機能的なプログラミング手法を使用することで、より容易に、より予測可能なコードを導きます.
    私は非常にパッケージと呼ばれるパッケージを介してアプリケーションのいずれかでMemoryizationを試してみることをお勧めしますmemoize-one .
    あなたはこの記事の概念のいずれかについての質問がある場合は、コメントに質問を残してお気軽に!
    私は常にdevのコミュニティの人々からの聴聞会を開いているので、私にも無料でご連絡ください.パフォーマンスのための回想を使用してあなたの意見を教えてください!
    次の方でお会いしましょう.