วิธีการเข้าถึงสถานะของเด็กในการตอบสนอง?


215

ฉันมีโครงสร้างต่อไปนี้:

FormEditor- เก็บ FieldEditor หลายอัน FieldEditor- แก้ไขฟิลด์ของแบบฟอร์มและบันทึกค่าต่าง ๆ เกี่ยวกับมันในสถานะ

เมื่อคลิกปุ่มภายใน FormEditor ฉันต้องการรวบรวมข้อมูลเกี่ยวกับฟิลด์จากFieldEditorส่วนประกอบทั้งหมดข้อมูลที่อยู่ในสถานะของพวกเขาและมีทั้งหมดภายใน FormEditor

ฉันถือว่าการจัดเก็บข้อมูลเกี่ยวกับเขตข้อมูลนอกFieldEditorสถานะของและวางไว้ในFormEditorสถานะของแทน อย่างไรก็ตามนั่นจะต้องFormEditorฟังFieldEditorส่วนประกอบแต่ละอย่างเมื่อมีการเปลี่ยนแปลงและจัดเก็บข้อมูลในสถานะ

ฉันไม่สามารถเข้าถึงสถานะของเด็กแทนได้หรือไม่ มันเหมาะไหม


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

@FelixKling ถ้าอย่างนั้นคุณกำลังบอกว่าวิธีที่เหมาะสำหรับเด็กที่จะสื่อสารกับผู้ปกครองนั้นเป็นแค่เหตุการณ์เท่านั้น?
Moshe Revah

3
ใช่กิจกรรมเป็นวิธีหนึ่ง หรือมีการไหลของข้อมูลทิศทางเดียวเช่น Flux Promotes: facebook.github.io/flux
Felix Kling

1
หากคุณไม่ได้ใช้งานFieldEditorแยกต่างหากให้บันทึกสถานะของพวกเขาเป็นFormEditorเสียงที่ดี หากเป็นกรณีนี้ของคุณFieldEditorอินสแตนซ์จะแสดงขึ้นอยู่กับการส่งผ่านโดยการแก้ไขรูปแบบของพวกเขาไม่ได้เป็นของprops stateวิธีที่ซับซ้อนมากขึ้น แต่มีความยืดหยุ่นคือการสร้างซีเรียลไลเซอร์ที่ผ่านการเชื่อมโยงลูก ๆ และค้นหาFormEditorอินสแตนซ์ทั้งหมดระหว่างพวกเขา วัตถุ JSON สามารถซ้อนกันเป็นทางเลือก (มากกว่าหนึ่งระดับ) ขึ้นอยู่กับระดับการซ้อนของอินสแตนซ์ในเครื่องมือแก้ไขแบบฟอร์ม
Meglio

4
ฉันคิดว่า React Docs 'Lifting State Up'น่าจะเป็นวิธีที่ 'Reacty' ที่สุดในการทำเช่นนี้
icc97

คำตอบ:


136

หากคุณมีตัวจัดการ onChange สำหรับแต่ละ FieldEditors แล้วฉันไม่เห็นสาเหตุที่คุณไม่สามารถย้ายสถานะไปยังองค์ประกอบ FormEditor และส่งผ่านการเรียกกลับจากที่นั่นไปยัง FieldEditors ที่จะอัปเดตสถานะหลัก ดูเหมือนว่าจะเป็นวิธี React-y มากขึ้นสำหรับฉัน

บางสิ่งบางอย่างตามแนวนี้อาจจะ:

const FieldEditor = ({ value, onChange, id }) => {
  const handleChange = event => {
    const text = event.target.value;
    onChange(id, text);
  };

  return (
    <div className="field-editor">
      <input onChange={handleChange} value={value} />
    </div>
  );
};

const FormEditor = props => {
  const [values, setValues] = useState({});
  const handleFieldChange = (fieldId, value) => {
    setValues({ ...values, [fieldId]: value });
  };

  const fields = props.fields.map(field => (
    <FieldEditor
      key={field}
      id={field}
      onChange={handleFieldChange}
      value={values[field]}
    />
  ));

  return (
    <div>
      {fields}
      <pre>{JSON.stringify(values, null, 2)}</pre>
    </div>
  );
};

// To add abillity to dynamically add/remove fields keep the list in state
const App = () => {
  const fields = ["field1", "field2", "anotherField"];

  return <FormEditor fields={fields} />;
};

