this.setState durumları beklediğim gibi birleştirmiyor


160

Aşağıdaki duruma sahibim:

this.setState({ selected: { id: 1, name: 'Foobar' } });  

Sonra durumu güncelliyorum:

this.setState({ selected: { name: 'Barfoo' }});

Yana setStatebirleştirmek varsayalım olduğunu Olmasını beklediğiniz:

{ selected: { id: 1, name: 'Barfoo' } }; 

Ama bunun yerine kimliği yiyor ve devlet:

{ selected: { name: 'Barfoo' } }; 

Bu beklenen davranış mı ve iç içe durum nesnesinin yalnızca bir özelliğini güncelleştirme çözümü nedir?

Yanıtlar:


143

Bence setState()özyinelemeli birleştirme yapmıyor.

this.state.selectedYeni bir durum oluşturmak için geçerli durumun değerini kullanabilir ve ardından bunu çağırabilirsiniz setState():

var newSelected = _.extend({}, this.state.selected);
newSelected.name = 'Barfoo';
this.setState({ selected: newSelected });

_.extend()Burada fonksiyonun işlevini (underscore.js kütüphanesinden) selected, sığ bir kopyasını oluşturarak devletin mevcut kısmında değişiklik yapılmasını önlemek için kullandım .

Başka bir çözüm, setStateRecursively()yeni bir durum üzerinde özyinelemeli birleştirme yapan ve daha sonra replaceState()onunla çağıran yazmak olacaktır :

setStateRecursively: function(stateUpdate, callback) {
  var newState = mergeStateRecursively(this.state, stateUpdate);
  this.replaceState(newState, callback);
}

7
Birden fazla kez yaparsanız veya güvenilir bir şekilde çalışır mı yoksa tepki yerine replaceState çağrılarını sıralayacak ve sonuncusu kazanacak mı?
edoloughlin

111
this.setState({ selected: { ...this.state.selected, name: 'barfoo' } })this.setState({ selected: _extends({}, this.state.selected, { name: 'barfoo' }) });
Genişletmeyi

8
@Pickels bu harika, React için güzel deyim ve underscore/ gerektirmez lodash. Kendi cevabına çevir lütfen.
ericsoco

14
this.state mutlaka güncel değildir . Genişletme yöntemini kullanarak beklenmedik sonuçlar alabilirsiniz. Güncelleme işlevinin geçilmesi setStatedoğru çözümdür.
Strom

1
@ EdwardD'Souza Lodash kütüphanesi. Genellikle alt çizgi olarak çağrılır, jQuery genellikle bir dolar işareti olarak çağrılır. (Yani, 's _.extend ().)
mjk

96

Değişmezlik yardımcıları son zamanlarda React.addons'a eklendi, böylece bununla şimdi böyle bir şey yapabilirsiniz:

var newState = React.addons.update(this.state, {
  selected: {
    name: { $set: 'Barfoo' }
  }
});
this.setState(newState);

Değişmezlik yardımcıları belgeleri .


7
güncelleme kullanımdan kaldırıldı. facebook.github.io/react/docs/update.html
thedanotto

3
@thedanotto Bu React.addons.update()yöntem kullanımdan kaldırılmış gibi görünüyor , ancak github.com/kolodny/immutability-helper değiştirme kitaplığı eşdeğer bir update()işlev içeriyor .
Bungle

37

Cevapların birçoğu mevcut durumu yeni verilerde birleştirmek için bir temel olarak kullandığından, bunun kırılabileceğini belirtmek istedim. Durum değişiklikleri kuyruğa alınır ve bir bileşenin durum nesnesini hemen değiştirmez. Kuyruk işlenmeden önce durum verilerine başvuruda bulunmanız, bu nedenle setState'te yaptığınız beklemedeki değişiklikleri yansıtmayan eski verileri verecektir. Dokümanlardan:

setState () hemen this.state öğesini değiştirmez ancak bekleyen bir durum geçişi oluşturur. Bu yöntemi çağırdıktan sonra this.state öğesine erişmek potansiyel olarak mevcut değeri döndürebilir.

