javascript - Reactで子コンポーネントの状態にアクセスする方法
私は次の構造を持っています:
FormEditor - 複数の FieldEditor インスタンスを保持します FieldEditor - フォームのフィールドを編集し、その状態にさまざまな値を保存します
FormEditor 内でボタンがクリックされたときに、すべての FieldEditor コンポーネントから、彼らの状態にあるフィールドに関する情報を収集し、それをすべて FormEditor 内に持ちたいです。
私は、FieldEditor の状態の外でフィールドに関する情報を保存し、それを FormEditor の状態に入れることを検討しました。しかし、それを行うには、FormEditor が各 FieldEditor コンポーネントの変更を監視し、その情報を自分の状態に保存する必要があります。
代わりに子供の状態に直接アクセスすることはできないのでしょうか?それは理想的ですか?
回答 #1
子コンポーネントの状態にアクセスする方法の詳細に入る前に、この特定のシナリオを扱うより良い解決策に関するMarkus-ipseの回答を必ず読んでください。
もし本当にコンポーネントの子の状態にアクセスしたい場合は、各子に ref というプロパティを割り当てることができます。参照を実装する方法は現在2つあります:React.createRef() を使用する方法と、コールバック refs を使用する方法です。
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 要素を返します。
コールバック参照
このプロパティは、アタッチされたコンポーネントへの参照が渡されるコールバック関数を受け取ります。このコールバックは、コンポーネントがマウントまたはアンマウントされた直後に実行されます。
例えば:
<FieldEditor
ref={(fieldEditor1) => {this.fieldEditor1 = fieldEditor1;}
{...props}
/>
これらの例では、参照は親コンポーネントに保存されます。コード内でこのコンポーネントを呼び出すには、次のように使用できます:
this.fieldEditor1
そして、状態を取得するには this.fieldEditor1.state を使用します。
1つ注意することは、子コンポーネントにアクセスしようとする前に、それがレンダリングされていることを確認することです ^_^
上記のように、これらの参照をコンポーネントではなくDOMノードで使用すると(例: <div ref={(divRef) => {this.myDiv=divRef;}} />)、this.divRefはコンポーネントインスタンスではなく、基盤となるDOM要素を返します。
詳細情報
もしReactのrefプロパティについてもっと読みたい場合は、このFacebookのページをチェックしてください。
子供の state を「物事を起こす」ために使うべきではないと書かれている「Refsを使いすぎない」セクションを必ず読んでください。
回答 #2
もしすでに個々の FieldEditors に対する onChange ハンドラーを持っているなら、状態を FormEditor コンポーネントに移して、そこから FieldEditors に親の状態を更新するコールバックを渡すことができない理由はないと思います。私には、それがより React 的な方法のように思えます。
おそらくこんな感じのものです:
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 the ability to dynamically add/remove fields, keep the list in state
const App = () => {
const fields = ["field1", "field2", "anotherField"];
return <FormEditor fields={fields} />;
};
オリジナル - プリフック版:
class FieldEditor extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
const text = event.target.value;
this.props.onChange(this.props.id, text);
}
render() {
return (
<div className="field-editor">
<input onChange={this.handleChange} value={this.props.value} />
</div>
);
}
}
class FormEditor extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.handleFieldChange = this.handleFieldChange.bind(this);
}
handleFieldChange(fieldId, value) {
this.setState({ [fieldId]: value });
}
render() {
const fields = this.props.fields.map(field => (
<FieldEditor
key={field}
id={field}
onChange={this.handleFieldChange}
value={this.state[field]}
/>
));
return (
<div>
{fields}
<div>{JSON.stringify(this.state)}</div>
</div>
);
}
}
// Convert to a class component and add the ability to dynamically add/remove fields by having it in state
const App = () => {
const fields = ["field1", "field2", "anotherField"];
return <FormEditor fields={fields} />;
};
ReactDOM.render(<App />, document.body);回答 #3
前の回答が言ったように、状態を上位コンポーネントに移動し、子コンポーネントに渡されたコールバックを通して状態を変更するようにしてください。
もし本当にフックとして宣言された子コンポーネントの状態にアクセスする必要がある場合、親コンポーネントで ref を宣言し、それを ref 属性として子に渡すことができます。ただし、その際には React.forwardRef を使用し、さらにフック useImperativeHandle を使って、親コンポーネントから呼び出せる関数を宣言する必要があります。
次の例を見てください:
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();回答 #4
2020年で、多くの皆さんがここに訪れて、同様の解決策を探すでしょうが、今回はHooks(素晴らしいです!)と、コードの可読性や構文の最新のアプローチを使ったものを求めるでしょう。
ですので、前の回答が述べていたように、この種の問題に対する最適なアプローチは、状態を子コンポーネント fieldEditor の外で保持することです。その方法はいくつかあります。
最も「複雑」なのは、親と子の両方がアクセスして変更できるグローバルなコンテキスト(状態)を使う場合です。コンポーネントがツリー階層の非常に深い位置にあり、各レベルで props を渡すのがコスト高になる場合に、これは素晴らしい解決策です。
この場合、それは価値がないと思いますし、よりシンプルなアプローチの方が、強力な React.useState() を使うだけで私たちが望む結果をもたらしてくれるでしょう。
React.useState() フックを使ったアプローチ - クラスコンポーネントよりずっと簡単
述べたように、私たちは変更を処理し、子コンポーネント 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 のコードをはるかにすっきりさせたかを見ることができます。
私の答えがフックについて学び、理解するのに役立つことを願っています。そして、動作する例を確認したい場合は、こちらです。
