Reduxの特定の配列項目内の単一の値を更新する方法


105

状態の再レンダリングによってUIの問題が発生する問題があり、ページの再レンダリングの量を減らすために、レデューサー内の特定の値のみを更新するように提案されました。

これは私の状態の例です

{
 name: "some name",
 subtitle: "some subtitle",
 contents: [
   {title: "some title", text: "some text"},
   {title: "some other title", text: "some other text"}
 ]
}

私は現在このように更新しています

case 'SOME_ACTION':
   return { ...state, contents: action.payload }

どこaction.payloadに新しい値を含む全体の配列です。しかし今、私は実際にはcontents配列の2番目のアイテムのテキストを更新する必要があるだけで、このようなものは機能しません

case 'SOME_ACTION':
   return { ...state, contents[1].text: action.payload }

action.payload更新に必要なテキストはどこにありますか。

回答:


57

React Immutabilityヘルパーを使用できます

import update from 'react-addons-update';

// ...    

case 'SOME_ACTION':
  return update(state, { 
    contents: { 
      1: {
        text: {$set: action.payload}
      }
    }
  });

私はあなたがおそらくもっとこのようなことをしていると思いますか?

case 'SOME_ACTION':
  return update(state, { 
    contents: { 
      [action.id]: {
        text: {$set: action.payload}
      }
    }
  });

確かに、どういうわけか私のリデューサー内で反応するこれらのヘルパーを含める必要がありますか?
Ilja 2016

8
パッケージには、に移動してきたがkolodny/immutability-helper、今それだyarn add immutability-helperimport update from 'immutability-helper';
SacWebDeveloper

私の場合はまったく同じですが、配列の要素の1つでオブジェクトを更新すると、更新された値がcomponentWillReceivePropsに反映されません。mapStateToPropsでコンテンツを直接使用していますが、反映するためにmapStateToPropsで他の何かを使用する必要がありますか?
Srishti Gupta

171

使用できますmap。次に実装例を示します。

case 'SOME_ACTION':
   return { 
       ...state, 
       contents: state.contents.map(
           (content, i) => i === 1 ? {...content, text: action.payload}
                                   : content
       )
    }

それも私がやっている方法です。mapが新しい配列を返すので、それが良いアプローチかどうかはわかりません。何かご意見は?
チアゴC. Sベンチュラ

@Venturaはい、更新する予定のオブジェクト(およびそのオブジェクトのみ)の配列とプレーンな新しいオブジェクトを作成するので、これは良いことです。これは、まさにあなたが望むものです
ivcandela

3
これが最善のアプローチだと思います
ジーザスゴメス

12
私も頻繁にこれを行いますが、必要なインデックスを更新するよりも、すべての要素を反復処理するのは比較的計算コストがかかります。
Ben Creasy 2017年

プリティワン!大好きです !
ネイトベン

21

すべてを1行で行う必要はありません。

case 'SOME_ACTION':
  const newState = { ...state };
  newState.contents = 
    [
      newState.contents[0],
      {title: newState.contnets[1].title, text: action.payload}
    ];
  return newState

1
あなたは正しい、私は簡単な修正をしました。ところで、配列は固定長ですか?変更したいインデックスも固定されていますか?現在のアプローチは、小さな配列と固定インデックスでのみ実行可能です。
Yuya

2
constはブロックスコープであるため、ケース本体を{}で囲みます。
Benny Powers

12

パーティーには非常に遅れていますが、ここにすべてのインデックス値で機能する一般的なソリューションがあります。

  1. 古いアレイindexから変更したいアレイまで新しいアレイを作成して展開します。

  2. 必要なデータを追加します。

  3. index変更したい配列から配列の最後まで新しい配列を作成して広げます

let index=1;// probabbly action.payload.id
case 'SOME_ACTION':
   return { 
       ...state, 
       contents: [
          ...state.contents.slice(0,index),
          {title: "some other title", text: "some other text"},
         ...state.contents.slice(index+1)
         ]
    }

更新:

コードを簡略化するために小さなモジュールを作成したので、関数を呼び出すだけです。

case 'SOME_ACTION':
   return {
       ...state,
       contents: insertIntoArray(state.contents,index, {title: "some title", text: "some text"})
    }

その他の例については、 リポジトリ

関数のシグネチャ:

insertIntoArray(originalArray,insertionIndex,newData)

3

Redux状態でこの種の操作が必要な場合、スプレッドオペレーターはあなたの友達だと思いますあり、このプリンシパルはすべての子供に適用されます。

これがあなたの状態だとしましょう:

const state = {
    houses: {
        gryffindor: {
          points: 15
        },
        ravenclaw: {
          points: 18
        },
        hufflepuff: {
          points: 7
        },
        slytherin: {
          points: 5
        }
    }
}

そして、あなたはRavenclawに3ポイントを追加したいです

const key = "ravenclaw";
  return {
    ...state, // copy state
    houses: {
      ...state.houses, // copy houses
      [key]: {  // update one specific house (using Computed Property syntax)
        ...state.houses[key],  // copy that specific house's properties
        points: state.houses[key].points + 3   // update its `points` property
      }
    }
  }

スプレッドオペレーターを使用すると、新しい状態のみを更新して、他のすべてをそのままにできます。

この驚くべき記事から取られた例では、優れた例でほとんどすべての可能なオプションを見つけることができます。


3

私の場合、ルイスの答えに基づいて、私はこのようなことをしました:

...State object...
userInfo = {
name: '...',
...
}

...Reducer's code...
case CHANGED_INFO:
return {
  ...state,
  userInfo: {
    ...state.userInfo,
    // I'm sending the arguments like this: changeInfo({ id: e.target.id, value: e.target.value }) and use them as below in reducer!
    [action.data.id]: action.data.value,
  },
};

0

これは私が私のプロジェクトの1つでそれをした方法です:

const markdownSaveActionCreator = (newMarkdownLocation, newMarkdownToSave) => ({
  type: MARKDOWN_SAVE,
  saveLocation: newMarkdownLocation,
  savedMarkdownInLocation: newMarkdownToSave  
});

const markdownSaveReducer = (state = MARKDOWN_SAVED_ARRAY_DEFAULT, action) => {
  let objTemp = {
    saveLocation: action.saveLocation, 
    savedMarkdownInLocation: action.savedMarkdownInLocation
  };

  switch(action.type) {
    case MARKDOWN_SAVE:
      return( 
        state.map(i => {
          if (i.saveLocation === objTemp.saveLocation) {
            return Object.assign({}, i, objTemp);
          }
          return i;
        })
      );
    default:
      return state;
  }
};

0

map()配列全体を反復処理するため、配列のメソッドを使用するとコストがかかる可能性があります。代わりに、3つの部分で構成される新しい配列を結合します。

  • ヘッド -変更されたアイテムの前のアイテム
  • 変更されたアイテム
  • テール -変更されたアイテムの後のアイテム

ここで私がコードで使用した例(NgRxですが、そのメカニズムは他のRedux実装でも同じです):

// toggle done property: true to false, or false to true

function (state, action) {
    const todos = state.todos;
    const todoIdx = todos.findIndex(t => t.id === action.id);

    const todoObj = todos[todoIdx];
    const newTodoObj = { ...todoObj, done: !todoObj.done };

    const head = todos.slice(0, todoIdx - 1);
    const tail = todos.slice(todoIdx + 1);
    const newTodos = [...head, newTodoObj, ...tail];
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.