วิธีอัปเดตคุณสมบัติสถานะซ้อนใน React


390

ฉันกำลังพยายามจัดระเบียบสถานะของฉันโดยใช้คุณสมบัติที่ซ้อนกันเช่นนี้:

this.state = {
   someProperty: {
      flag:true
   }
}

แต่การอัพเดทสถานะเช่นนี้

this.setState({ someProperty.flag: false });

ไม่ทำงาน สิ่งนี้สามารถทำได้อย่างถูกต้อง?


4
คุณหมายถึงอะไรไม่ทำงาน นี่ไม่ใช่คำถามที่ดีมาก - เกิดอะไรขึ้น ข้อผิดพลาดใด ๆ ข้อผิดพลาดอะไร
Darren Sweeney


16
คำตอบคือไม่ได้ใช้รัฐซ้อนในการตอบสนอง อ่านนี้คำตอบที่ดี
Qwerty

4
ควรใช้อะไรแทน
Jānis Elmeris

42
การไม่ใช้สถานะซ้อนเป็นคำตอบที่ยอมรับไม่ได้สำหรับการใช้ React ที่ใช้กันอย่างแพร่หลายในปัจจุบัน สถานการณ์นี้กำลังจะเกิดขึ้นและนักพัฒนาต้องการคำตอบสำหรับเรื่องนี้
serraosays

คำตอบ:


455

เพื่อsetStateวัตถุที่ซ้อนกันคุณสามารถทำตามวิธีการด้านล่างเพราะฉันคิดว่า setState ไม่รองรับการอัพเดทที่ซ้อนกัน

var someProperty = {...this.state.someProperty}
someProperty.flag = true;
this.setState({someProperty})

แนวคิดคือการสร้างวัตถุจำลองดำเนินการกับมันแล้วแทนที่สถานะขององค์ประกอบด้วยวัตถุที่อัปเดต

ตอนนี้ผู้ประกอบการแพร่กระจายสร้างสำเนาของวัตถุที่ซ้อนกันเพียงระดับเดียว หากรัฐของคุณอยู่ในระดับสูงเช่น:

this.state = {
   someProperty: {
      someOtherProperty: {
          anotherProperty: {
             flag: true
          }
          ..
      }
      ...
   }
   ...
}

คุณสามารถ setState โดยใช้ตัวดำเนินการสเปรดในแต่ละระดับเช่น

this.setState(prevState => ({
    ...prevState,
    someProperty: {
        ...prevState.someProperty,
        someOtherProperty: {
            ...prevState.someProperty.someOtherProperty, 
            anotherProperty: {
               ...prevState.someProperty.someOtherProperty.anotherProperty,
               flag: false
            }
        }
    }
}))

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

immutability helperดูคำตอบนี้เกี่ยวกับวิธีการอัปเดตของรัฐที่มี


2
@Ohgodwhy ไม่มีสิ่งนี้ไม่สามารถเข้าถึงรัฐได้โดยตรงตั้งแต่ {... this.state.someProperty} retuns obejct ใหม่somePropertyและเรากำลังแก้ไขสิ่งนั้น
Shubham Khatri

4
สิ่งนี้ใช้ไม่ได้สำหรับฉัน .. ฉันใช้ react รุ่น @ 15.6.1
Rajiv

5
@Stophface เราสามารถใช้ Lodash เพื่อโคลนลึก ๆ ได้ แต่ทุกคนจะไม่รวมไลบรารีนี้เพื่อตั้งค่าสถานะ
Shubham Khatri

15
สิ่งนี้ไม่ได้ละเมิดreactjs.org/docs/หรือไม่? หากการเปลี่ยนแปลงสองครั้งสำหรับวัตถุที่ซ้อนกันถูกแบตช์การเปลี่ยนแปลงครั้งล่าสุดจะเขียนทับครั้งแรก ดังนั้นวิธีที่ถูกต้องที่สุดคือ: this.setState((prevState) => ({nested: {...prevState.nested, propertyToSet: newValue}}) น่าเบื่อนิดหน่อยแม้ว่า ...
olejorgenb

2
ฉันไม่คิดว่าคุณต้องการ...prevState,ตัวอย่างสุดท้ายคุณsomePropertyและลูก ๆ ของมัน
Jemar Jones

140

เพื่อเขียนมันในหนึ่งบรรทัด

this.setState({ someProperty: { ...this.state.someProperty, flag: false} });

12
ขอแนะนำให้ใช้ตัวอัปเดตสำหรับ setState แทนการเข้าถึง this.state โดยตรงภายใน setState reactjs.org/docs/react-component.html#setstate
Raghu Teja

6
ฉันเชื่อว่ากำลังพูดอย่าเพิ่งอ่านthis.stateทันทีsetStateและคาดหวังว่ามันจะมีคุณค่าใหม่ เมื่อเทียบกับการโทรภายในthis.state setStateหรือยังมีปัญหากับหลังที่ไม่ชัดเจนสำหรับฉัน
trpt4him

2
ดังที่trpt4himกล่าวลิงก์ที่คุณพูดถึงเกี่ยวกับปัญหาการเข้าถึงthis.stateหลังจากsetStateนั้น นอกจากนี้เรายังไม่ได้ผ่านการthis.state คุณสมบัติผู้ประกอบการแพร่กระจาย มันเหมือนกับ Object.assign () github.com/tc39/proposal-object-rest-spreadsetState
Yoseph

3
@ RaghuTeja อาจจะทำให้ this.setState(currentState => { someProperty: { ...currentState.someProperty, flag: false} });การหลีกเลี่ยงปัญหานี้ได้หรือไม่
Webwoman

ฉันได้เพิ่มคำตอบวิธีการบรรลุเหมือนกันกับ useState hook เพื่อความสมบูรณ์
แอนดรู

90

บางครั้งคำตอบโดยตรงไม่ใช่คำตอบที่ดีที่สุด :)

