Làm thế nào để truy cập trạng thái của trẻ em trong React?


214

Tôi có cấu trúc như sau:

FormEditor- giữ nhiều FieldEditor FieldEditor- chỉnh sửa một trường của biểu mẫu và lưu các giá trị khác nhau về trạng thái đó trong trạng thái của nó

Khi một nút được nhấp trong FormEditor, tôi muốn có thể thu thập thông tin về các trường từ tất cả các FieldEditorthành phần, thông tin ở trạng thái của chúng và có tất cả trong FormEditor.

Tôi đã xem xét việc lưu trữ thông tin về các trường bên ngoài FieldEditortrạng thái và thay vào đó đặt nó ở FormEditortrạng thái. Tuy nhiên, điều đó sẽ yêu cầu FormEditorlắng nghe từng FieldEditorthành phần của nó khi chúng thay đổi và lưu trữ thông tin của chúng ở trạng thái.

Tôi không thể chỉ truy cập vào trạng thái của trẻ em? Có lý tưởng không?


4
"Tôi không thể chỉ truy cập vào trạng thái của trẻ em chứ? Nó có lý tưởng không?" Không. Nhà nước là một cái gì đó nội bộ và không nên rò rỉ ra bên ngoài. Bạn có thể thực hiện các phương thức truy cập cho thành phần của mình, nhưng ngay cả điều đó không lý tưởng.
Felix Kling

@FelixKling Sau đó, bạn có gợi ý rằng cách lý tưởng để trẻ giao tiếp với cha mẹ chỉ là sự kiện?
Moshe Revah

3
Vâng, các sự kiện là một cách. Hoặc có luồng dữ liệu một hướng như Flux quảng bá: facebook.github.io/flux
Felix Kling

1
Nếu bạn sẽ không sử dụng FieldEditors riêng biệt, lưu trạng thái của chúng trong FormEditorâm thanh tốt. Nếu đây là trường hợp, các FieldEditorphiên bản của bạn sẽ kết xuất dựa trên propsthông qua trình soạn thảo biểu mẫu của họ chứ không phải của họ state. Một cách phức tạp hơn nhưng linh hoạt hơn là tạo ra một trình tuần tự hóa đi qua bất kỳ phần tử con nào và tìm tất cả các FormEditorthể hiện trong số chúng và tuần tự hóa chúng thành một đối tượng JSON. Đối tượng JSON có thể được lồng tùy ý (nhiều hơn một cấp) dựa trên các mức lồng nhau của các thể hiện trong trình soạn thảo biểu mẫu.
Meglio

4
Tôi nghĩ rằng React Docs 'Nâng trạng thái lên' có lẽ là cách 'Phản ứng' nhất để thực hiện việc này
icc97

Câu trả lời:


135

Nếu bạn đã có trình xử lý onChange cho từng FieldEditor riêng lẻ, tôi không hiểu lý do tại sao bạn không thể chuyển trạng thái lên thành phần FormEditor và chuyển một cuộc gọi lại từ đó đến FieldEditor sẽ cập nhật trạng thái cha. Đó dường như là một cách React-y hơn để làm điều đó, với tôi.

Một cái gì đó dọc theo dòng này có lẽ:

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} />;
};

Bản gốc - phiên bản pre-hook:


2
Điều này hữu ích cho onChange, nhưng không nhiều trong onSubmit.
190290000 Người đàn ông rúp

1
@ 190290000RubleMan Tôi không chắc ý của bạn là gì, nhưng vì các trình xử lý onChange trên các trường riêng lẻ đảm bảo rằng bạn luôn có sẵn dữ liệu biểu mẫu hoàn chỉnh trong trạng thái của mình trong thành phần FormEditor, onSubmit của bạn sẽ chỉ bao gồm một cái gì đó như myAPI.SaveStuff(this.state);. Nếu bạn giải thích thêm một chút, có lẽ tôi có thể cho bạn câu trả lời tốt hơn :)
Markus-ipse

Giả sử tôi có một biểu mẫu với 3 trường thuộc loại khác nhau, mỗi trường có xác nhận riêng. Tôi muốn xác thực từng người trong số họ trước khi gửi. Nếu xác nhận thất bại, mỗi trường có lỗi sẽ xuất hiện lại. Tôi chưa tìm ra cách để làm điều này với giải pháp đề xuất của bạn, nhưng có lẽ có một cách!
190290000 Người đàn ông rúp

1
@ 190290000RubleMan Có vẻ như là một trường hợp khác với câu hỏi ban đầu. Mở một câu hỏi mới với nhiều chi tiết hơn và có lẽ một số mã mẫu, và tôi có thể xem lại sau :)
Markus-ipse

