永遠に機能する:より安全な状態のための不変のオブジェクト


著者によってFederico Kereki
前の記事ではForever Functional: Injecting for Purity 私たちは純粋な関数は、彼らが仕事を行うときに観察可能な副作用を生成しないことに言及し、これは“外で”何も変更していません含まれています.しかし、我々はすべてのオブジェクトは、たとえconst , 変更可能です.グローバルな状態は、このように「副作用」の可能な目標です:どんな機能もそれを変えるかもしれなくて、状況を理解しにくい状況に至ります.本稿では、より安全な状態取り扱いのための不変のオブジェクトで働く方法について議論します.したがって、偶然にそれらを変えることは難しくなるか、または直接に不可能になります.
開発者が安全で、保護された方法で働くようになる規則を実施することは不可能です.しかし、データ構造を変更不能にすることができます(そして、我々はそのようなオブジェクトで状態を保ちます)状態を変更する方法を制御します.元のデータを直接変更することはできませんが、代わりに新しいオブジェクトを生成しないようなインターフェイスで動作しなければなりません.観察者は、これが正確に背後にある考えであることに気づくかもしれないRedux : 状態を直接変更することはありません.新しい状態を生成するために、還元子が処理するアクションを送ります.
ここでは、オブジェクトの凍結やクローニングなどのJavaScriptが提供する様々な方法を研究しますので、完全な不変性解決が必要なものについての基本的な理解を得ることができます.しかし、我々は100 %の生産準備コードを達成するために、おそらくgetters , setters , private attributes , その他このため、利用可能なライブラリを見てみましょうImmutable , Immer , Seamless-immutable , など.
以下のオプションを考えます.
  • 変数の変更を防ぐためのconst宣言の使用
  • すべての変更を避けるためのオブジェクトの凍結
  • 変更されたクローンを作成する
  • 適用されるオブジェクトを変更するmutator関数の回避
  • 他のよりあいまいな方法
  • 始めましょう!

    const宣言の使用
    我々が考えることができた最初の解決策はconst宣言を使用して変数を変更不能にすることです.しかし、これはオブジェクトや配列では動作しませんconst 定義は、元のオブジェクトまたは配列への参照に適用されます.一方、新しいオブジェクトまたは配列をconst 変数は、確かにその内容を変更することができます.
    const myself = { name: "Federico Kereki" };
    myself = { name: "Somebody else" }; // this throws an error, but...
    myself.name = "Somebody else";      // no problem here!
    
    私たちはconst 変数ですが、変更できます.定数の使用は、booleans、数字、または文字列のような他の型で動作します.しかし、オブジェクトや配列を操作するとき、凍結やクローニングなどの他の方法に頼らなければなりません.

    mutator関数の回避
    問題の原因はいくつかのJavaScriptメソッドがそれらが適用されるオブジェクトを変異することによって働くということです.我々がちょうどこれらのメソッドのどれかを使うならば、我々は副作用を引き起こしている(故意に、または、知られていない)でしょう.問題のほとんどはarrays and methods 例えば
  • fill() 配列を値で埋める
  • sort() 配列を配置する
  • reverse() 配列の要素を代入する
  • push() , pop() , shift() , unshift() , and splice() 配列から要素を追加または削除するには
  • この動作は他のメソッドとは異なりますconcat() , map() , filter() , flat() , これは元の配列または配列を変更しません.幸いなことに、これは簡単な方法です.Mutatorメソッドを使用したい場合は、元の配列のコピーを生成し、メソッドを適用します.あなたに私の例をあげましょうMastering JavaScript Functional Programming ブック.配列の最大要素を取得したい場合.可能な方法は、最初に配列をソートし、最後の要素をポップすることです.
    const maxStrings = a => a.sort().pop();
    let countries = ["Argentina", "Uruguay", "Brasil", "Paraguay"];
    console.log(maxStrings(countries)); // "Uruguay"
    console.log(countries); // ["Argentina", "Brasil", "Paraguay"]
    
    おっ!我々は最大を得たが、元の配列がクラッシュした.我々は、我々の機能をコピーで働くために書き直すことができます.
    const maxStrings2 = a => [...a].sort().pop();
    const maxStrings3 = a => a.slice().sort().pop();
    let countries = ["Argentina", "Uruguay", "Brasil", "Paraguay"];
    console.log(maxStrings2(countries)); // "Uruguay"
    console.log(maxStrings3(countries)); // "Uruguay"
    console.log(countries); // ["Argentina", "Uruguay", "Brasil", "Paraguay"] - unchanged
    
    現在、我々は元の配列のコピーにmutatorメソッドを適用しているので、私たちの新しいバージョンは、副作用なしで機能的です.これは解決策ですが、開発者には慎重に依存します我々がよりよくすることができるかどうか見ましょう.

    凍結対象
    JavaScriptは、偶然(または意図的な)を避ける方法を提供します配列とオブジェクトへの変更凍結凍結された配列またはオブジェクトを変更することはできません.これを行うと、例外なく例外なく失敗します.前のセクションから例を見てみましょう.
    const myself = { name: "Federico Kereki" };
    Object.freeze(myself);
    myself.name = "Somebody else";      // won't have effect
    console.log(myself.name);           // Federico Kereki
    
    これも配列で動作します.
    const someData = [ 22, 9, 60 ];
    Object.freeze(someData);
    someData[1] = 80;                   // no effect
    console.log(someData);              // still [ 22, 9, 60 ]
    
    凍結はうまくいきますが、問題があります.冷凍オブジェクトが凍結されていないオブジェクトを含む場合、それらは変更可能です.
    const myself = { name: "Federico Kereki", someData: [ 22, 9, 60 ] };
    Object.freeze(myself);
    myself.name = "Somebody else";      // won't have effect
    myself.someData[1] = 80;            // but this will!
    console.log(someData);              // [ 22, 80, 60 ]
    
    実際に不変のオブジェクトをやりとりするためには、オブジェクトとその属性、およびそれらの属性の属性を再帰的に凍結するコードを記述しなければなりません.
    const deepFreeze = obj => {
      if (obj && typeof obj === "object" && !Object.isFrozen(obj)) {
        Object.freeze(obj);
        Object.getOwnPropertyNames(obj).forEach(prop => deepFreeze(obj[prop]));
      }
    return};
    
    我々deepFreeze() 関数は、オブジェクトを代わりにObject.freeze() は、元のメソッドのセマンティクスを保持します.これをコード化するとき、おそらく円形の参照に注意しなければなりません.問題を回避するには、まずオブジェクトを凍結します.再帰的メソッドを既に凍結されたオブジェクトに適用すると、何もしません.

    オープンソースセッション
    Webアプリケーションの生産におけるデバッグは、挑戦的で、時間がかかるかもしれません.OpenReplay フルストーリー、ログオンとhotjarにオープンソースの代替手段です.それはあなたが監視し、すべてのユーザーが再生し、どのようにすべての問題のためにあなたのアプリの行動を示す再生することができます.
    それはあなたのユーザーの肩を見ながら、ブラウザの検査官を開いているようなものだ.
    OpenReplayは現在利用可能なオープンソースの代替手段です.

    ハッピーデバッギング、現代のフロントエンドチームStart monitoring your web app for free .

    クローンの作成
    凍結は可能性がありますが、時には新しい(突然変異した)オブジェクトまたは配列を生成する必要があります.このようにRedux この方法は、現在の状態とアクション(いくつかの新しいデータ)を取得し、新しい更新データを生成するために新しいデータを状態に適用する機能です.reduxは現在の状態を変更することを禁止します.代わりに新しいオブジェクトを生成しなければなりません.
    どのように我々はオブジェクトをクローン化できますか?ちょうどコピーしないでください:以下のようなものはちょうど同じオブジェクトに新しい参照を作成します.
    const myself = { name: "Federico Kereki", otherData: { day:22, month:9 } };
    const newSelf = myself;    // not a clone; just a reference to myself
    
    単純なオブジェクトでは、拡散は(部分!)解決法
    const newSelf = { ...myself };
    
    しかしnewSelf.otherData まだ、元のオブジェクトにmyself オブジェクト.
    newSelf.otherData.day = 80;
    console.log(myself.otherData.day);   // 80, not 22!
    
    私たちはdeepCopy() オブジェクトをコピーするたびに、適切なコンストラクターを呼び出すことによって新しいものをビルドする機能です.上記の本から、次のコードも取り上げられます.
    const deepCopy = obj => {
      let aux = obj;
      if (obj && typeof obj === "object") {
        aux = new obj.constructor();
        Object.getOwnPropertyNames(obj).forEach(
          prop => (aux[prop] = deepCopy(obj[prop]))
        );
      }
      return aux;
    };
    
    それで、我々はオブジェクトに関する問題を避ける2つの方法を持ちます:凍結(変更を避けるために)とクローニング(完全に別々のコピーを生産することができます).我々は今、安全を達成することができます!

    他の(より不明瞭な)方法
    これらは唯一の解決策ではありませんが、彼らはあなたを開始するのに十分です.また、次のようないくつかの他の方法も考えられます.
  • すべての属性にセッターを追加するので、変更は禁止されます
  • 一般的な記述setAttributeByPath() オブジェクトを取る関数"otherData.day" ) そして、新しい値、および元の1つのクローンを作ることによって更新されたオブジェクトを生成する
  • または光学などのより高度な機能プログラミングのアイデアに入る.これにはレンズ(物体からの値の取得や設定)やプリズム(レンズのように)が含まれています.しかし、それは別の記事により良い左です!
  • 最後に、利用可能なライブラリを使用する可能性を無視しないでくださいimmutable.js その他多数.

    概要
    この記事では、オブジェクトや配列を処理する際に、不要な副作用によって引き起こされる問題を解決しました.我々は、凍結、クローニング、およびmutatorの回避を含むそれらを解決するいくつかの方法を検討してきた.すべてのこれらのツールは非常に便利です、そして、多くのウェブUIフレームワークはimimabilityを仮定するか、必要とします、それで、現在、あなたはこれらの必要条件の前にいます!