ตอบโต้: จะอัพเดท state.item [1] ให้อยู่ในสถานะใช้ setState ได้อย่างไร?


259

ฉันกำลังสร้างแอพที่ผู้ใช้สามารถออกแบบฟอร์มของตัวเองได้ เช่นระบุชื่อของเขตข้อมูลและรายละเอียดของคอลัมน์อื่น ๆ ที่ควรจะรวม

องค์ประกอบที่สามารถใช้ได้เป็น JSFiddle ที่นี่

สถานะเริ่มต้นของฉันมีลักษณะเช่นนี้:

var DynamicForm = React.createClass({
  getInitialState: function() {
   var items = {};
   items[1] = { name: 'field 1', populate_at: 'web_start',
                same_as: 'customer_name',
                autocomplete_from: 'customer_name', title: '' };
   items[2] = { name: 'field 2', populate_at: 'web_end',
                same_as: 'user_name', 
                    autocomplete_from: 'user_name', title: '' };

     return { items };
   },

  render: function() {
     var _this = this;
     return (
       <div>
         { Object.keys(this.state.items).map(function (key) {
           var item = _this.state.items[key];
           return (
             <div>
               <PopulateAtCheckboxes this={this}
                 checked={item.populate_at} id={key} 
                   populate_at={data.populate_at} />
            </div>
            );
        }, this)}
        <button onClick={this.newFieldEntry}>Create a new field</button>
        <button onClick={this.saveAndContinue}>Save and Continue</button>
      </div>
    );
  }

ฉันต้องการอัปเดตสถานะเมื่อผู้ใช้เปลี่ยนค่าใด ๆ แต่ฉันมีเวลายากที่จะกำหนดเป้าหมายวัตถุที่ถูกต้อง:

var PopulateAtCheckboxes = React.createClass({
  handleChange: function (e) {
     item = this.state.items[1];
     item.name = 'newName';
     items[1] = item;
     this.setState({items: items});
  },
  render: function() {
    var populateAtCheckbox = this.props.populate_at.map(function(value) {
      return (
        <label for={value}>
          <input type="radio" name={'populate_at'+this.props.id} value={value}
            onChange={this.handleChange} checked={this.props.checked == value}
            ref="populate-at"/>
          {value}
        </label>
      );
    }, this);
    return (
      <div className="populate-at-checkboxes">
        {populateAtCheckbox}
      </div>
    );
  }
});

ฉันจะสร้างthis.setStateเพื่ออัปเดตได้items[1].nameอย่างไร



1
คำตอบที่คุณเลือกสำหรับคำถามนี้คืออะไร
Braian Mellor

คำตอบ:


126

คุณสามารถใช้updateผู้ช่วยที่ไม่สามารถเปลี่ยนแปลงได้สำหรับสิ่งนี้ :

this.setState({
  items: update(this.state.items, {1: {name: {$set: 'updated field name'}}})
})

หรือหากคุณไม่สนใจเกี่ยวกับความสามารถในการตรวจจับการเปลี่ยนแปลงของรายการนี้ในshouldComponentUpdate()วิธีวงจรชีวิตโดยใช้===คุณสามารถแก้ไขสถานะได้โดยตรงและบังคับให้ส่วนประกอบแสดงผลอีกครั้ง - นี่เป็นคำตอบเดียวกับ @limelights 'อย่างมีประสิทธิภาพ ดึงวัตถุออกจากสถานะและแก้ไขมัน

this.state.items[1].name = 'updated field name'
this.forceUpdate()

การโพสต์แก้ไขเพิ่มเติม:

ตรวจสอบบทเรียนSimple Component Communicationจากการฝึกอบรมตอบโต้เพื่อดูตัวอย่างวิธีส่งผ่านฟังก์ชั่นการโทรกลับจากผู้ปกครองที่มีสถานะเป็นผู้ปกครองไปยังองค์ประกอบย่อยที่ต้องการกระตุ้นการเปลี่ยนแปลงสถานะ


125

เนื่องจากมีข้อมูลที่ผิดจำนวนมากในชุดข้อความนี้นี่เป็นวิธีที่คุณสามารถทำได้โดยไม่ต้องใช้ตัวช่วย libs:

handleChange: function (e) {
    // 1. Make a shallow copy of the items
    let items = [...this.state.items];
    // 2. Make a shallow copy of the item you want to mutate
    let item = {...items[1]};
    // 3. Replace the property you're intested in
    item.name = 'newName';
    // 4. Put it back into our array. N.B. we *are* mutating the array here, but that's why we made a copy first
    items[1] = item;
    // 5. Set the state to our new copy
    this.setState({items});
},

คุณสามารถรวมขั้นตอนที่ 2 และ 3 หากคุณต้องการ:

let item = {
    ...items[1],
    name: 'newName'
}

หรือคุณสามารถทำสิ่งทั้งหมดในหนึ่งบรรทัด:

this.setState(({items}) => ({
    items: [
        ...items.slice(0,1),
        {
            ...items[1],
            name: 'newName',
        },
        ...items.slice(2)
    ]
}));

หมายเหตุ: ฉันทำitemsอาร์เรย์ OP ใช้วัตถุ อย่างไรก็ตามแนวคิดก็เหมือนกัน


คุณสามารถเห็นสิ่งที่เกิดขึ้นใน terminal / console ของคุณ:

 node
> items = [{name:'foo'},{name:'bar'},{name:'baz'}]
[ { name: 'foo' }, { name: 'bar' }, { name: 'baz' } ]
> clone = [...items]
[ { name: 'foo' }, { name: 'bar' }, { name: 'baz' } ]
> item1 = {...clone[1]}
{ name: 'bar' }
> item1.name = 'bacon'
'bacon'
> clone[1] = item1
{ name: 'bacon' }
> clone
[ { name: 'foo' }, { name: 'bacon' }, { name: 'baz' } ]
> items
[ { name: 'foo' }, { name: 'bar' }, { name: 'baz' } ] // good! we didn't mutate `items`
> items === clone
false // these are different objects
> items[0] === clone[0]
true // we don't need to clone items 0 and 2 because we're not mutating them (efficiency gains!)
> items[1] === clone[1]
false // this guy we copied

4
@TranslucentCloud Oh yeah, วิธีการช่วยเหลือเป็นมั่นเหมาะดี แต่ผมคิดว่าทุกคนควรจะรู้ว่าสิ่งที่เกิดขึ้นภายใต้ประทุน :-)
mpen

ทำไมเราต้องมีขั้นตอนที่ 2 และ 3 ทำไมเราไม่สามารถกลายพันธุ์โคลนหลังจากขั้นตอนแรกโดยตรง?
Evmorov

1
@Evmorov เพราะไม่ใช่การโคลนนิ่งที่ลึก ขั้นตอนที่ 1 เป็นเพียงการโคลนอาร์เรย์ไม่ใช่วัตถุภายใน ในคำอื่น ๆ แต่ละวัตถุภายในอาร์เรย์ใหม่ยังคง "ชี้ไปที่" วัตถุที่มีอยู่ในหน่วยความจำ - การกลายพันธุ์หนึ่งจะกลายพันธุ์อื่น ๆ (พวกเขาเป็นหนึ่งและเหมือนกัน) ดูitems[0] === clone[0]บิตในตัวอย่างเทอร์มินัลของฉันที่ด้านล่าง สาม=ตรวจสอบว่าวัตถุที่อ้างถึงในสิ่งเดียวกัน
mpen

มีความซับซ้อนมากขึ้นถ้าคุณไม่รู้จักดัชนีของรายการในอาร์เรย์
Ian Warburton

@IanWarburton ไม่ได้จริงๆ items.findIndex()ควรทำให้งานสั้นลง
mpen

92

ทางที่ผิด!

handleChange = (e) => {
    const { items } = this.state;
    items[1].name = e.target.value;

    // update state
    this.setState({
        items,
    });
};

ตามที่ระบุโดยนักพัฒนาที่ดีขึ้นมากมายในความคิดเห็น: กลายพันธุ์ของรัฐนั้นผิด!

เอาฉันไปคิดดู งานด้านบน แต่ใช้พลังของ React ตัวอย่างเช่นcomponentDidUpdateจะไม่เห็นสิ่งนี้เป็นการอัปเดตเนื่องจากมีการแก้ไขโดยตรง

ดังนั้นทางที่ถูกต้องคือ:

handleChange = (e) => {
    this.setState(prevState => ({
        items: {
            ...prevState.items,
            [prevState.items[1].name]: e.target.value,
        },
    }));
};

25
ES6 เพียงเพราะคุณใช้ "const"?
nkkollaw

49
ไม่เรียกitems[1].role = e.target.valueสถานะการกลายพันธุ์โดยตรงหรือไม่
antony

28
คุณกำลังกลายพันธุ์ของรัฐนี่เป็นความคิดที่ขัดแย้งกับการรักษาสถานะที่ไม่เปลี่ยนรูปแบบอย่างที่แนะนำ นี่จะทำให้คุณเจ็บปวดมากในแอปพลิเคชั่นขนาดใหญ่
ncubica

8
@MarvinVK คำตอบของคุณบอกว่า "ดังนั้นวิธีปฏิบัติที่ดีที่สุดคือ:" ตามด้วยการใช้ "this.forceUpdate ();" ซึ่งไม่แนะนำให้เป็นมันอาจจะถูกเขียนทับโดย setState () ดูfacebook.github.io/react/docs/react-component.html#state ดีที่สุดที่จะเปลี่ยนแปลงสิ่งนี้ดังนั้นจึงไม่สับสนกับผู้อ่านในอนาคต
James Z.

8
แค่ต้องการชี้ให้เห็นว่ามีความคิดเห็นจำนวนมากที่ไม่เกี่ยวข้องตอนนี้เนื่องจากการแก้ไขและวิธีการที่ถูกต้องเป็นวิธีที่ถูกต้อง
heez

50

ในการปรับเปลี่ยนวัตถุ / ตัวแปรที่ซ้อนกันอย่างลึกซึ้งในสถานะของ React โดยทั่วไปจะใช้วิธีการสามวิธี: vanilla JavaScript's Object.assign, immutability-helperและcloneDeepจากLodash Lodash

นอกจากนี้ยังมี libs บุคคลที่สามที่ได้รับความนิยมน้อยกว่าอื่น ๆ มากมายเพื่อให้บรรลุเป้าหมายนี้ แต่ในคำตอบนี้ฉันจะกล่าวถึงตัวเลือกทั้งสามนี้ นอกจากนี้ยังมีวิธีการบางอย่างเพิ่มเติมของ vanilla JavaScript เช่นการกระจายอาร์เรย์ (ดูคำตอบของ @ mpen ตัวอย่าง) แต่มันไม่ง่ายนักใช้งานง่ายและสามารถจัดการกับสถานการณ์การจัดการสถานะทั้งหมด

ตามที่ได้ชี้ไปครั้งนับไม่ถ้วนในการโหวตความคิดเห็นด้านบนเพื่อตอบซึ่งผู้เขียนเสนอการกลายพันธุ์โดยตรงของรัฐ: เพียงแค่ไม่ทำอย่างนั้นก็ไม่ได้ทำอย่างนั้นนี่คือรูปแบบการต่อต้านปฏิกิริยาที่แพร่หลายซึ่งจะนำไปสู่ผลลัพธ์ที่ไม่พึงประสงค์อย่างหลีกเลี่ยงไม่ได้ เรียนรู้วิธีการที่เหมาะสม

ลองเปรียบเทียบสามวิธีที่ใช้กันอย่างแพร่หลาย

รับโครงสร้างวัตถุสถานะนี้:

state = {
    outer: {
        inner: 'initial value'
    }
}

คุณสามารถใช้วิธีการต่อไปนี้เพื่ออัปเดตส่วนใหญ่ innerค่าของเขตข้อมูลโดยไม่ส่งผลกระทบต่อส่วนที่เหลือของรัฐ

1. Object.assign ของ Vanilla JavaScript

const App = () => {
  const [outer, setOuter] = React.useState({ inner: 'initial value' })

  React.useEffect(() => {
    console.log('Before the shallow copying:', outer.inner) // initial value
    const newOuter = Object.assign({}, outer, { inner: 'updated value' })
    console.log('After the shallow copy is taken, the value in the state is still:', outer.inner) // initial value
    setOuter(newOuter)
  }, [])

  console.log('In render:', outer.inner)

  return (
    <section>Inner property: <i>{outer.inner}</i></section>
  )
}

ReactDOM.render(
  <App />,
  document.getElementById('react')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>

<main id="react"></main>

โปรดทราบว่าObject.assign จะไม่ทำการโคลนนิ่งอย่างลึกล้ำเนื่องจากคัดลอกเฉพาะค่าคุณสมบัติและนั่นคือสาเหตุที่สิ่งที่เรียกว่าการคัดลอกตื้น (ดูความคิดเห็น)

สำหรับการทำงานเราควรจัดการคุณสมบัติของดั้งเดิมชนิด ( outer.inner) นั่นคือสตริงตัวเลขบูลีน

ในตัวอย่างนี้เรากำลังสร้างค่าคงที่ใหม่ ( const newOuter...) โดยใช้Object.assignซึ่งสร้างวัตถุว่างเปล่า ( {}) คัดลอกouterวัตถุ ( { inner: 'initial value' }) ลงในมันแล้วคัดลอกวัตถุอื่น{ inner: 'updated value' } ไปมัน

วิธีนี้ในที่สุดnewOuterค่าคงที่ที่สร้างขึ้นใหม่จะเก็บค่า{ inner: 'updated value' }ตั้งแต่innerคุณสมบัติถูกแทนที่ นี้newOuterเป็นวัตถุใหม่ที่ไม่ได้เชื่อมโยงกับวัตถุในสถานะดังนั้นจึงสามารถกลายพันธุ์ได้ตามต้องการและสถานะจะยังคงเหมือนเดิมและจะไม่เปลี่ยนแปลงจนกว่าจะมีการรันคำสั่งเพื่ออัปเดต

ส่วนสุดท้ายคือการใช้setOuter()setter เพื่อแทนที่ต้นฉบับouterในสถานะด้วยnewOuterวัตถุที่สร้างขึ้นใหม่(เฉพาะค่าจะเปลี่ยนชื่อคุณสมบัติouterจะไม่)

state = { outer: { inner: { innerMost: 'initial value' } } }ตอนนี้คิดว่าเรามีสถานะลึกมากขึ้นเช่น เราสามารถลองสร้างnewOuterวัตถุและเติมด้วยouterเนื้อหาจากสถานะ แต่Object.assignจะไม่สามารถคัดลอกinnerMostค่าของnewOuterวัตถุที่สร้างขึ้นใหม่นี้ตั้งแต่innerMostซ้อนกันลึกเกินไป

คุณยังคงสามารถคัดลอกinnerได้เช่นในตัวอย่างด้านบน แต่เนื่องจากตอนนี้มันเป็นวัตถุไม่ใช่แบบดั้งเดิมการอ้างอิงจากnewOuter.innerจะถูกคัดลอกไปที่outer.innerแทนซึ่งหมายความว่าเราจะจบลงด้วยท้องถิ่นnewOuterวัตถุเชื่อมโยงกับวัตถุในสถานะโดยตรง .

ซึ่งหมายความว่าในกรณีนี้การกลายพันธุ์ของสิ่งที่สร้างขึ้นในท้องถิ่นnewOuter.innerจะส่งผลโดยตรงต่อouter.innerวัตถุ (ในสถานะ) เนื่องจากในความเป็นจริงแล้วพวกมันกลายเป็นสิ่งเดียวกัน (ในหน่วยความจำของคอมพิวเตอร์)

Object.assign ดังนั้นจะทำงานได้ก็ต่อเมื่อคุณมีโครงสร้างสถานะระดับลึกที่เรียบง่ายในระดับเดียวกับสมาชิกที่อยู่ด้านในสุดที่มีค่าประเภทดั้งเดิม

ถ้าคุณมีวัตถุลึก (ระดับ 2 หรือมากกว่า) Object.assignซึ่งคุณควรปรับปรุงไม่ได้ใช้ คุณเสี่ยงต่อการกลายพันธุ์รัฐโดยตรง

2. โคลนของ Lodash ลึก

const App = () => {
  const [outer, setOuter] = React.useState({ inner: 'initial value' })

  React.useEffect(() => {
    console.log('Before the deep cloning:', outer.inner) // initial value
    const newOuter = _.cloneDeep(outer) // cloneDeep() is coming from the Lodash lib
    newOuter.inner = 'updated value'
    console.log('After the deeply cloned object is modified, the value in the state is still:', outer.inner) // initial value
    setOuter(newOuter)
  }, [])

  console.log('In render:', outer.inner)

  return (
    <section>Inner property: <i>{outer.inner}</i></section>
  )
}

ReactDOM.render(
  <App />,
  document.getElementById('react')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>

<main id="react"></main>

โคลนของLodashนั้นใช้ง่ายกว่ามาก มันทำการโคลนนิ่งที่ลึกดังนั้นจึงเป็นตัวเลือกที่มีประสิทธิภาพหากคุณมีสถานะที่ค่อนข้างซับซ้อนที่มีวัตถุหลายระดับหรืออาร์เรย์ภายใน เพียงแค่cloneDeep()คุณสมบัติของรัฐระดับบนสุดกลายพันธุ์ส่วนโคลนในสิ่งที่คุณต้องการและsetOuter()มันกลับไปที่รัฐ

3. การเปลี่ยนไม่ได้ - ผู้ช่วย

const App = () => {
  const [outer, setOuter] = React.useState({ inner: 'initial value' })
  
  React.useEffect(() => {
    const update = immutabilityHelper
    console.log('Before the deep cloning and updating:', outer.inner) // initial value
    const newOuter = update(outer, { inner: { $set: 'updated value' } })
    console.log('After the cloning and updating, the value in the state is still:', outer.inner) // initial value
    setOuter(newOuter)
  }, [])

  console.log('In render:', outer.inner)

  return (
    <section>Inner property: <i>{outer.inner}</i></section>
  )
}

ReactDOM.render(
  <App />,
  document.getElementById('react')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
<script src="https://wzrd.in/standalone/immutability-helper@3.0.0"></script>

<main id="react"></main>

immutability-helperนำมันไปสู่ระดับใหม่ทั้งหมดและสิ่งที่ยอดเยี่ยมเกี่ยวกับมันคือมันไม่เพียง แต่$setสามารถให้คุณค่ากับรายการไอเท็ม แต่ยัง$pushรวม$spliceถึง$merge(ฯลฯ ) นี่คือรายการคำสั่งที่มีให้

หมายเหตุด้านข้าง

โปรดจำไว้ว่าsetOuterเพียงปรับเปลี่ยนคุณสมบัติระดับแรกของออบเจ็กต์ของรัฐ ( outerในตัวอย่างเหล่านี้) ไม่ใช่การซ้อนแบบเชิงลึก (outer.inner ) หากมันทำงานในลักษณะที่แตกต่างกันคำถามนี้จะไม่มีอยู่จริง

เป็นที่หนึ่งที่เหมาะสมสำหรับโครงการของคุณ?

หากคุณไม่ต้องการหรือไม่สามารถใช้อ้างอิงภายนอกและมีโครงสร้างของรัฐที่เรียบง่าย , Object.assignติด

หากคุณจัดการกับรัฐที่มีขนาดใหญ่และ / หรือซับซ้อนซับซ้อน Lodash's cloneDeepเป็นตัวเลือกที่ชาญฉลาด

หากคุณต้องการความสามารถขั้นสูงเช่นหากโครงสร้างรัฐของคุณซับซ้อนและคุณต้องดำเนินการทุกอย่างกับมันลองimmutability-helperมันเป็นเครื่องมือขั้นสูงที่สามารถใช้สำหรับการจัดการสถานะ

... หรือคุณต้องการทำสิ่งนี้จริง ๆหรือ?

หากคุณเก็บข้อมูลที่ซับซ้อนในสถานะของ React บางทีนี่อาจเป็นเวลาที่ดีที่คุณจะคิดถึงวิธีการจัดการอื่น ๆ การตั้งค่าสถานะของวัตถุที่ซับซ้อนในส่วนประกอบของ React ไม่ใช่การดำเนินการที่ตรงไปตรงมาและฉันขอแนะนำให้คิดถึงวิธีการที่แตกต่างกัน

เป็นไปได้ว่าคุณควรเก็บข้อมูลที่ซับซ้อนของคุณในร้าน Redux ให้ดีขึ้นควรตั้งค่าไว้ที่นั่นโดยใช้ reducers และ / หรือ sagas


Object.assignไม่ได้ทำสำเนาลึก พูดถ้าหากa = {c: {d: 1}}และb = Object.assign({}, a)จากนั้นคุณดำเนินการb.c.d = 4แล้วก็a.c.dจะกลายพันธุ์
Awol

คุณถูกต้องค่า1ของวัตถุในสุด ( a.c.d) จะกลายพันธุ์ แต่ถ้าคุณจะมอบหมายผู้สืบทอดตำแหน่งระดับต่อbไปเช่นนี้: b.c = {f: 1}ส่วนที่เกี่ยวข้องของaจะไม่ถูกทำให้กลายพันธุ์ (มันจะยังคงอยู่{d: 1}) เยี่ยมมากแล้วฉันจะอัปเดตคำตอบทันที
Neurotransmitter

สิ่งที่คุณได้กำหนดไว้เป็นจริงและไม่ได้เป็นshallow copy deep copyง่ายที่จะสับสนในสิ่งที่shallow copyหมายถึง ในshallow copy, a !== bแต่ที่สำคัญจากวัตถุแต่ละแหล่งa,a[key] === b[key]
แหกคุก

ใช่ความตื้นเขินกล่าวถึงอย่างชัดเจนObject.assignในคำตอบ
สารสื่อประสาท

JSON.parse(JSON.stringify(object))ยังเป็นตัวแปรสำหรับโคลนลึก ประสิทธิภาพการทำงานที่เลวร้ายแล้ว lodash cloneDeepแม้ว่า measurethat.net/Benchmarks/Show/2751/0/...
tylik

35

ผมมีปัญหาเหมือนกัน. นี่เป็นวิธีง่ายๆที่ใช้งานได้!

const newItems = [...this.state.items];
newItems[item] = value;
this.setState({ items:newItems });

11
@TranslucentCloud - นี่แน่นอนที่สุดไม่ใช่การกลายพันธุ์โดยตรง อาร์เรย์ดั้งเดิมถูกโคลนแก้ไขและจากนั้นตั้งค่าสถานะอีกครั้งโดยใช้ Array ที่โคลน
vsync

@vsync ใช่แล้วหลังจากที่คุณแก้ไขคำตอบเดิมนี่ไม่ใช่การกลายพันธุ์เลย
สารสื่อประสาท

2
@TranslucentCloud - ก่อนหน้านี้การแก้ไขของฉันไม่เกี่ยวข้องกับเรื่องนี้ขอบคุณมากสำหรับทัศนคติ @ Jonas ที่นี่เพียงทำผิดพลาดง่ายในคำตอบของเขาใช้{วงเล็บปีกกาแทนวงเล็บซึ่งฉันได้แก้ไข
vsync

31

ตามเอกสารของ React ในsetState การใช้Object.assignตามคำแนะนำโดยคำตอบอื่น ๆ ที่นี่ไม่เหมาะ เนื่องจากลักษณะของsetStateการทำงานแบบอะซิงโครนัสการโทรที่ตามมาโดยใช้เทคนิคนี้อาจแทนที่การโทรก่อนหน้าซึ่งทำให้เกิดผลลัพธ์ที่ไม่พึงประสงค์

เอกสารตอบกลับขอแนะนำให้ใช้รูปแบบตัวอัปเดตsetStateซึ่งดำเนินการกับสถานะก่อนหน้านี้แทน โปรดทราบว่าเมื่ออัปเดตอาร์เรย์หรือวัตถุคุณต้องส่งคืนอาร์เรย์หรือวัตถุใหม่เนื่องจาก React ต้องการให้เรารักษาสถานะที่ไม่สามารถเปลี่ยนแปลงได้ การใช้โอเปอเรเตอร์การแพร่กระจายของไวยากรณ์ ES6 เพื่อคัดลอกอาเรย์ที่ตื้นสร้างหรือปรับปรุงคุณสมบัติของวัตถุที่ดัชนีที่กำหนดของอาเรย์จะมีลักษณะดังนี้:

this.setState(prevState => {
    const newItems = [...prevState.items];
    newItems[index].name = newName;
    return {items: newItems};
})

2
นี่เป็นคำตอบที่เหมาะสมถ้าคุณใช้ ES6 เป็นแบบอินไลน์ที่ @Jonas ตอบ อย่างไรก็ตามเนื่องจากคำอธิบายมันโดดเด่น
Sourabh

ใช่รหัสนี้ทำงานได้อย่างสมบูรณ์ดีและง่ายมาก ..
Shoeb ร์ซา

29

ก่อนได้รับรายการที่คุณต้องการเปลี่ยนสิ่งที่คุณต้องการบนวัตถุนั้นและตั้งค่ากลับในสถานะ วิธีที่คุณใช้สถานะโดยผ่านวัตถุเพียงอย่างเดียวgetInitialStateจะง่ายขึ้นหากคุณใช้วัตถุที่มีคีย์

handleChange: function (e) {
   item = this.state.items[1];
   item.name = 'newName';
   items[1] = item;

   this.setState({items: items});
}

3
Nah Uncaught TypeError: Cannot read property 'items' of nullมันถัวเฉลี่ย
martins

ไม่มันจะไม่ getInitialStateข้อผิดพลาดของคุณส่วนใหญ่มีแนวโน้มเกิดจากวิธีการที่คุณกำลังทำ
Henrik Andersson

10
@HenrikAndersson มีบางอย่างที่คุณมองไม่เห็น itemsไม่ได้กำหนดไว้ทุกที่
Edward D'Souza

1
@ EdwardD'Souza คุณถูกต้องจริงๆ! คำตอบของฉันคือแสดงให้เห็นว่าควรกำหนดและใช้อย่างไร วิธีการตั้งค่ารหัสผู้ถามไม่ทำงานสำหรับสิ่งที่เขา / เธอต้องการซึ่งเป็นสาเหตุที่จำเป็นต้องใช้วัตถุที่มีคีย์
Henrik Andersson

7
นี่คือรูปแบบการต่อต้าน React ทั่วไปคุณกำลังกำหนดการitemอ้างอิงไปยังthis.state.items[1]ตัวแปรของรัฐ จากนั้นคุณแก้ไขitem( item.name = 'newName') และทำให้กลายพันธุ์สถานะโดยตรงซึ่งเป็นกำลังใจอย่างมาก ในตัวอย่างของคุณไม่มีความจำเป็นที่จะต้องโทรthis.setState({items: items})เพราะสถานะจะกลายพันธุ์โดยตรงแล้ว
สารสื่อประสาท

22

อย่ากลายพันธุ์ในสถานที่ มันสามารถทำให้เกิดผลลัพธ์ที่ไม่คาดคิด ฉันได้เรียนรู้บทเรียนของฉัน! ทำงานกับสำเนา / โคลนเสมอObject.assign()เป็นสิ่งที่ดี:

item = Object.assign({}, this.state.items[1], {name: 'newName'});
items[1] = item;
this.setState({items: items});

https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign


8
สิ่งที่อยู่itemsในตัวอย่างของคุณหรือไม่ คุณหมายถึงthis.state.itemsหรืออย่างอื่น?
Buh Buh

ฉันทดสอบสิ่งนี้ แต่เขาหายไปหนึ่งบรรทัด ดังกล่าวข้างต้นควรจะมีคำพูดบรรทัดitems[1] = item; items = this.state.items;ระวังจาวาสคริปต์ของฉันเป็นสนิมและฉันกำลังเรียนรู้ที่จะตอบสนองต่อโครงการบ้านของฉันดังนั้นฉันจึงไม่รู้ว่านี่เป็นสิ่งที่ดีหรือไม่ดี :-)
Greg0ry

4

มันง่ายมาก

ขั้นแรกให้ดึงวัตถุรายการทั้งหมดออกจากสถานะอัปเดตส่วนของวัตถุรายการตามต้องการและนำวัตถุรายการทั้งหมดกลับสู่สถานะผ่าน setState

handleChange: function (e) {
  items = Object.assign(this.state.items); // Pull the entire items object out. Using object.assign is a good idea for objects.
  items[1].name = 'newName'; // update the items object as needed
  this.setState({ items }); // Put back in state
}

"Object.assign จะทำงานหากคุณมีโครงสร้างสถานะระดับลึกที่เรียบง่ายซึ่งมีสมาชิกที่อยู่ด้านในสุดที่ถือค่าชนิดดั้งเดิม"
สารสื่อประสาท

3

เนื่องจากตัวเลือกข้างต้นไม่เหมาะกับฉันฉันจึงตัดสินใจใช้แผนที่:

this.setState({items: this.state.items.map((item,idx)=> idx!==1 ?item :{...item,name:'new_name'}) })

2

การกลายพันธุ์ฟรี:

// given a state
state = {items: [{name: 'Fred', value: 1}, {name: 'Wilma', value: 2}]}

// This will work without mutation as it clones the modified item in the map:
this.state.items
   .map(item => item.name === 'Fred' ? {...item, ...{value: 3}} : item)

this.setState(newItems)

2
ฉันไม่เห็นว่าnewItemsตั้งอยู่ที่ไหน
สารสื่อประสาท

แผนที่และการเปรียบเทียบในอาร์เรย์นั้นน่ากลัวสำหรับประสิทธิภาพหรือไม่
Natassia Tavares

@NatassiaTavares .. อะไรนะ? บางทีคุณอาจสับสนfo ofหรือforEachแผนที่เร็วที่สุด
Deano

1

พบว่ามันยากอย่างน่าประหลาดใจและไม่มีเวทมนตร์แพร่กระจาย ES6 ที่ดูเหมือนจะทำงานได้ตามที่คาดไว้ คือการใช้โครงสร้างเช่นนี้เพื่อรับคุณสมบัติองค์ประกอบการแสดงผลเพื่อวัตถุประสงค์ในการจัดวาง

พบโดยใช้updateวิธีการจากimmutability-helperที่จะตรงไปตรงมามากที่สุดในตัวอย่างที่ง่ายนี้:

constructor(props) {
    super(props)
    this.state = { values: [] }
    this.updateContainerState = this.updateContainerState.bind(this)
  }

updateContainerState(index, value) {
    this.setState((state) => update(state, { values: { [index]: { $set: value } } }))
  }

ดัดแปลงมาจากhttps://github.com/kolodny/immutability-helper#computed-property-names

ของสมาชิกอาเรย์ที่จะถูกอัพเดตเป็นอ็อบเจ็กต์ที่ซับซ้อนซ้อนกันมากขึ้นใช้วิธีการทำสำเนาแบบลึกที่เหมาะสมตามความซับซ้อน

มีวิธีที่ดีกว่าในการจัดการพารามิเตอร์เลย์เอาต์ แต่นี่คือวิธีจัดการกับอาร์เรย์ ค่าที่เกี่ยวข้องสำหรับแต่ละองค์ประกอบลูกสามารถคำนวณนอกองค์ประกอบได้ แต่ฉันพบว่าสะดวกกว่าที่จะส่ง containerState ลงเพื่อให้เด็กสามารถดึงคุณสมบัติตามที่ต้องการและอัปเดตอาร์เรย์สถานะหลักที่ดัชนีที่กำหนด

import React from 'react'
import update from 'immutability-helper'
import { ContainerElement } from './container.component.style.js'
import ChildComponent from './child-component'
export default class ContainerComponent extends React.Component {
  constructor(props) {
    super(props)
    this.state = { values: [] }
    this.updateContainerState = this.updateContainerState.bind(this)
  }

  updateContainerState(index, value) {
    this.setState((state) => update(state, { values: { [index]: { $set: value } } }))
  }

  // ...

  render() {
    let index = 0
    return (
      <ContainerElement>
      <ChildComponent
        index={index++}
        containerState={this.state}
        updateContainerState={this.updateContainerState}
      />
      <ChildComponent
        index={index++}
        containerState={this.state}
        updateContainerState={this.updateContainerState}
      />
      </ContainerElement>
    )
  }
}

1

ใช้อาเรย์แผนที่พร้อมฟังก์ชั่นลูกศรในหนึ่งบรรทัด

this.setState({
    items: this.state.items.map((item, index) =>
      index === 1 ? { ...item, name: 'newName' } : item,
   )
})

1
นี่เป็นพื้นเหมือนกับคำตอบอื่น ๆ ที่โพสต์เมื่อประมาณหนึ่งปีก่อน
CertainPerformance

0

ใช้เหตุการณ์ต่อhandleChangeไปเพื่อหาองค์ประกอบที่มีการเปลี่ยนแปลงแล้วอัปเดต เพื่อที่คุณอาจจะต้องเปลี่ยนสถานที่ให้บริการเพื่อระบุและปรับปรุง

ดูซอที่https://jsfiddle.net/69z2wepo/6164/


0

ฉันจะย้ายการเปลี่ยนแปลงการจัดการฟังก์ชั่นและเพิ่มพารามิเตอร์ดัชนี

handleChange: function (index) {
    var items = this.state.items;
    items[index].name = 'newName';
    this.setState({items: items});
},

ไปยังองค์ประกอบรูปแบบไดนามิกและส่งผ่านไปยังองค์ประกอบ PopulateAtCheckboxes เป็นเสา ในขณะที่คุณวนรายการของคุณคุณสามารถรวมตัวนับเพิ่มเติม (เรียกว่าดัชนีในรหัสด้านล่าง) เพื่อส่งต่อไปยังการเปลี่ยนแปลงหมายเลขอ้างอิงดังที่แสดงด้านล่าง

{ Object.keys(this.state.items).map(function (key, index) {
var item = _this.state.items[key];
var boundHandleChange = _this.handleChange.bind(_this, index);
  return (
    <div>
        <PopulateAtCheckboxes this={this}
            checked={item.populate_at} id={key} 
            handleChange={boundHandleChange}
            populate_at={data.populate_at} />
    </div>
);
}, this)}

ในที่สุดคุณสามารถเรียกผู้ฟังการเปลี่ยนแปลงของคุณตามที่แสดงด้านล่าง

<input type="radio" name={'populate_at'+this.props.id} value={value} onChange={this.props.handleChange} checked={this.props.checked == value} ref="populate-at"/>

อย่าเปลี่ยนแปลงสถานะของ React โดยตรง
สารสื่อประสาท

0

หากคุณจำเป็นต้องเปลี่ยนเพียงส่วนหนึ่งของArrayคุณมีองค์ประกอบปฏิกิริยากับรัฐตั้งค่าเป็น

state = {items: [{name: 'red-one', value: 100}, {name: 'green-one', value: 999}]}

เป็นการดีที่สุดที่จะอัปเดตred-oneในArrayดังนี้:

const itemIndex = this.state.items.findIndex(i=> i.name === 'red-one');
const newItems = [
   this.state.items.slice(0, itemIndex),
   {name: 'red-one', value: 666},
   this.state.items.slice(itemIndex)
]

this.setState(newItems)

คือnewArrayอะไร คุณหมายถึงnewItemsอะไร หากคุณทำเช่นนั้นจะไม่ทำให้รัฐมีเพียงหนึ่งรายการหลังจากนั้นหรือไม่
micnil

นี้จะแนะนำคุณสมบัติใหม่ให้newItemsกับstateวัตถุและจะไม่ปรับปรุงitemsคุณสมบัติที่มีอยู่
สารสื่อประสาท

0

หรือถ้าคุณมีรายการที่สร้างขึ้นแบบไดนามิกและคุณไม่รู้จักดัชนี แต่มีรหัสหรือรหัส:

let ItemsCopy = []
let x = this.state.Items.map((entry) =>{

    if(entry.id == 'theIDYoureLookingFor')
    {
        entry.PropertyToChange = 'NewProperty'
    }

    ItemsCopy.push(entry)
})


this.setState({Items:ItemsCopy});


0

รหัสต่อไปนี้เป็นเรื่องง่ายในสมองที่น่าเบื่อของฉัน การลบวัตถุและแทนที่ด้วยสิ่งที่อัปเดต

    var udpateditem = this.state.items.find(function(item) { 
                   return item.name == "field_1" });
    udpateditem.name= "New updated name"                       
    this.setState(prevState => ({                                   
    items:prevState.dl_name_template.filter(function(item) { 
                                    return item.name !== "field_1"}).concat(udpateditem)
    }));

0

วิธีการเกี่ยวกับการสร้างองค์ประกอบอื่น (สำหรับวัตถุที่ต้องเข้าไปในอาร์เรย์) และส่งต่อไปนี้เป็นอุปกรณ์ประกอบฉาก?

  1. องค์ประกอบดัชนี - ดัชนีจะใช้ในการสร้าง / ปรับปรุงในอาร์เรย์
  2. ฟังก์ชั่นการตั้งค่า - ฟังก์ชั่นนี้ใส่ข้อมูลลงในอาร์เรย์ตามดัชนีองค์ประกอบ
<SubObjectForm setData={this.setSubObjectData}                                                            objectIndex={index}/>

ที่นี่ {index} สามารถส่งผ่านตามตำแหน่งที่ใช้ SubObjectForm นี้

และ setSubObjectData สามารถเป็นเช่นนี้

 setSubObjectData: function(index, data){
      var arrayFromParentObject= <retrieve from props or state>;
      var objectInArray= arrayFromParentObject.array[index];
      arrayFromParentObject.array[index] = Object.assign(objectInArray, data);
 }

ใน SubObjectForm สามารถเรียก this.props.setData จากการเปลี่ยนแปลงข้อมูลตามที่ระบุด้านล่าง

<input type="text" name="name" onChange={(e) => this.props.setData(this.props.objectIndex,{name: e.target.value})}/>

0
this.setState({
      items: this.state.items.map((item,index) => {
        if (index === 1) {
          item.name = 'newName';
        }
        return item;
      })
    });

1
นี่ไม่เหมาะสมคุณวนซ้ำทั้งแถวเพื่ออัปเดตเฉพาะรายการที่สองใช่หรือไม่
wscourge

คุณกำลังกลายพันธุ์องค์ประกอบในอาร์เรย์แรกด้วยคุณควรใช้item = Object.assign({}, item, {name: 'newName'});
remram

0

@ คำตอบ Jonnygonanan ทำงานได้อย่างสมบูรณ์แบบ แต่สำหรับตัวแปรสถานะอาร์เรย์เท่านั้น ในกรณีที่ตัวแปรสถานะเป็นเพียงพจนานุกรมเดียวให้ทำตามนี้:

inputChange = input => e => {
    this.setState({
        item: update(this.state.item, {[input]: {$set: e.target.value}})
    })
}

คุณสามารถแทนที่[input]ด้วยชื่อฟิลด์ของพจนานุกรมของคุณและe.target.valueตามค่าของมัน รหัสนี้ทำการปรับปรุงเกี่ยวกับเหตุการณ์การเปลี่ยนแปลงการป้อนข้อมูลของแบบฟอร์มของฉัน



-3
 handleChanges = (value, key) => {
     // clone the current State object
    let cloneObject = _.extend({}, this.state.currentAttribute);
    // key as user.name and value= "ABC" then current attributes have current properties as we changes
    currentAttribute[key] = value;
    // then set the state "currentAttribute" is key and "cloneObject" is changed object.  
    this.setState({currentAttribute: cloneObject});

และเปลี่ยนจากกล่องข้อความเพิ่ม onChange เหตุการณ์

onChange = {
   (event) => {                                                
      this.handleChanges(event.target.value, "title");
   }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.