Bạn có thể thấy câu trả lời này hữu ích : nó sử dụng các móc trạng thái, nó bao gồm nơi tạo trạng thái, cách tránh kết xuất lại biểu mẫu trên mỗi lần nhấn phím, cách xác thực trường, v.v.
Andrew

189

Ngay trước khi tôi đi vào chi tiết về cách bạn có thể truy cập trạng thái của một thành phần con, vui lòng đảm bảo đọc câu trả lời của Markus-ipse về một giải pháp tốt hơn để xử lý tình huống cụ thể này.

Nếu bạn thực sự muốn truy cập vào trạng thái của trẻ em của một thành phần, bạn có thể chỉ định một thuộc tính được gọi refcho mỗi đứa trẻ. Hiện tại có hai cách để thực hiện các tham chiếu: Sử dụng React.createRef()và gọi lại refs.

Sử dụng React.createRef()

Đây hiện là cách được khuyến nghị để sử dụng tài liệu tham khảo kể từ React 16.3 (Xem tài liệu để biết thêm thông tin). Nếu bạn đang sử dụng phiên bản cũ hơn, hãy xem bên dưới về các tham chiếu gọi lại.

Bạn sẽ cần tạo một tham chiếu mới trong hàm tạo của thành phần cha mẹ và sau đó gán nó cho một đứa trẻ thông qua refthuộc tính.

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

Để truy cập loại ref này, bạn sẽ cần sử dụng:

const currentFieldEditor1 = this.FieldEditor1.current;

Điều này sẽ trả về một thể hiện của thành phần được gắn kết để bạn có thể sử dụng currentFieldEditor1.stateđể truy cập trạng thái.

Chỉ cần một lưu ý nhanh để nói rằng nếu bạn sử dụng các tham chiếu này trên nút DOM thay vì một thành phần (ví dụ <div ref={this.divRef} />) thì this.divRef.currentsẽ trả về phần tử DOM bên dưới thay vì một thể hiện thành phần.

Gọi lại giới thiệu

Thuộc tính này có chức năng gọi lại được chuyển tham chiếu đến thành phần đính kèm. Cuộc gọi lại này được thực hiện ngay sau khi thành phần được gắn kết hoặc ngắt kết nối.

Ví dụ:

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

Trong các ví dụ này, tham chiếu được lưu trữ trên thành phần cha. Để gọi thành phần này trong mã của bạn, bạn có thể sử dụng:

this.fieldEditor1

và sau đó sử dụng this.fieldEditor1.stateđể có được nhà nước.

Một điều cần lưu ý, đảm bảo thành phần con của bạn đã được hiển thị trước khi bạn cố gắng truy cập nó ^ _ ^

Như trên, nếu bạn sử dụng các tham chiếu này trên một nút DOM thay vì một thành phần (ví dụ <div ref={(divRef) => {this.myDiv = divRef;}} />) thì this.divRefsẽ trả về phần tử DOM bên dưới thay vì một thể hiện thành phần.

Thêm thông tin

Nếu bạn muốn đọc thêm về tài sản ref của React, hãy xem trang này từ Facebook.

Hãy chắc chắn rằng bạn đã đọc phần " Đừng lạm dụng giới thiệu " nói rằng bạn không nên sử dụng trẻ em stateđể "làm cho mọi thứ xảy ra".

Hy vọng điều này sẽ giúp ^ _ ^

Chỉnh sửa: Đã thêm React.createRef()phương thức để tạo ref. Đã xóa mã ES5.


5
Ngoài ra tidbit nhỏ gọn, bạn có thể gọi các chức năng từ this.refs.yourComponentNameHere. Tôi đã thấy nó hữu ích cho việc thay đổi trạng thái thông qua các chức năng. Ví dụ: this.refs.textInputField.clearInput();
Dylan Pierce

7
Cẩn thận (từ các tài liệu): Refs là một cách tuyệt vời để gửi tin nhắn đến một cá thể con cụ thể theo cách bất tiện để thực hiện thông qua truyền phát Reactive propsstate. Tuy nhiên, chúng không nên là sự trừu tượng hóa để truyền dữ liệu qua ứng dụng của bạn. Theo mặc định, sử dụng luồng dữ liệu Phản ứng và lưu refs cho các trường hợp sử dụng vốn không phản ứng.
Lynn

2
Tôi chỉ nhận trạng thái được xác định bên trong hàm tạo (không cập nhật trạng thái)
Vlado Pandžić

3
Sử dụng ref bây giờ được phân loại là sử dụng ' Các thành phần không được kiểm soát ' với khuyến nghị sử dụng 'Các thành phần được kiểm soát'
icc97