Bu, sonraki setState çağrılarında "geçerli" durumun referans olarak kullanılması güvenilir olmadığı anlamına gelir. Örneğin:

  1. Durum nesnesindeki bir değişikliği sıraya alan ilk setState çağrısı
  2. SetState için ikinci çağrı. Durumunuz iç içe nesneler kullanır, bu nedenle birleştirme gerçekleştirmek istersiniz. SetState öğesini çağırmadan önce, geçerli durum nesnesini alırsınız. Bu nesne, yukarıda setState'e yapılan ilk çağrıda yapılan kuyruktaki değişiklikleri yansıtmaz, çünkü hala "eski" olarak kabul edilmesi gereken orijinal durumdur.
  3. Birleştirme gerçekleştirin. Sonuç orijinal "eski" durum artı yeni ayarladığınız yeni verilerdir, ilk setState çağrısında yapılan değişiklikler yansıtılmaz. SetState çağrınız bu ikinci değişikliği sıralar.
  4. Tepki işlemleri kuyruğu. İlk setState çağrısı işlenir, durum güncellenir. İkinci setState çağrısı işlenir, durum güncellenir. İkinci setState nesnesi artık ilkinin yerini aldı ve bu çağrıyı yaparken sahip olduğunuz veriler eski olduğu için, bu ikinci çağrıdaki değiştirilmiş eski veriler, ilk çağrıda yapılan ve kaybolan değişiklikleri hızlandırdı.
  5. Kuyruk boş olduğunda, React vb. Oluşturulup oluşturulmayacağını belirler. Bu noktada ikinci setState çağrısında yapılan değişiklikleri yaparsınız ve ilk setState çağrısı hiç gerçekleşmemiş gibi olur.

Geçerli durumu kullanmanız gerekiyorsa (örneğin verileri iç içe geçmiş bir nesneye birleştirmek için), setState alternatif olarak bir işlevi nesne yerine bağımsız değişken olarak kabul eder; işlev, önceki herhangi bir durum güncellemesinden sonra çağrılır ve durumu bağımsız değişken olarak geçirir - böylece bu, önceki değişikliklere uymayı garanti eden atom değişikliklerini yapmak için kullanılabilir.


5
Bunun nasıl yapılacağına dair bir örnek veya daha fazla doküman var mı? afaik, kimse bunu dikkate almaz.
Josef.B

@Dennis Cevabımı aşağıda deneyin.
Martin Dawson

Sorunun nerede olduğunu anlamıyorum. Açıkladığınız şey yalnızca setState çağrıldıktan sonra "yeni" duruma erişmeye çalıştığınızda gerçekleşir. Bu OP sorusuyla hiçbir ilgisi olmayan tamamen farklı bir sorundur. Yeni bir durum nesnesi oluşturabilmeniz için setState öğesini çağırmadan önce eski duruma erişmek asla sorun olmaz ve aslında React'in beklediği de tam olarak budur.
nextgentech

@nextgentech "Yeni bir durum nesnesi oluşturabilmeniz için setState çağrılmadan önce eski duruma erişim" hiçbir zaman sorun olmaz " - yanılıyorsunuz. Eriştiğinizde eski (veya daha ziyade kuyruğa alınmış güncellemeyi bekleyen) olabilecek üzerine yazma durumundan bahsediyoruz this.state.
Madbreaks

1
@Madbreaks Tamam, evet, resmi dokümanları yeniden okuduktan sonra durumu önceki duruma göre güvenilir bir şekilde güncellemek için setState işlev formunu kullanmanız gerektiğini belirtiyorum. Bununla birlikte, setState'i aynı işlevde birden çok kez çağırmaya çalışmazsanız bugüne kadar bir sorunla karşılaşmadım. Teknik özellikleri tekrar okumamı sağlayan yanıtlar için teşekkürler.
nextgentech

10

Başka bir kütüphane kurmak istemedim, işte başka bir çözüm.

Onun yerine:

this.setState({ selected: { name: 'Barfoo' }});

Bunun yerine şunu yapın:

var newSelected = Object.assign({}, this.state.selected);
newSelected.name = 'Barfoo';
this.setState({ selected: newSelected });

Veya yorumlardaki @ icc97 sayesinde daha da özlü ama tartışmasız daha az okunabilir:

this.setState({ selected: Object.assign({}, this.state.selected, { name: "Barfoo" }) });

Ayrıca, açıkçası, bu cevap @bgannonpl'in yukarıda belirtilen endişelerini ihlal etmemektedir.