เวอร์ชั่นสั้น:

รหัสนี้

this.state = {
    someProperty: {
        flag: true
    }
}

ควรทำให้ง่ายขึ้นเป็นสิ่งที่ต้องการ

this.state = {
    somePropertyFlag: true
}

รุ่นยาว:

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

ให้จินตนาการถึงสถานะต่อไปนี้:

{
    parent: {
        child1: 'value 1',
        child2: 'value 2',
        ...
        child100: 'value 100'
    }
}

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

ดังนั้นคุณต้องสร้างใหม่ทั้งหมด parentวัตถุใหม่แต่ในกรณีนี้เราจะพบปัญหาอื่น ปฏิกิริยาจะคิดว่าเด็กทุกคนมีการเปลี่ยนแปลงค่าของพวกเขาและจะทำให้พวกเขาทั้งหมดอีกครั้ง แน่นอนมันไม่ดีต่อประสิทธิภาพ

มันยังคงเป็นไปได้ที่จะแก้ปัญหาโดยการเขียนตรรกะที่ซับซ้อนบางอย่างshouldComponentUpdate()แต่ฉันอยากจะหยุดที่นี่และใช้วิธีแก้ปัญหาง่าย ๆ จากเวอร์ชั่นสั้น ๆ


4
ฉันคิดว่ามีกรณีการใช้งานที่รัฐซ้อนกันดีอย่างสมบูรณ์ เช่นสถานะการติดตามคู่ของคีย์ - ค่าแบบไดนามิก ดูที่นี่วิธีที่คุณสามารถอัปเดตสถานะเพียงรายการเดียวในรัฐ: reactjs.org/docs/…
pors

1
การเปรียบเทียบแบบตื้นจะใช้สำหรับองค์ประกอบที่บริสุทธิ์ตามค่าเริ่มต้น
บาเซิล Shishani

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

1
คุณจะแนะนำอย่างไรให้ใช้ React เพื่อทำสิ่งต่าง ๆ เช่นการแสดงผลแผนภูมิแบบโต้ตอบ (ตัวอย่างเช่น บริษัท ของฉันมีเซนเซอร์จำนวนมากในส่วนต่างๆของโลกแต่ละประเภทแตกต่างกันมีคุณสมบัติแตกต่างกันในสถานที่ต่างกัน (80+) ) และแน่นอนว่ามีการจัดระเบียบในลำดับชั้นเนื่องจากการติดตั้งแต่ละครั้งมีค่าใช้จ่ายหลายพันดอลลาร์และเป็นโครงการที่สมบูรณ์ดังนั้นจึงเป็นไปไม่ได้ที่จะจัดการกับมันโดยไม่มีลำดับชั้น) ฉันอยากรู้ว่าคุณจะไม่สร้างแบบจำลองเหมือนต้นไม้เหมือน ... ต้นไม้ใน React ได้อย่างไร
DanyAlejandro

10
ดูเหมือนว่า React ไม่ได้ถูกสร้างขึ้นเพื่อใช้แทนสิ่งที่จัดเก็บเป็นโครงสร้างแบบเรียกซ้ำที่มีความลึกไม่สำคัญ มันน่าผิดหวังเล็กน้อยเนื่องจากมุมมองแบบต้นไม้ปรากฏขึ้นในแอปพลิเคชันที่ซับซ้อนเกือบทุกตัว (gmail, ตัวจัดการไฟล์, ระบบจัดการเอกสารใด ๆ , .. ) ฉันเคยใช้ React สำหรับสิ่งเหล่านี้ แต่มีความกระตือรือร้นที่จะบอกทุกคนว่าคุณกำลังทำรูปแบบการต่อต้านและการแนะนำวิธีการแก้ปัญหาคือการรักษาสำเนาของต้นไม้ในสถานะของทุกโหนด (ใช่ 80 สำเนา) ฉันชอบที่จะเห็นส่วนประกอบมุมมองแบบต้นไม้ที่ไม่แฮ็กที่จะไม่ทำลายกฎการตอบโต้ใด ๆ
DanyAlejandro