Có thể làm điều này nếu thành phần con là connectedthành phần Redux ?
jmancherje

6

Năm 2020 và rất nhiều bạn sẽ đến đây để tìm kiếm một giải pháp tương tự nhưng với Hook (Chúng rất tuyệt!) Và với các phương pháp mới nhất về độ sạch của mã và cú pháp.

Vì vậy, như các câu trả lời trước đây đã nêu, cách tiếp cận tốt nhất cho loại vấn đề này là giữ trạng thái bên ngoài thành phần con fieldEditor. Bạn có thể làm điều đó theo nhiều cách.

"Phức tạp" nhất là với bối cảnh toàn cầu (trạng thái) mà cả cha mẹ và con cái đều có thể truy cập và sửa đổi. Đây là một giải pháp tuyệt vời khi các thành phần nằm rất sâu trong hệ thống phân cấp cây và do đó rất tốn kém để gửi đạo cụ ở mỗi cấp.

Trong trường hợp này tôi nghĩ rằng nó không đáng, và cách tiếp cận đơn giản hơn sẽ mang lại cho chúng tôi kết quả mà chúng tôi muốn, chỉ cần sử dụng mạnh mẽ React.useState().

Cách tiếp cận với hook React.useState (), cách đơn giản hơn so với các thành phần Class

Như đã nói, chúng tôi sẽ đối phó với các thay đổi và lưu trữ dữ liệu của thành phần con fieldEditortrong cha mẹ của chúng tôi fieldForm. Để làm điều đó, chúng tôi sẽ gửi một tham chiếu đến hàm sẽ xử lý và áp dụng các thay đổi cho fieldFormtrạng thái, bạn có thể làm điều đó với:

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>
  );
}

Lưu ý rằng React.useState({})sẽ trả về một mảng với vị trí 0 là giá trị được chỉ định trong cuộc gọi (Đối tượng trống trong trường hợp này) và vị trí 1 là tham chiếu đến hàm sửa đổi giá trị.

Bây giờ với thành phần con FieldEditor, bạn thậm chí không cần tạo hàm với câu lệnh return, hằng số nạc có hàm mũi tên sẽ làm được!

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

Chúng tôi đã hoàn thành, không có gì hơn, chỉ với hai thành phần chức năng chất nhờn này, chúng tôi có mục tiêu cuối cùng là "truy cập" FieldEditorgiá trị con của chúng tôi và thể hiện điều đó ở cha mẹ.

Bạn có thể kiểm tra câu trả lời được chấp nhận từ 5 năm trước và xem cách Hook tạo mã React gọn hơn (Rất nhiều!).

Hy vọng câu trả lời của tôi giúp bạn tìm hiểu và hiểu thêm về Móc, và nếu bạn muốn kiểm tra một ví dụ hoạt động ở đây .


4

Bây giờ Bạn có thể truy cập trạng thái của InputField, con của FormEditor.

Về cơ bản bất cứ khi nào có sự thay đổi trạng thái của trường đầu vào (con), chúng ta sẽ nhận được giá trị từ đối tượng sự kiện và sau đó chuyển giá trị này cho Parent trong đó ở trạng thái trong Parent được đặt.

Khi nhấn vào nút, chúng tôi chỉ in trạng thái của các trường Nhập liệu.

Điểm mấu chốt ở đây là chúng tôi đang sử dụng các đạo cụ để lấy id / giá trị của Trường đầu vào và cũng để gọi các hàm được đặt làm thuộc tính trên Trường đầu vào trong khi chúng tôi tạo các trường Nhập liệu con có thể sử dụng lại.

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
Không chắc chắn tôi có thể nâng cấp cái này. Bạn đề cập gì ở đây chưa được trả lời bởi @ Markus-ipse?
Alexander Bird

3

Như các câu trả lời trước đã nói, hãy cố gắng chuyển trạng thái sang thành phần hàng đầu và sửa đổi trạng thái thông qua các cuộc gọi lại được truyền cho trẻ em.

Trong trường hợp bạn thực sự cần truy cập vào trạng thái con được khai báo là thành phần chức năng (hook), bạn có thể khai báo ref trong thành phần cha, sau đó chuyển nó làm thuộc tính ref cho con nhưng bạn cần sử dụng React.forwardRef và sau đó hook useImperativeHandle để khai báo một hàm bạn có thể gọi trong thành phần cha.

Hãy xem ví dụ sau:

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]);
})

Sau đó, bạn sẽ có thể nhận myState trong thành phần Parent bằng cách gọi: myRef.current.getMyState();

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.