Olumlu oy, kontrol et. Sadece neden böyle yapmadığını merak ediyorsun? this.setState({ selected: Object.assign(this.state.selected, { name: "Barfoo" }));
Justus Romijn

1
@JustusRomijn Ne yazık ki mevcut durumu doğrudan değiştirirsiniz. Object.assign(a, b)öznitelikleri alır b, ekler / üzerine yazar ave sonra döndürür a. Durumu doğrudan değiştirirseniz, insanların tepkileri artar.
Ryan Shillington

Kesinlikle. Bunun yalnızca sığ kopyalama / atama için çalıştığını unutmayın.
Madbreaks

1
Sen başına bunu yapabilirsiniz @JustusRomijn MDN assign bir satırda eklerseniz {}ilk argüman olarak: this.setState({ selected: Object.assign({}, this.state.selected, { name: "Barfoo" }) });. Bu orijinal durumu değiştirmez.
icc97

@ icc97 Güzel! Bunu cevabıma ekledim.
Ryan Shillington

8

@Bgannonpl cevabına göre önceki durumu koruma:

Lodash örneği:

this.setState((previousState) => _.merge({}, previousState, { selected: { name: "Barfood"} }));

Düzgün çalışıp çalışmadığını kontrol etmek için ikinci parametre işlevi geri çağırma işlevini kullanabilirsiniz:

this.setState((previousState) => _.merge({}, previousState, { selected: { name: "Barfood"} }), () => alert(this.state.selected));

Aksi takdirde diğer özellikleri atar mergeçünkü kullandım extend.

Reaksiyon Değişmezliği örneği:

import update from "react-addons-update";

this.setState((previousState) => update(previousState, {
    selected:
    { 
        name: {$set: "Barfood"}
    }
});

2

Bu tür bir durum için çözümüm , işaret edilen başka bir cevap gibi , Immutability yardımcılarını kullanmaktır .

Durumu derinlemesine ayarlamak yaygın bir durum olduğundan, şu karışımı oluşturdum:

var SeStateInDepthMixin = {
   setStateInDepth: function(updatePath) {
       this.setState(React.addons.update(this.state, updatePath););
   }
};

Bu karışım çoğu bileşenime dahil edildi ve genellikle setStatedoğrudan kullanmıyorum.

Bu mixin ile, istenen efekti elde etmek için yapmanız gereken tek şey işlevi setStateinDepthaşağıdaki şekilde çağırmaktır :

setStateInDepth({ selected: { name: { $set: 'Barfoo' }}})

Daha fazla bilgi için:


Geç cevap için özür dilerim, ancak Mixin örneğiniz ilginç görünüyor. Ancak, 2 iç içe durumum varsa, örneğin this.state.test.one ve this.state.test.two. Bunlardan sadece birinin güncelleneceği ve diğerinin silineceği doğru mu? Birincisini güncellediğimde, ikincisi devletteysem kaldırılacak ...
mamruoc

hy @mamruoc, kullanarak this.state.test.twogüncelleme yaptıktan sonra hala orada olacaktır . React sınırlı işlevini almak için tüm bileşenleri benim bu kodu kullanın . this.state.test.onesetStateInDepth({test: {one: {$set: 'new-value'}}})setState
Dorian

1
Çözümü buldum. this.state.testDizi olarak başladım . Nesneleri değiştirmek, çözdü.
mamruoc

2

Es6 sınıfları kullanıyorum ve üst durumumda birkaç karmaşık nesne ile sona erdi ve ana bileşenimi daha modüler hale getirmeye çalışıyordum, bu yüzden durumu üst bileşende tutmak için basit bir sınıf sarıcısı oluşturdum, ancak daha yerel mantığa izin verdim .

Wrapper sınıfı, yapıcı olarak bir işlevi, ana bileşen durumunda bir özellik ayarlar.

export default class StateWrapper {

    constructor(setState, initialProps = []) {
        this.setState = props => {
            this.state = {...this.state, ...props}
            setState(this.state)
        }
        this.props = initialProps
    }

    render() {
        return(<div>render() not defined</div>)
    }

    component = props => {
        this.props = {...this.props, ...props}
        return this.render()
    }
}

Sonra üst durumdaki her karmaşık özellik için, bir StateWrapped sınıfı oluşturun. Burada yapıcıdaki varsayılan sahne özelliklerini ayarlayabilirsiniz ve bunlar sınıf başlatıldığında ayarlanır, değerler için yerel duruma başvurabilir ve yerel durumu ayarlayabilir, yerel işlevlere başvurabilir ve zinciri geçmesini sağlayabilirsiniz:

class WrappedFoo extends StateWrapper {

    constructor(...props) { 
        super(...props)
        this.state = {foo: "bar"}
    }

    render = () => <div onClick={this.props.onClick||this.onClick}>{this.state.foo}</div>

    onClick = () => this.setState({foo: "baz"})


}

Bu nedenle, üst düzey bileşenim, her sınıfı üst düzey durum özelliğine, basit bir render'e ve çapraz bileşenle iletişim kuran tüm işlevlere ayarlamak için yapıcıya ihtiyaç duyar.

class TopComponent extends React.Component {

    constructor(...props) {
        super(...props)

        this.foo = new WrappedFoo(
            props => this.setState({
                fooProps: props
            }) 
        )

        this.foo2 = new WrappedFoo(
            props => this.setState({
                foo2Props: props
            }) 
        )

        this.state = {
            fooProps: this.foo.state,
            foo2Props: this.foo.state,
        }

    }

    render() {
        return(
            <div>
                <this.foo.component onClick={this.onClickFoo} />
                <this.foo2.component />
            </div>
        )
    }

    onClickFoo = () => this.foo2.setState({foo: "foo changed foo2!"})
}

Benim amacım için oldukça iyi çalışıyor gibi görünüyor, her bir sarılmış bileşen kendi durumunu izliyor, ancak üst bileşendeki durumu güncellediğinden, üst düzey bileşende sarılmış bileşenlere atadığınız özelliklerin durumunu değiştiremeyeceğinizi unutmayın. her değiştiğinde.


2

Şu an itibariyle,

Sonraki durum önceki duruma bağlıysa, bunun yerine güncelleyici işlev formunu kullanmanızı öneririz:

https://reactjs.org/docs/react-component.html#setstate belgelerine göre , aşağıdakileri kullanarak:

this.setState((prevState) => {
    return {quantity: prevState.quantity + 1};
});

Diğer cevaplardan eksik olan alıntı / bağlantı için teşekkürler.
Madbreaks

1

Çözüm

Düzenleme: Bu çözüm forma sözdizimini kullanmak için kullanılır . Amaç, herhangi bir referansı olmayan bir nesne yapmaktı prevState, bu yüzden prevStatedeğiştirilmeyecekti. Ama benim kullanımımda, prevStatebazen değiştirilmiş gibi görünüyordu. Yani, yan etkileri olmayan mükemmel klonlama için, şimdi prevStateJSON'a dönüştükten sonra tekrar geri dönüyoruz. (İlham JSON geldi kullanmak MDN'yi .)

Hatırlamak:

adımlar

  1. Kök düzeyi özelliğinin bir kopyasını oluşturun .state sen değişikliği istediğini
  2. Bu yeni nesneyi değiştir
  3. Güncelleme nesnesi oluşturma
  4. Güncellemeyi iade et

Adım 3 ve 4 bir satırda birleştirilebilir.

Misal

this.setState(prevState => {
    var newSelected = JSON.parse(JSON.stringify(prevState.selected)) //1
    newSelected.name = 'Barfoo'; //2
    var update = { selected: newSelected }; //3
    return update; //4
});

Basitleştirilmiş örnek:

this.setState(prevState => {
    var selected = JSON.parse(JSON.stringify(prevState.selected)) //1
    selected.name = 'Barfoo'; //2
    return { selected }; //3, 4
});

Bu, React kurallarını güzelce izler. Eicksl'in benzer bir soruya verdiği cevaba dayanarak .


1

ES6 çözümü

Başlangıçta devleti kurduk

this.setState({ selected: { id: 1, name: 'Foobar' } }); 
//this.state: { selected: { id: 1, name: 'Foobar' } }

Durum nesnesinin bir düzeyindeki bir özelliği değiştiriyoruz:

const { selected: _selected } = this.state
const  selected = { ..._selected, name: 'Barfoo' }
this.setState({selected})
//this.state: { selected: { id: 1, name: 'Barfoo' } }

0

Tepki durumu, yinelemeli birleşmeyi gerçekleştirmez setStateAynı anda yerinde durum üyesi güncelleştirmelerinin olmayacağını beklerken, . Ekteki nesneleri / dizileri kendiniz kopyalamanız (array.slice veya Object.assign ile) veya ayrılmış kitaplığı kullanmanız gerekir.

Bunun gibi. NestedLink doğrudan bileşik Reaksiyon durumunun işlenmesini destekler.

this.linkAt( 'selected' ).at( 'name' ).set( 'Barfoo' );

Ayrıca bağlantı selectedveya selected.nametek pervane gibi her yerde geçirilebilir ve orada modifiye set.


-2

başlangıç ​​durumunu ayarladın mı?

Kendi kodumu kullanacağım örneğin:

    getInitialState: function () {
        return {
            dragPosition: {
                top  : 0,
                left : 0
            },
            editValue : "",
            dragging  : false,
            editing   : false
        };
    }

Üzerinde çalıştığım bir uygulamada, durumu bu şekilde ayarlayıp kullanıyorum. Ben setStatedaha sonra tek tek istediğiniz gibi devletleri düzenleyebilirsiniz inanıyorum ben böyle çağırıyorum:

    onChange: function (event) {
        event.preventDefault();
        event.stopPropagation();
        this.setState({editValue: event.target.value});
    },

Durumu, aradığınız React.createClassişlev içinde ayarlamanız gerektiğini unutmayıngetInitialState


1
bileşeninizde hangi karışımı kullanıyorsunuz? Bu benim için çalışmıyor
Sachin

-3

Değiştirmek için tmp var kullanıyorum.

changeTheme(v) {
    let tmp = this.state.tableData
    tmp.theme = v
    this.setState({
        tableData : tmp
    })
}

2
Burada tmp.theme = v mutasyon durumudur. Yani önerilen yol bu değil.
almoraleslopez

1
let original = {a:1}; let tmp = original; tmp.a = 5; // original is now === Object {a: 5} Henüz size sorun vermemiş olsa bile bunu yapmayın.
Chris
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.