Redux'da belirli bir dizi öğesinin içindeki tek değer nasıl güncellenir


107

Durumun yeniden oluşturulmasının kullanıcı arabirimi sorunlarına neden olduğu ve bir sayfadaki yeniden oluşturma miktarını azaltmak için yalnızca indirgeyicimin içindeki belirli değeri güncellemem önerildiği bir sorunum var.

bu benim durumumun örneği

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

ve şu anda bu şekilde güncelliyorum

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

burada action.payloadyeni değerler içeren tam bir dizi. Ama şimdi içindekiler dizisindeki ikinci öğenin metnini güncellemem gerekiyor ve bunun gibi bir şey çalışmıyor

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

action.payloadşimdi güncelleme için ihtiyacım olan bir metin nerede .

Yanıtlar:


59

Sen kullanabilirsiniz Değiştirilemezlik yardımcıları Tepki

import update from 'react-addons-update';

// ...    

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

Muhtemelen böyle bir şey yapacağınızı hayal etmeme rağmen?

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

gerçekten de, bu yardımcıları bir şekilde redüktörümün içine reaksiyona dahil etmem gerekiyor mu?
Ilja

8
Paket taşınmış olsa da kolodny/immutability-helper, şimdi yarn add immutability-helperveimport update from 'immutability-helper';
SacWebDeveloper

Benim durumum tamamen aynı, ancak dizideki öğelerden birindeki nesneyi güncellediğimde, güncellenen değer componentWillReceiveProps'a yansımıyor. İçeriği doğrudan mapStateToProps'umda kullanıyorum, yansıtması için mapStateToProps'ta başka bir şey kullanmak zorunda mıyız?
Srishti Gupta

173

Kullanabilirsiniz map. İşte örnek bir uygulama:

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

ben de böyle yapıyorum, bunun iyi bir yaklaşım olup olmadığından emin değilim, çünkü map yeni bir dizi döndürecek. Düşüncesi olan var mı?
Thiago

@Ventura evet, iyi çünkü dizi için yeni bir referans ve güncellenmesi amaçlanan (ve sadece o) için düz yeni bir nesne yaratıyor, ki bu tam olarak istediğiniz şey
ivcandela

3
Bence bu en iyi yaklaşım
Jesus Gomez

12
Bunu da sık sık yapıyorum, ancak gerekli dizini güncellemek yerine tüm öğeleri yinelemek hesaplama açısından nispeten pahalı görünüyor.
Ben Creasy

güzel biri ! Onu seviyorum !
Nate Ben

21

Her şeyi tek satırda yapmak zorunda değilsiniz:

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

1
Haklısın, hızlı bir düzeltme yaptım. btw, dizi sabit uzunlukta mı? ve değiştirmek istediğiniz dizin de sabit mi? Mevcut yaklaşım sadece küçük dizi ve sabit indeks ile uygulanabilir.
Yuya

2
const blok kapsamlı olduğundan, vaka gövdesini {} içine alın.
Benny Powers

15

Partiye çok geç ama işte her endeks değeriyle çalışan genel bir çözüm.

  1. Eski diziden indexdeğiştirmek istediğiniz diziye kadar yeni dizi oluşturur ve yayarsınız .

  2. İstediğiniz verileri ekleyin.

  3. indexDeğiştirmek istediğiniz diziden dizinin sonuna kadar yeni dizi oluşturun ve yayın

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)
         ]
    }

Güncelleme:

Kodu basitleştirmek için küçük bir modül yaptım, bu yüzden sadece bir fonksiyonu çağırmanız gerekiyor:

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

Daha fazla örnek için, depoya bir göz atın

işlev imzası:

insertIntoArray(originalArray,insertionIndex,newData)

4

Redux eyaletinizde bu tür işlemlere ihtiyaç duyduğunuzda spread operatörünün arkadaşınız olduğuna ve bu ilkenin tüm çocuklar için geçerli olduğuna inanıyorum .

Bunun sizin durumunuz olduğunu varsayalım:

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

Ve Ravenclaw'a 3 puan eklemek istiyorsunuz

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
      }
    }
  }

Yayılma işlecini kullanarak yalnızca yeni durumu güncelleyerek diğer her şeyi olduğu gibi bırakabilirsiniz.

Bu harika makaleden alınan örnek, harika örneklerle hemen hemen her olası seçeneği bulabilirsiniz.


3

Benim durumumda, Luis'in cevabına dayanarak böyle bir şey yaptım:

...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

Projelerimden biri için bunu böyle yaptım:

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

Korkarım ki map()bir dizinin yöntemini kullanmak pahalı olabilir çünkü tüm dizi yinelenecek. Bunun yerine, üç bölümden oluşan yeni bir diziyi birleştiriyorum:

  • başlık - değiştirilen öğeden önceki öğeler
  • değiştirilmiş öğe
  • kuyruk - değiştirilen öğeden sonraki öğeler

İşte kodumda kullandığım örnek (NgRx, ancak diğer Redux uygulamaları için de aynıdır):

// 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];
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.