61

คำปฏิเสธ

รัฐซ้อนใน React เป็นการออกแบบที่ผิด

อ่านคำตอบที่ยอดเยี่ยมนี้

 

การให้เหตุผลเบื้องหลังคำตอบนี้:

setState ของ React เป็นเพียงความสะดวกในตัว แต่ในไม่ช้าคุณก็จะรู้ว่ามันมีขีด จำกัด การใช้คุณสมบัติที่กำหนดเองและการใช้ forceUpdate อย่างชาญฉลาดจะช่วยให้คุณได้มากขึ้น เช่น:

class MyClass extends React.Component {
    myState = someObject
    inputValue = 42
...

ตัวอย่างเช่น MobX, คูบอกสถานะอย่างสมบูรณ์และใช้คุณสมบัติที่สังเกตได้ที่กำหนดเอง
ใช้ Observables แทนสถานะใน React components

 


คำตอบสำหรับความทุกข์ยากของคุณ - ดูตัวอย่างที่นี่

มีอีกวิธีที่สั้นกว่าในการอัปเดตคุณสมบัติที่ซ้อนอยู่

this.setState(state => {
  state.nested.flag = false
  state.another.deep.prop = true
  return state
})

ในหนึ่งบรรทัด

 this.setState(state => (state.nested.flag = false, state))

หมายเหตุ: นี่คือการประกอบจุลภาค ~ MDNเห็นมันในการดำเนินการที่นี่ (Sandbox)

มันคล้ายกับ(แม้ว่าสิ่งนี้จะไม่เปลี่ยนการอ้างอิงสถานะ)

this.state.nested.flag = false
this.forceUpdate()

สำหรับความแตกต่างที่ลึกซึ้งในบริบทนี้ระหว่างforceUpdateและsetStateดูตัวอย่างที่เชื่อมโยง

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

คำเตือน

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

ตัวอย่างเช่นคุณอาจส่งเสาแบนที่มีการเปลี่ยนแปลงซึ่งอัปเดตและผ่านได้อย่างง่ายดาย

render(
  //some complex render with your nested state
  <ChildComponent complexNestedProp={this.state.nested} pleaseRerender={Math.random()}/>
)

ในขณะนี้แม้ว่าการอ้างอิงสำหรับ complexNestedProp จะไม่เปลี่ยนแปลง ( shouldComponentUpdate )

this.props.complexNestedProp === nextProps.complexNestedProp

คอมโพเนนต์จะแสดงผลใหม่เมื่อใดก็ตามที่มีการอัปเดตองค์ประกอบหลักซึ่งเป็นกรณีหลังจากการโทรthis.setStateหรือthis.forceUpdateในพาเรนต์

ผลของการผ่าเหล่าของรัฐ

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

ลองนึกภาพเส้นเวลาที่ควรแสดงข้อมูลประวัติการกลายพันธุ์ข้อมูลภายใต้มือจะส่งผลให้เกิดพฤติกรรมที่ไม่คาดคิดเนื่องจากจะเปลี่ยนรายการก่อนหน้า

รัฐไหล รัฐไหลซ้อนกัน

อย่างไรก็ตามที่นี่คุณจะเห็นNested PureChildClassว่าไม่ได้รับการจัดแสดงใหม่เนื่องจากอุปกรณ์ประกอบฉากล้มเหลวในการเผยแพร่


12
คุณไม่ควรปิดสถานะตอบโต้ใด ๆ หากมีส่วนประกอบซ้อนกันที่ใช้ข้อมูลจาก state.nested หรือ state.another พวกเขาจะไม่แสดงผลซ้ำหรือจะไม่มีลูก นี่เป็นเพราะวัตถุไม่เปลี่ยนแปลงดังนั้น React จึงคิดว่าไม่มีอะไรเปลี่ยนแปลง
Zeragamba

@ SpyMaster356 ฉันได้อัปเดตคำตอบของฉันเพื่อแก้ไขปัญหานี้แล้ว นอกจากนี้โปรดทราบว่า MobX ตัวอย่างเช่นคูstateทั้งหมดและใช้คุณสมบัติที่สังเกตได้ที่กำหนดเอง React's setStateเป็นเพียงความสะดวกสบายในตัว แต่ในไม่ช้าคุณจะรู้ว่ามันมีขีด จำกัด การใช้คุณสมบัติที่กำหนดเองและการใช้อย่างชาญฉลาดforceUpdateช่วยให้คุณได้มากขึ้น ใช้ Observables แทนสถานะใน React components
Qwerty

นอกจากนี้ฉันได้เพิ่มตัวอย่างreact-experiments.herokuapp.com/state-flow (ลิงก์ไปยังคอมไพล์ที่ด้านบน)
Qwerty

ถ้าคุณโหลดตัวอย่างวัตถุที่มีแอ็ตทริบิวต์ที่ซ้อนกันจากฐานข้อมูลเข้าสู่สถานะ จะหลีกเลี่ยงสถานะซ้อนในกรณีนั้นได้อย่างไร?
tobiv

3
@tobiv ดีกว่าที่จะแปลงข้อมูลตามการดึงข้อมูลเป็นโครงสร้างที่แตกต่างกัน แต่ขึ้นอยู่กับ usecase มันก็โอเคที่จะมีวัตถุที่ซ้อนกันหรืออาร์เรย์ของวัตถุในสถานะถ้าคุณคิดว่าพวกเขาเป็นหน่วยที่กะทัดรัดของข้อมูลบางอย่าง ปัญหาเกิดขึ้นเมื่อคุณต้องการจัดเก็บสวิตช์ UI หรือข้อมูลที่สามารถสังเกตได้อื่น ๆ (เช่นข้อมูลที่ควรเปลี่ยนมุมมองทันที) เนื่องจากต้องแบนเพื่อให้เกิดอัลกอริธึมการกระจายการตอบสนองภายในอย่างถูกต้อง สำหรับการเปลี่ยนแปลงในวัตถุที่ซ้อนกันอื่น ๆ มันง่ายที่จะทริกเกอร์หรือใช้ที่เฉพาะเจาะจงthis.forceUpdate() shouldComponentUpdate
Qwerty

21

หากคุณกำลังใช้ ES2015 คุณสามารถเข้าถึง Object.assign คุณสามารถใช้มันเพื่อปรับปรุงวัตถุที่ซ้อนกัน

this.setState({
  someProperty: Object.assign({}, this.state.someProperty, {flag: false})
});

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

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


12
และฉันอาจจะผิด แต่ฉันไม่คิดว่าคุณกำลังใช้ React หากไม่มี ES2015
Madbreaks

16
const newState = Object.assign({}, this.state);
newState.property.nestedProperty = "new value";
this.setState(newState);

1
ดูเหมือนว่าทางออกที่หรูหรากว่านี้ 1) หลีกเลี่ยงการแพร่กระจายที่น่าเบื่อภายใน setState และ 2) ทำให้สถานะใหม่ใน setState เป็นอย่างดีหลีกเลี่ยงการกลายพันธุ์ใน setState โดยตรง สุดท้าย แต่ไม่ท้ายสุด 3) หลีกเลี่ยงการใช้ห้องสมุดใด ๆ เพียงแค่งานง่าย ๆ
Webwoman

