การอัพเดทสถานะของการเปลี่ยนอุปกรณ์ประกอบฉากในแบบฟอร์มตอบโต้


184

ฉันมีปัญหากับแบบฟอร์มการตอบโต้และการจัดการสถานะอย่างถูกต้อง ฉันมีช่องป้อนเวลาในรูปแบบ (ในคำกริยา) ค่าเริ่มต้นถูกตั้งค่าเป็นตัวแปรสถานะในgetInitialStateและถูกส่งผ่านมาจากองค์ประกอบหลัก ในตัวมันเองทำงานได้ดี

ปัญหาเกิดขึ้นเมื่อฉันต้องการอัปเดตค่าเริ่มต้น start_time ผ่านองค์ประกอบหลัก setState start_time: new_timeการปรับปรุงตัวเองที่เกิดขึ้นในองค์ประกอบแม่ผ่าน อย่างไรก็ตามในรูปแบบของฉันค่าเริ่มต้น start_time จะไม่มีการเปลี่ยนแปลงเนื่องจากมีการกำหนดเพียงครั้งเดียวgetInitialStateเท่านั้น

ฉันพยายามใช้componentWillUpdateเพื่อบังคับให้เกิดการเปลี่ยนแปลงผ่านทางรัฐsetState start_time: next_props.start_timeซึ่งใช้งานได้จริง แต่ให้Uncaught RangeError: Maximum call stack size exceededข้อผิดพลาดแก่ฉัน

ดังนั้นคำถามของฉันคืออะไรวิธีที่ถูกต้องในการปรับปรุงสถานะในกรณีนี้คืออะไร? ฉันกำลังคิดผิดเกี่ยวกับเรื่องนี้หรือไม่?

รหัสปัจจุบัน:

@ModalBody = React.createClass
  getInitialState: ->
    start_time: @props.start_time.format("HH:mm")

  #works but takes long and causes:
  #"Uncaught RangeError: Maximum call stack size exceeded"
  componentWillUpdate: (next_props, next_state) ->
    @setState(start_time: next_props.start_time.format("HH:mm"))

  fieldChanged: (fieldName, event) ->
    stateUpdate = {}
    stateUpdate[fieldName] = event.target.value
    @setState(stateUpdate)

  render: ->
    React.DOM.div
      className: "modal-body"
      React.DOM.form null,
        React.createElement FormLabelInputField,
          type: "time"
          id: "start_time"
          label_name: "Start Time"
          value: @state.start_time
          onChange: @fieldChanged.bind(null, "start_time”)

@FormLabelInputField = React.createClass
  render: ->
    React.DOM.div
      className: "form-group"
      React.DOM.label
        htmlFor: @props.id
        @props.label_name + ": "
      React.DOM.input
        className: "form-control"
        type: @props.type
        id: @props.id
        value: @props.value
        onChange: @props.onChange

คำตอบ:


287

componentWillReceiveProps มีการลดระดับเนื่องจากปฏิกิริยาที่ 16: ใช้getDerivedStateFromPropsแทน

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

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

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

เมื่อใดก็ตามที่เป็นไปได้ให้คำนวณค่าแบบ on-the-fly เพื่อให้แน่ใจว่าจะไม่ซิงค์ในภายหลังและทำให้เกิดปัญหาในการบำรุงรักษา

โดยทั่วไปทุกครั้งที่คุณกำหนดผู้ปกครองpropsให้กับเด็กstateวิธีการเรนเดอร์จะไม่ถูกเรียกใช้เสมอในการอัปเดต prop คุณต้องเรียกใช้ด้วยตนเองโดยใช้componentWillReceivePropsวิธีการ

componentWillReceiveProps(nextProps) {
  // You don't have to do this check first, but it can help prevent an unneeded render
  if (nextProps.startTime !== this.state.startTime) {
    this.setState({ startTime: nextProps.startTime });
  }
}

84
เลิกใช้เมื่อวันที่ 16 ตอบสนอง
เพื่อน

7
@dude ยังไม่ได้คัดค้านสิ่งที่คุณอ้างถึงเป็นเพียงหัวขึ้นสำหรับการอ้างอิงในอนาคต ฉันพูด[..]going to be deprecated in the future
paddotk

7
@poepje อาจยังไม่ได้รับการคัดค้าน แต่ถูกมองว่าไม่ปลอดภัยตามมาตรฐานปัจจุบันและควรหลีกเลี่ยง
unflores

12
ดังนั้นสิ่งที่ควรเป็นวิธีการใหม่ในการทำเช่นนี้หลังจาก componentWillReceiveProps เลิกใช้แล้ว?
Boris D. Teoharov

5
@Boris ทีนี้ทีมตอบโต้ก็บอกให้คุณยัด พวกเขาให้วิธีการใหม่ที่เรียกว่า getDerivedStateFromProps จับเป็นว่านี่เป็นวิธีการคงที่ หมายความว่าคุณไม่สามารถทำอะไรแบบอะซิงโครนัสเพื่ออัปเดตสถานะได้ (เนื่องจากคุณต้องส่งคืนสถานะใหม่ทันที) ไม่สามารถเข้าถึงวิธีหรือคลาสได้ คุณยังสามารถใช้บันทึกช่วยจำได้ แต่ไม่เหมาะกับทุกกรณีการใช้งาน ทีมตอบโต้ต้องการบังคับให้พวกเขาทำสิ่งต่าง ๆ อีกครั้ง มันเป็นการตัดสินใจออกแบบที่โง่และไร้ความสามารถอย่างมาก
ig-dev

76

เห็นได้ชัดว่าสิ่งต่าง ๆ กำลังเปลี่ยนแปลง .... getDerivedStateFromProps ()ตอนนี้เป็นฟังก์ชันที่ต้องการ

class Component extends React.Component {
  static getDerivedStateFromProps(props, current_state) {
    if (current_state.value !== props.value) {
      return {
        value: props.value,
        computed_prop: heavy_computation(props.value)
      }
    }
    return null
  }
}

(รหัสข้างต้นโดย danburzo @ github)


7
FYI, คุณต้องกลับด้วยnullหากไม่มีอะไรควรเปลี่ยนหลังจากคุณไปแล้ว, คุณควรไปกับreturn null
IlgıtYıldırım

@ IlgıtYıldırım - มีรหัสที่แก้ไขตั้งแต่ 4 คนยกระดับความคิดเห็นของคุณ - มันสร้างความแตกต่างได้จริงหรือ?
ErichBSchulz

มีทรัพยากรที่ดีงามที่จะไปในเชิงลึกเกี่ยวกับตัวเลือกที่แตกต่างกันและทำไมคุณจะใช้อย่างใดอย่างหนึ่งgetDerivedStateFromPropsหรือ memoization reactjs.org/blog/2018/06/07/...
unflores

2
getDerivedStateFromProps ถูกบังคับให้เป็นแบบสแตติก หมายความว่าคุณไม่สามารถทำอะไรแบบอะซิงโครนัสเพื่ออัปเดตสถานะไม่สามารถเข้าถึงวิธีหรือคลาสได้ ทีมตอบโต้ต้องการบังคับให้พวกเขาทำสิ่งต่าง ๆ อีกครั้ง มันเป็นการตัดสินใจออกแบบที่โง่และไร้ความสามารถอย่างมาก
ig-dev

39

componentWillReceiveProps จะเลิกใช้เพราะใช้ "มักจะนำไปสู่ข้อบกพร่องและความไม่สอดคล้องกัน"

keyหากสิ่งที่เปลี่ยนแปลงจากภายนอกพิจารณารีเซ็ตองค์ประกอบเด็กทั้งหมดด้วย

การจัดเตรียมkeyprop ให้กับคอมโพเนนต์ child ต้องแน่ใจว่าเมื่อใดก็ตามที่ค่าkeyการเปลี่ยนแปลงจากภายนอกคอมโพเนนต์นี้จะถูกเรนเดอร์ใหม่ เช่น,

<EmailInput
  defaultEmail={this.props.user.email}
  key={this.props.user.id}
/>

เกี่ยวกับประสิทธิภาพ:

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


1
กุญแจความลับ! ทำงานได้อย่างสมบูรณ์แบบใน React 16 ดังกล่าวข้างต้น
Darren Sweeney

คีย์ไม่ทำงานหากเป็นวัตถุและคุณไม่มีสตริงที่ไม่ซ้ำกัน
user3468806

รหัสทำงานกับวัตถุฉันทำ แน่นอนว่าฉันมีสตริงที่ไม่ซ้ำกันสำหรับคีย์แม้ว่า
tsujp

@ user3468806 หากไม่ใช่วัตถุที่ซับซ้อนที่มีการอ้างอิงภายนอกคุณสามารถใช้JSON.stringify(myObject)เพื่อรับคีย์ที่ไม่ซ้ำจากวัตถุของคุณ
Roy Prins

24

นอกจากนี้ยังมีcomponentDidUpdateใช้ได้

ฟังก์ชั่นลายเซ็น:

componentDidUpdate(prevProps, prevState, snapshot)

ใช้สิ่งนี้เป็นโอกาสในการทำงานใน DOM เมื่อองค์ประกอบได้รับการปรับปรุง renderไม่ได้รับการเรียกร้องให้เริ่มต้น

ดูคุณอาจไม่จำเป็นต้องมารัฐบทความซึ่งอธิบายต่อต้านแบบสำหรับทั้งและcomponentDidUpdate getDerivedStateFromPropsฉันพบว่ามันมีประโยชน์มาก


ท้ายที่สุดฉันใช้componentDidUpdateเพราะมันเรียบง่ายและเหมาะสำหรับกรณีส่วนใหญ่
KeitelDOG

14

วิธี hooks ใหม่ของการทำเช่นนี้คือการใช้ useEffect แทน componentWillReceiveProps แบบเก่า:

componentWillReceiveProps(nextProps) {
  // You don't have to do this check first, but it can help prevent an unneeded render
  if (nextProps.startTime !== this.state.startTime) {
    this.setState({ startTime: nextProps.startTime });
  }
}

จะกลายเป็นสิ่งต่อไปนี้ในส่วนประกอบขับเคลื่อน hooks ทำงาน:

// store the startTime prop in local state
const [startTime, setStartTime] = useState(props.startTime)
// 
useEffect(() => {
  if (props.startTime !== startTime) {
    setStartTime(props.startTime);
  }
}, [props.startTime]);

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


5

คุณอาจไม่ต้องการรัฐที่ได้รับ

1. ตั้งรหัสจากผู้ปกครอง

เมื่อมีการเปลี่ยนแปลงที่สำคัญ React จะสร้างอินสแตนซ์องค์ประกอบใหม่แทนที่จะอัปเดตปัจจุบัน โดยปกติคีย์จะใช้สำหรับรายการแบบไดนามิก แต่ก็มีประโยชน์ที่นี่

2. ใช้getDerivedStateFromProps/componentWillReceiveProps

หากคีย์ไม่ทำงานด้วยเหตุผลบางอย่าง (ส่วนประกอบอาจมีราคาแพงมากในการเริ่มต้น)

โดยการใช้getDerivedStateFromPropsคุณสามารถรีเซ็ตส่วนใด ๆ ของรัฐได้ แต่ดูเหมือนว่าจะมีบั๊กกี้เล็กน้อยในเวลานี้ (v16.7)! ดูลิงค์ด้านบนสำหรับการใช้งาน


2

จากเอกสารตอบสนอง: https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html

การลบสถานะเมื่อการเปลี่ยนอุปกรณ์ประกอบฉากเป็นรูปแบบการต่อต้าน

เนื่องจาก React 16, componentWillReceiveProps จะถูกคัดค้าน จากเอกสารปฏิกิริยาตอบสนองแนวทางที่แนะนำในกรณีนี้คือการใช้

  1. ส่วนประกอบที่ควบคุมอย่างสมบูรณ์: ParentComponentความModalBodyประสงค์ของจะเป็นของstart_timeรัฐ นี่ไม่ใช่วิธีที่ฉันชอบในกรณีนี้เนื่องจากฉันคิดว่า modal ควรเป็นเจ้าของสถานะนี้
  2. องค์ประกอบที่ไม่มีการควบคุมอย่างเต็มที่พร้อมกุญแจ: นี่เป็นวิธีการที่ฉันชอบ ตัวอย่างจากการตอบสนองเอกสาร: https://codesandbox.io/s/6v1znlxyxn คุณจะเป็นเจ้าของอย่างเต็มที่start_timeจากรัฐของคุณModalBodyและใช้getInitialStateเหมือนที่คุณได้ทำไปแล้ว หากต้องการรีเซ็ตstart_timeสถานะคุณเพียงเปลี่ยนคีย์จากParentComponent


0

ใช้บันทึก

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

ระบุว่ามูลค่าของรัฐ start_timeเป็นเพียงเสาstart_time.format("HH:mm")ข้อมูลที่มีอยู่ในเสานั้นมีอยู่แล้วในตัวเองเพียงพอสำหรับการปรับปรุงองค์ประกอบ

อย่างไรก็ตามหากคุณไม่ต้องการเพียงโทรรูปแบบในการเปลี่ยนแปลงเสาวิธีที่ถูกต้องในการทำเช่นนี้ต่อเอกสารล่าสุดจะผ่านการบันทึก: https://reactjs.org/blog/2018/06/07/you-probably-dont- จำเป็นที่จะต้องมา-state.html # สิ่งที่เกี่ยวกับ memoization


-1

ฉันคิดว่าการใช้ ref นั้นปลอดภัยสำหรับฉันไม่จำเป็นต้องสนใจวิธีการข้างต้น

class Company extends XComponent {
    constructor(props) {
        super(props);
        this.data = {};
    }
    fetchData(data) {
        this.resetState(data);
    }
    render() {
        return (
            <Input ref={c => this.data['name'] = c} type="text" className="form-control" />
        );
    }
}
class XComponent extends Component {
    resetState(obj) {
        for (var property in obj) {
            if (obj.hasOwnProperty(property) && typeof this.data[property] !== 'undefined') {
                if ( obj[property] !== this.data[property].state.value )
                    this.data[property].setState({value: obj[property]});
                else continue;
            }
            continue;
        }
    }
}

ฉันคิดว่าคำตอบนี้เป็นความลับ (รหัสอ่านได้ยากและไม่มีคำอธิบายใด ๆ / เชื่อมโยงกับปัญหาของ OP) และไม่จัดการปัญหาของ OP ซึ่งเป็นวิธีจัดการสถานะเริ่มต้น
netchkin
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.