ต้นฉบับ - รุ่นตะขอล่วงหน้า:


2
สิ่งนี้มีประโยชน์สำหรับ onChange แต่ไม่มากใน onSubmit
190290000 รูเบิลชาย

1
@ 190290000RubleMan ฉันไม่แน่ใจว่าสิ่งที่คุณหมายถึง แต่เนื่องจากรถขน onChange ในแต่ละเขตข้อมูลให้แน่ใจว่าคุณมักจะมีข้อมูลที่สมบูรณ์แบบที่มีอยู่ในรัฐของคุณในองค์ประกอบ FormEditor ของคุณ onSubmit myAPI.SaveStuff(this.state);ของคุณก็จะรวมถึงสิ่งที่ต้องการ ถ้าคุณทำอย่างละเอียดอีกเล็กน้อยบางทีฉันสามารถให้คำตอบที่ดี :)
มาร์คัส-ipse

ว่าฉันมีรูปแบบที่มี 3 สาขาที่แตกต่างกันแต่ละประเภทมีการตรวจสอบของตัวเอง ฉันต้องการตรวจสอบแต่ละรายการก่อนส่ง หากการตรวจสอบล้มเหลวแต่ละฟิลด์ที่มีข้อผิดพลาดควรแสดงผลซ้ำ ฉันไม่ได้คิดว่าจะทำอย่างไรกับวิธีแก้ปัญหาที่เสนอของคุณ แต่อาจมีวิธี!
190290000 รูเบิลชาย

1
@ 190290000RubleMan ดูเหมือนว่าจะแตกต่างไปจากคำถามเดิม เปิดคำถามใหม่ที่มีรายละเอียดมากขึ้นและบางทีบางตัวอย่างรหัสและฉันสามารถมีลักษณะต่อมา :)
มาร์คัส-ipse

คุณอาจพบคำตอบนี้มีประโยชน์ : มันใช้สถานะ hooks มันครอบคลุมสถานที่ที่รัฐควรจะสร้างวิธีการหลีกเลี่ยงการแสดงผลแบบฟอร์มในทุกการกดแป้นพิมพ์วิธีการตรวจสอบสนาม ฯลฯ
Andrew

189

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

หากคุณต้องการเข้าถึงสถานะของลูก ๆ ขององค์ประกอบคุณสามารถกำหนดคุณสมบัติที่เรียกrefให้เด็กแต่ละคนได้ ขณะนี้มีสองวิธีในการใช้การอ้างอิง: การใช้React.createRef()และการอ้างอิงกลับ

การใช้ React.createRef()

ปัจจุบันนี้เป็นวิธีที่แนะนำให้ใช้การอ้างอิงตั้งแต่ React 16.3 (ดูเอกสารสำหรับข้อมูลเพิ่มเติม) หากคุณใช้รุ่นก่อนหน้าโปรดดูด้านล่างเกี่ยวกับการอ้างอิงการโทรกลับ

คุณจะต้องสร้างการอ้างอิงใหม่ในตัวสร้างขององค์ประกอบหลักของคุณแล้วกำหนดให้เด็กผ่านทางrefแอตทริบิวต์

class FormEditor extends React.Component {
  constructor(props) {
    super(props);
    this.FieldEditor1 = React.createRef();
  }
  render() {
    return <FieldEditor ref={this.FieldEditor1} />;
  }
}

ในการเข้าถึงการอ้างอิงประเภทนี้คุณจะต้องใช้:

const currentFieldEditor1 = this.FieldEditor1.current;

นี่จะส่งคืนอินสแตนซ์ของคอมโพเนนต์ที่เมาท์ดังนั้นคุณจึงสามารถใช้currentFieldEditor1.stateเพื่อเข้าถึงสถานะ

เพียงบันทึกย่อที่จะบอกว่าถ้าคุณใช้การอ้างอิงเหล่านี้บนโหนด DOM แทนองค์ประกอบ (เช่น<div ref={this.divRef} />) จากนั้นthis.divRef.currentจะส่งคืนองค์ประกอบ DOM พื้นฐานแทนอินสแตนซ์องค์ประกอบ

Refs โทรกลับ

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

ตัวอย่างเช่น:

<FieldEditor
    ref={(fieldEditor1) => {this.fieldEditor1 = fieldEditor1;}
    {...props}
/>

ในตัวอย่างเหล่านี้การอ้างอิงจะถูกเก็บไว้ในองค์ประกอบหลัก ในการเรียกส่วนนี้ในรหัสของคุณคุณสามารถใช้:

this.fieldEditor1

แล้วใช้this.fieldEditor1.stateเพื่อรับสถานะ

สิ่งหนึ่งที่ควรทราบตรวจสอบให้แน่ใจว่าองค์ประกอบลูกของคุณแสดงผลแล้วก่อนที่คุณจะพยายามเข้าถึง ^ _ ^

ดังกล่าวข้างต้นหากคุณใช้การอ้างอิงเหล่านี้บนโหนด DOM แทนองค์ประกอบ (เช่น<div ref={(divRef) => {this.myDiv = divRef;}} />) จากนั้นthis.divRefจะส่งคืนองค์ประกอบ DOM พื้นฐานแทนอินสแตนซ์ขององค์ประกอบ

ข้อมูลเพิ่มเติม

หากคุณต้องการอ่านเพิ่มเติมเกี่ยวกับคุณสมบัติการอ้างอิงของ React ให้ตรวจสอบหน้านี้จาก Facebook

ตรวจสอบให้แน่ใจว่าคุณอ่านหัวข้อ " อย่าใช้มากจนเกินไป " ที่ระบุว่าคุณไม่ควรใช้เด็กstateเพื่อ "ทำให้สิ่งต่าง ๆ เกิดขึ้น"

หวังว่านี่จะช่วยได้ ^ _ ^

แก้ไข: เพิ่มReact.createRef()วิธีการสร้างการอ้างอิง ลบรหัส ES5


5
this.refs.yourComponentNameHereนอกจากนี้ยังมีอาหารอันโอชะน้อยเรียบร้อยคุณสามารถเรียกฟังก์ชั่นจาก ฉันพบว่ามีประโยชน์สำหรับการเปลี่ยนสถานะผ่านฟังก์ชั่น ตัวอย่าง: this.refs.textInputField.clearInput();
Dylan Pierce

7
ระวัง (จากเอกสาร): Refs เป็นวิธีที่ดีที่จะส่งข้อความไปยังอินสแตนซ์ของเด็กโดยเฉพาะอย่างยิ่งในทางที่จะไม่สะดวกที่จะทำสตรีมมิ่งผ่านปฏิกิริยาและprops stateอย่างไรก็ตามสิ่งเหล่านี้ควรไม่ใช่สิ่งที่เป็นนามธรรมของคุณสำหรับข้อมูลที่ไหลผ่านแอปพลิเคชันของคุณ ตามค่าเริ่มต้นให้ใช้การไหลของข้อมูลrefแบบรีแอคทีฟและบันทึกs สำหรับกรณีการใช้งานที่ไม่เกิดปฏิกิริยาโดยเนื้อแท้
ลินน์

2
ฉันจะได้รับสถานะที่กำหนดไว้ภายในนวกรรมิก (ไม่ถึงรัฐวันที่)
Vlado Pandžić

3
การใช้การอ้างอิงในขณะนี้จัดอยู่ในประเภทการใช้ ' ส่วนประกอบที่ไม่มีการควบคุม ' พร้อมกับคำแนะนำให้ใช้ 'ส่วนประกอบที่ควบคุม'
icc97

เป็นไปได้ไหมถ้าองค์ประกอบลูกเป็นconnectedองค์ประกอบRedux ?
jmancherje

7

ในปี2020และคุณจำนวนมากจะมาที่นี่เพื่อค้นหาโซลูชันที่คล้ายกัน แต่ด้วยHooks (พวกเขายอดเยี่ยมมาก!) และด้วยวิธีการล่าสุดในแง่ของความสะอาดของรหัสและไวยากรณ์

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

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

React.useState()ในกรณีนี้ผมคิดว่าวิธีไม่คุ้มค่าและง่ายมากขึ้นเราจะนำผลที่เราต้องการเพียงแค่ใช้ที่มีประสิทธิภาพ

Approach กับ React.useState () hook วิธีที่ง่ายกว่าด้วย Class Components

ในฐานะที่บอกว่าเราจะจัดการกับการเปลี่ยนแปลงและการจัดเก็บข้อมูลขององค์ประกอบที่เด็กของเราในแม่ของเราfieldEditor fieldFormในการทำเช่นนั้นเราจะส่งการอ้างอิงไปยังฟังก์ชั่นที่จะจัดการและนำการเปลี่ยนแปลงไปใช้กับfieldFormรัฐคุณสามารถทำได้ด้วย:

function FieldForm({ fields }) {
  const [fieldsValues, setFieldsValues] = React.useState({});
  const handleChange = (event, fieldId) => {
    let newFields = { ...fieldsValues };
    newFields[fieldId] = event.target.value;

    setFieldsValues(newFields);
  };

  return (
    <div>
      {fields.map(field => (
        <FieldEditor
          key={field}
          id={field}
          handleChange={handleChange}
          value={fieldsValues[field]}
        />
      ))}
      <div>{JSON.stringify(fieldsValues)}</div>
    </div>
  );
}

โปรดทราบว่าReact.useState({})จะส่งคืนอาร์เรย์ที่มีตำแหน่ง 0 ซึ่งเป็นค่าที่ระบุในการโทร (วัตถุว่างเปล่าในกรณีนี้) และตำแหน่งที่ 1 เป็นการอ้างอิงถึงฟังก์ชันที่ปรับเปลี่ยนค่า

ขณะนี้มีองค์ประกอบย่อยFieldEditorคุณไม่จำเป็นต้องสร้างฟังก์ชันด้วยคำสั่ง return ซึ่งจะทำให้ค่าคงที่แบบลีนพร้อมฟังก์ชันลูกศรทำ!

const FieldEditor = ({ id, value, handleChange }) => (
  <div className="field-editor">
    <input onChange={event => handleChange(event, id)} value={value} />
  </div>
);

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

คุณสามารถตรวจสอบคำตอบที่ยอมรับได้เมื่อ 5 ปีที่แล้วและดูว่า Hooks ทำ React code leaner ได้อย่างไร (มาก!)

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


4

ตอนนี้คุณสามารถเข้าถึงสถานะของ InputField ซึ่งเป็นลูกของ FormEditor

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

เมื่อคลิกปุ่มเราเพียงแค่พิมพ์สถานะของฟิลด์อินพุต

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

class InputField extends React.Component{
  handleChange = (event)=> {
    const val = event.target.value;
    this.props.onChange(this.props.id , val);
  }

  render() {
    return(
      <div>
        <input type="text" onChange={this.handleChange} value={this.props.value}/>
        <br/><br/>
      </div>
    );
  }
}       


class FormEditorParent extends React.Component {
  state = {};
  handleFieldChange = (inputFieldId , inputFieldValue) => {
    this.setState({[inputFieldId]:inputFieldValue});
  }
  //on Button click simply get the state of the input field
  handleClick = ()=>{
    console.log(JSON.stringify(this.state));
  }

  render() {
    const fields = this.props.fields.map(field => (
      <InputField
        key={field}
        id={field}
        onChange={this.handleFieldChange}
        value={this.state[field]}
      />
    ));

    return (
      <div>
        <div>
          <button onClick={this.handleClick}>Click Me</button>
        </div>
        <div>
          {fields}
        </div>
      </div>
    );
  }
}

const App = () => {
  const fields = ["field1", "field2", "anotherField"];
  return <FormEditorParent fields={fields} />;
};

ReactDOM.render(<App/>, mountNode);

7
ไม่แน่ใจว่าฉันสามารถลงคะแนนได้ คุณพูดถึงอะไรที่นี่ที่ยังไม่ได้รับคำตอบจาก @ Markus-ipse
Alexander Bird

3

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

ในกรณีที่คุณต้องการเข้าถึงสถานะลูกที่ได้รับการประกาศให้เป็นองค์ประกอบการทำงาน (hooks) คุณสามารถประกาศการอ้างอิงในองค์ประกอบหลักจากนั้นส่งผ่านเป็นแอตทริบิวต์การอ้างอิงไปยังเด็ก แต่คุณต้องใช้React.forwardRefและจากนั้น hook ใช้ImperativeHandleเพื่อประกาศฟังก์ชันที่คุณสามารถเรียกใช้ในคอมโพเนนต์พาเรนต์

ลองดูตัวอย่างต่อไปนี้:

const Parent = () => {
    const myRef = useRef();
    return <Child ref={myRef} />;
}

const Child = React.forwardRef((props, ref) => {
    const [myState, setMyState] = useState('This is my state!');
    useImperativeHandle(ref, () => ({getMyState: () => {return myState}}), [myState]);
})

จากนั้นคุณควรจะได้รับ myState ในองค์ประกอบผู้ปกครองโดยการโทร: myRef.current.getMyState();

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