ตัวอย่างที่สองไม่เท่ากัน หากpropertyคุณสมบัติมีหลายคุณสมบัติซ้อนกันอันดับแรกจะลบออกส่วนที่สองจะไม่ลบออก codesandbox.io/s/nameless-pine-42thu
Qwerty

Qwerty คุณพูดถูกต้องขอบคุณสำหรับความพยายาม ฉันลบรุ่น ES6 แล้ว
eyecandy.dev

ตรงไปตรงมาและเรียบง่าย!
Tony Sepia

ฉันใช้โซลูชันของคุณ สถานะ obj ของฉันมีขนาดเล็กและทำงานได้ดี การตอบสนองจะแสดงผลสำหรับสถานะที่เปลี่ยนไปเท่านั้นหรือจะแสดงทุกอย่างหรือไม่
Kenji Noguchi

14

มีห้องสมุดมากมายที่จะช่วยในเรื่องนี้ ตัวอย่างเช่นการใช้immutability-helper :

import update from 'immutability-helper';

const newState = update(this.state, {
  someProperty: {flag: {$set: false}},
};
this.setState(newState);

ใช้ชุด lodash / fp :

import {set} from 'lodash/fp';

const newState = set(["someProperty", "flag"], false, this.state);

ใช้การผสาน lodash / fp :

import {merge} from 'lodash/fp';

const newState = merge(this.state, {
  someProperty: {flag: false},
});

หากคุณใช้lodashคุณอาจต้องการลอง_.cloneDeepและตั้งค่าสถานะเป็นสำเนาที่ลอกแบบมา
อี้ซิงหลิว

@YixingLiu: ฉันได้อัปเดตคำตอบที่จะใช้lodash/fpดังนั้นเราไม่ต้องกังวลเกี่ยวกับการกลายพันธุ์
tokland

ข้อได้เปรียบในการเลือกmergeหรือsetฟังก์ชั่นใน lodash / fp นอกเหนือจากไวยากรณ์แล้ว?
Toivo Säwén

คำตอบที่ดีบางทีคุณอาจต้องการเพิ่มว่าคุณต้องโทรthis.setState(newState)หาวิธีการ lodash / fp เช่นกัน gotcha อีกอย่างหนึ่งคือ lodash .set(ไม่ใช่ fp) มีลำดับของการขัดแย้งที่แตกต่างกันซึ่งทำให้ฉันสะดุดในขณะที่
Toivo Säwén

7

เราใช้ Immer https://github.com/mweststrate/immerเพื่อจัดการปัญหาประเภทนี้

เพียงแค่แทนที่รหัสนี้ในหนึ่งในองค์ประกอบของเรา

this.setState(prevState => ({
   ...prevState,
        preferences: {
            ...prevState.preferences,
            [key]: newValue
        }
}));

ด้วยสิ่งนี้

import produce from 'immer';

this.setState(produce(draft => {
    draft.preferences[key] = newValue;
}));

ด้วย immer คุณจัดการสถานะของคุณเป็น "วัตถุปกติ" ความมหัศจรรย์เกิดขึ้นเบื้องหลังด้วยวัตถุพร็อกซี


6

ต่อไปนี้เป็นรูปแบบของคำตอบแรกที่ให้ไว้ในชุดข้อความนี้ซึ่งไม่ต้องการแพ็คเกจเพิ่มเติมไลบรารีหรือฟังก์ชั่นพิเศษ

state = {
  someProperty: {
    flag: 'string'
  }
}

handleChange = (value) => {
  const newState = {...this.state.someProperty, flag: value}
  this.setState({ someProperty: newState })
}

ในการตั้งค่าสถานะของเขตข้อมูลที่ซ้อนเฉพาะคุณได้ตั้งค่าวัตถุทั้งหมด ฉันไม่นี้โดยการสร้างตัวแปรnewStateและการแพร่กระจายเนื้อหาของรัฐในปัจจุบันเป็นมันเป็นครั้งแรกโดยใช้ ES2015 ประกอบการแพร่กระจาย จากนั้นฉันแทนที่ค่าของthis.state.flagด้วยค่าใหม่ (ตั้งแต่ฉันตั้งค่าflag: value หลังจากที่ฉันกระจายสถานะปัจจุบันไปยังวัตถุflagเขตข้อมูลในสถานะปัจจุบันจะถูกแทนที่) จากนั้นฉันก็กำหนดสถานะของวัตถุsomePropertyของฉันnewState


6

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

สำหรับสถานะเช่นนี้

state = {
 contact: {
  phone: '888-888-8888',
  email: 'test@test.com'
 }
 address: {
  street:''
 },
 occupation: {
 }
}

วิธีที่ใช้ซ้ำ ive ที่ใช้จะมีลักษณะเช่นนี้

handleChange = (obj) => e => {
  let x = this.state[obj];
  x[e.target.name] = e.target.value;
  this.setState({ [obj]: x });
};

จากนั้นเพียงส่งผ่านชื่อ obj สำหรับการซ้อนแต่ละครั้งที่คุณต้องการระบุ ...

<TextField
 name="street"
 onChange={handleChange('address')}
 />

4

ฉันใช้วิธีนี้

หากคุณมีสถานะซ้อนอยู่เช่นนี้

   this.state = {
          formInputs:{
            friendName:{
              value:'',
              isValid:false,
              errorMsg:''
            },
            friendEmail:{
              value:'',
              isValid:false,
              errorMsg:''
            }
}

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

handleChange(el) {
    let inputName = el.target.name;
    let inputValue = el.target.value;

    let statusCopy = Object.assign({}, this.state);
    statusCopy.formInputs[inputName].value = inputValue;

    this.setState(statusCopy);
  }

นี่คือ html พร้อมฟังเหตุการณ์

<input type="text" onChange={this.handleChange} " name="friendName" />

ได้รับการยืนยัน สิ่งนี้ใช้งานได้ดีถ้าคุณจัดการกับโครงการที่สร้างไว้แล้วพร้อมด้วยคุณสมบัติแบบซ้อน
metal_jacke1

4

สร้างสำเนาของรัฐ:

let someProperty = JSON.parse(JSON.stringify(this.state.someProperty))

ทำการเปลี่ยนแปลงในวัตถุนี้:

someProperty.flag = "false"

ตอนนี้อัพเดตสถานะ

this.setState({someProperty})

setState ไม่ตรงกัน ดังนั้นในขณะที่อัปเดตบุคคลอื่นสามารถเรียกใช้ฟังก์ชันนี้และคัดลอกสถานะที่ล้าสมัย
Aviv Ben Shabat

3

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

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

หากคุณมี:

const [state, setState] = useState({ someProperty: { flag: true, otherNestedProp: 1 }, otherProp: 2 })

คุณสามารถตั้งค่าคุณสมบัติที่ซ้อนกันได้โดยการโคลนปัจจุบันและแก้ไขส่วนที่ต้องการของข้อมูลตัวอย่างเช่น

setState(current => { ...current, someProperty: { ...current.someProperty, flag: false } });

หรือคุณสามารถใช้ไลบรารี Immer เพื่อทำให้การโคลนและการแพตช์ของวัตถุง่ายขึ้น

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

import { useStateLink } from '@hookstate/core' 
const state = useStateLink({ someProperty: { flag: true, otherNestedProp: 1 }, otherProp: 2 })

รับฟิลด์เพื่อแสดงผล:

state.nested.someProperty.nested.flag.get()
// or 
state.get().someProperty.flag

ตั้งค่าฟิลด์ที่ซ้อนกัน:

state.nested.someProperty.nested.flag.set(false)

นี่คือตัวอย่างที่ Hookstate ที่รัฐลึก / ซ้อนซ้ำในลักษณะเหมือนต้นไม้โครงสร้างข้อมูล


2

อีกสองตัวเลือกที่ยังไม่ได้กล่าวถึง:

  1. หากคุณมีสถานะซ้อนกันอย่างลึกล้ำให้พิจารณาว่าคุณสามารถปรับโครงสร้างวัตถุย่อยเพื่อนั่งที่รากได้หรือไม่ ทำให้การปรับปรุงข้อมูลง่ายขึ้น
  2. มีไลบรารี่ที่มีประโยชน์มากมายสำหรับจัดการสถานะไม่เปลี่ยนรูปที่อยู่ในเอกสาร Redux ฉันแนะนำ Immer เนื่องจากช่วยให้คุณสามารถเขียนรหัสในลักษณะ mutative แต่จัดการการโคลนที่จำเป็นเบื้องหลัง นอกจากนี้ยังตรึงวัตถุที่เป็นผลลัพธ์เพื่อให้คุณไม่สามารถกลายพันธุ์ในภายหลังโดยไม่ได้ตั้งใจ

2

เพื่อทำสิ่งต่าง ๆ โดยทั่วไปฉันทำงานกับคำตอบของ @ ShubhamKhatri และ @ Qwerty

วัตถุของรัฐ

this.state = {
  name: '',
  grandParent: {
    parent1: {
      child: ''
    },
    parent2: {
      child: ''
    }
  }
};

การควบคุมการป้อนข้อมูล

<input
  value={this.state.name}
  onChange={this.updateState}
  type="text"
  name="name"
/>
<input
  value={this.state.grandParent.parent1.child}
  onChange={this.updateState}
  type="text"
  name="grandParent.parent1.child"
/>
<input
  value={this.state.grandParent.parent2.child}
  onChange={this.updateState}
  type="text"
  name="grandParent.parent2.child"
/>

เมธอด updateState

setState เป็นคำตอบของ @ ShubhamKhatri

updateState(event) {
  const path = event.target.name.split('.');
  const depth = path.length;
  const oldstate = this.state;
  const newstate = { ...oldstate };
  let newStateLevel = newstate;
  let oldStateLevel = oldstate;

  for (let i = 0; i < depth; i += 1) {
    if (i === depth - 1) {
      newStateLevel[path[i]] = event.target.value;
    } else {
      newStateLevel[path[i]] = { ...oldStateLevel[path[i]] };
      oldStateLevel = oldStateLevel[path[i]];
      newStateLevel = newStateLevel[path[i]];
    }
  }
  this.setState(newstate);
}

setState เป็นคำตอบของ @ Qwerty

updateState(event) {
  const path = event.target.name.split('.');
  const depth = path.length;
  const state = { ...this.state };
  let ref = state;
  for (let i = 0; i < depth; i += 1) {
    if (i === depth - 1) {
      ref[path[i]] = event.target.value;
    } else {
      ref = ref[path[i]];
    }
  }
  this.setState(state);
}

หมายเหตุ: วิธีการข้างต้นไม่สามารถใช้กับอาร์เรย์ได้


2

ฉันให้ความสำคัญอย่างยิ่งกับข้อกังวลที่เปล่งออกมาโดยรอบแล้วในการสร้างสำเนาส่วนประกอบสถานะของคุณอย่างสมบูรณ์ กับที่กล่าวว่าฉันจะขอแนะนำให้อิมเมอ

import produce from 'immer';

<Input
  value={this.state.form.username}
  onChange={e => produce(this.state, s => { s.form.username = e.target.value }) } />

สิ่งนี้ควรใช้งานได้React.PureComponent(เช่นการเปรียบเทียบสถานะที่ตื้นด้วย React) เนื่องจากImmerใช้พร็อกซีออบเจ็กต์อย่างชาญฉลาดเพื่อคัดลอกแผนผังของรัฐที่มีความลึกโดยพลการ อิมเมอร์ยังเป็นประเภทที่ปลอดภัยกว่าเมื่อเปรียบเทียบกับห้องสมุดอย่าง Immutability Helper และเหมาะสำหรับผู้ใช้จาวาสคริปต์และผู้ใช้ตัวเรียงพิมพ์


ฟังก์ชันอรรถประโยชน์ของtypescript

function setStateDeep<S>(comp: React.Component<any, S, any>, fn: (s: 
Draft<Readonly<S>>) => any) {
  comp.setState(produce(comp.state, s => { fn(s); }))
}

onChange={e => setStateDeep(this, s => s.form.username = e.target.value)}

1
stateUpdate = () => {
    let obj = this.state;
    if(this.props.v12_data.values.email) {
      obj.obj_v12.Customer.EmailAddress = this.props.v12_data.values.email
    }
    this.setState(obj)
}

1
ฉันไม่คิดว่ามันถูกต้องเนื่องจากobjเป็นเพียงการอ้างอิงถึงรัฐดังนั้นคุณจึงกลายพันธุ์this.stateวัตถุจริง
Ciprian Tomoiagă

0

ฉันพบสิ่งนี้จะทำงานสำหรับฉันมีแบบฟอร์มโครงการในกรณีของฉันที่เช่นคุณมี ID และชื่อและฉันอยากจะรักษาสถานะของโครงการที่ซ้อนกัน

return (
  <div>
      <h2>Project Details</h2>
      <form>
        <Input label="ID" group type="number" value={this.state.project.id} onChange={(event) => this.setState({ project: {...this.state.project, id: event.target.value}})} />
        <Input label="Name" group type="text" value={this.state.project.name} onChange={(event) => this.setState({ project: {...this.state.project, name: event.target.value}})} />
      </form> 
  </div>
)

แจ้งให้เราทราบ!


0

บางสิ่งเช่นนี้อาจพอเพียง

const isObject = (thing) => {
    if(thing && 
        typeof thing === 'object' &&
        typeof thing !== null
        && !(Array.isArray(thing))
    ){
        return true;
    }
    return false;
}

/*
  Call with an array containing the path to the property you want to access
  And the current component/redux state.

  For example if we want to update `hello` within the following obj
  const obj = {
     somePrimitive:false,
     someNestedObj:{
        hello:1
     }
  }

  we would do :
  //clone the object
  const cloned = clone(['someNestedObj','hello'],obj)
  //Set the new value
  cloned.someNestedObj.hello = 5;

*/
const clone = (arr, state) => {
    let clonedObj = {...state}
    const originalObj = clonedObj;
    arr.forEach(property => {
        if(!(property in clonedObj)){
            throw new Error('State missing property')
        }

        if(isObject(clonedObj[property])){
            clonedObj[property] = {...originalObj[property]};
            clonedObj = clonedObj[property];
        }
    })
    return originalObj;
}

const nestedObj = {
    someProperty:true,
    someNestedObj:{
        someOtherProperty:true
    }
}

const clonedObj = clone(['someProperty'], nestedObj);
console.log(clonedObj === nestedObj) //returns false
console.log(clonedObj.someProperty === nestedObj.someProperty) //returns true
console.log(clonedObj.someNestedObj === nestedObj.someNestedObj) //returns true

console.log()
const clonedObj2 = clone(['someProperty','someNestedObj','someOtherProperty'], nestedObj);
console.log(clonedObj2 === nestedObj) // returns false
console.log(clonedObj2.someNestedObj === nestedObj.someNestedObj) //returns false
//returns true (doesn't attempt to clone because its primitive type)
console.log(clonedObj2.someNestedObj.someOtherProperty === nestedObj.someNestedObj.someOtherProperty) 

0

ฉันรู้ว่ามันเป็นคำถามเก่า แต่ก็ยังต้องการแบ่งปันวิธีที่ฉันประสบความสำเร็จ สมมติว่าสถานะในนวกรรมิกดูเหมือนว่านี้:

  constructor(props) {
    super(props);

    this.state = {
      loading: false,
      user: {
        email: ""
      },
      organization: {
        name: ""
      }
    };

    this.handleChange = this.handleChange.bind(this);
  }

handleChangeฟังก์ชั่นของฉันเป็นแบบนี้:

  handleChange(e) {
    const names = e.target.name.split(".");
    const value = e.target.type === "checkbox" ? e.target.checked : e.target.value;
    this.setState((state) => {
      state[names[0]][names[1]] = value;
      return {[names[0]]: state[names[0]]};
    });
  }

และให้แน่ใจว่าคุณตั้งชื่ออินพุตตาม:

<input
   type="text"
   name="user.email"
   onChange={this.handleChange}
   value={this.state.user.firstName}
   placeholder="Email Address"
/>

<input
   type="text"
   name="organization.name"
   onChange={this.handleChange}
   value={this.state.organization.name}
   placeholder="Organization Name"
/>

0

ฉันทำการอัปเดตแบบซ้อนด้วยการลดการค้นหา:

ตัวอย่าง:

ตัวแปรที่ซ้อนกันในสถานะ:

state = {
    coords: {
        x: 0,
        y: 0,
        z: 0
    }
}

ฟังก์ชั่น:

handleChange = nestedAttr => event => {
  const { target: { value } } = event;
  const attrs = nestedAttr.split('.');

  let stateVar = this.state[attrs[0]];
  if(attrs.length>1)
    attrs.reduce((a,b,index,arr)=>{
      if(index==arr.length-1)
        a[b] = value;
      else if(a[b]!=null)
        return a[b]
      else
        return a;
    },stateVar);
  else
    stateVar = value;

  this.setState({[attrs[0]]: stateVar})
}

ใช้:

<input
value={this.state.coords.x}
onChange={this.handleTextChange('coords.x')}
/>

0

นี่คือ initialState ของฉัน

    const initialStateInput = {
        cabeceraFamilia: {
            familia: '',
            direccion: '',
            telefonos: '',
            email: ''
        },
        motivoConsulta: '',
        fechaHora: '',
        corresponsables: [],
    }

เบ็ดหรือคุณสามารถแทนที่ด้วยรัฐ (องค์ประกอบชั้น)

const [infoAgendamiento, setInfoAgendamiento] = useState(initialStateInput);

วิธีการสำหรับ handleChange

const actualizarState = e => {
    const nameObjects = e.target.name.split('.');
    const newState = setStateNested(infoAgendamiento, nameObjects, e.target.value);
    setInfoAgendamiento({...newState});
};

วิธีการตั้งค่าสถานะด้วยสถานะซ้อนกัน

const setStateNested = (state, nameObjects, value) => {
    let i = 0;
    let operativeState = state;
    if(nameObjects.length > 1){
        for (i = 0; i < nameObjects.length - 1; i++) {
            operativeState = operativeState[nameObjects[i]];
        }
    }
    operativeState[nameObjects[i]] = value;
    return state;
}

ในที่สุดนี่คืออินพุตที่ฉันใช้

<input type="text" className="form-control" name="cabeceraFamilia.direccion" placeholder="Dirección" defaultValue={infoAgendamiento.cabeceraFamilia.direccion} onChange={actualizarState} />

0

หากคุณใช้ formik ในโครงการของคุณมีวิธีง่าย ๆ ในการจัดการสิ่งนี้ นี่เป็นวิธีที่ง่ายที่สุดในการทำ formik

ก่อนอื่นให้ตั้งค่าเริ่มต้นของคุณภายในแอตทริบิวต์ formik initivalues ​​หรือในการตอบสนอง สถานะ

ที่นี่ค่าเริ่มต้นจะถูกกำหนดในสถานะตอบสนอง

   state = { 
     data: {
        fy: {
            active: "N"
        }
     }
   }

กำหนดข้างต้นค่าเริ่มต้นสำหรับเขตข้อมูล formik ภายในinitiValuesแอตทริบิวต์formik

<Formik
 initialValues={this.state.data}
 onSubmit={(values, actions)=> {...your actions goes here}}
>
{({ isSubmitting }) => (
  <Form>
    <Field type="checkbox" name="fy.active" onChange={(e) => {
      const value = e.target.checked;
      if(value) setFieldValue('fy.active', 'Y')
      else setFieldValue('fy.active', 'N')
    }}/>
  </Form>
)}
</Formik>

สร้างคอนโซลเพื่อตรวจสอบสถานะที่อัพเดตเป็นstringแทนฟังก์ชันbooleanformik setFieldValueเพื่อตั้งค่าสถานะหรือใช้เครื่องมือดีบักเกอร์ตอบโต้เพื่อดูการเปลี่ยนแปลงที่เกิดขึ้นภายในค่าสถานะ formik


0

ลองรหัสนี้:

this.setState({ someProperty: {flag: false} });

แต่สิ่งนี้จะลบคุณสมบัติอื่น ๆ ที่มีอยู่ในวัตถุที่อ้างอิงโดยsomePropertyและหลังจากที่โค้ดข้างต้นถูกเรียกใช้งานflagคุณสมบัติจะยังคงอยู่เท่านั้น
Yashas

0

ฉันเห็นสิ่งต่อไปนี้ในหนังสือ:

this.setState(state => state.someProperty.falg = false);

แต่ฉันไม่แน่ใจว่ามันถูกต้อง ..

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.