<p>私は React を初めて使用しており、いくつかの実践的なプロジェクトを通じて学習しています。現在、フォームの処理と検証に取り組んでいます。 SPA で React Router の Form コンポーネントを使用しています。フォーム内には、ラベル入力とエラー メッセージをレンダリングする FormGroup 要素があります。また、FormGroup コンポーネント内で独自の入力コンポーネントを使用して、フォームで使用される入力のロジックと状態管理を分離します。 </p>
<p>そこで、次のようにサンプル ログイン ページに Form コンポーネントと FormGroup コンポーネントを配置しました。 </p>
<p><em>pages/Login.js</em></p>
<pre class="brush:js;toolbar:false;">import { useState } from 'react';
import { Link, Form, useNavigate, useSubmit } from 'react-router-dom';
FormGroup を '../components/UI/FormGroup' からインポートします。
'../components/UI/Button' からボタンをインポートします。
カードを「../components/UI/Card」からインポートします。
インポート './Login.scss';
関数 LoginPage() {
const navigate = useNavigate();
const submit = useSubmit();
const [isLoginValid, setIsLoginValid] = useState(false);
const [isPasswordValid, setIsPasswordValid] = useState(false);
varresetLoginInput = null;
varresetPasswordInput = null;
isFormValid = false;
if(isLoginValid && isPasswordValid) {
isFormValid = true;
}
関数 formSubmitHandler(event) {
イベント.preventDefault();
if(!isFormValid) {
戻る;
}
リセットログイン入力();
リセットパスワード入力();
submit(event.currentTarget);
}
関数loginValidityChangeHandler(isValid) {
setIsLoginValid(isValid);
}
関数passwordValidityChangeHandler(isValid) {
setIsPasswordValid(isValid);
}
関数resetLoginInputHandler(reset) {
リセットログイン入力 = リセット;
}
関数resetPasswordInputHandler(reset) {
リセットパスワード入力 = リセット;
}
関数 switchToSignupHandler() {
ナビゲート('/サインアップ');
}
戻る (
<div className="ログイン">
<div className="login__logo">
囲碁カップ
</div>
<p className="login__description">
Go Cup アカウントにログインします
</p>
<カード枠>
<フォーム onSubmit={formSubmitHandler}>
<フォームグループ
id="ログイン"
label="ユーザー名または電子メール アドレス"
inputProps={{
タイプ: "テキスト"、
名前: 「ログイン」、
有効性: (値) => {
値 = 値.trim();
if(!値) {
return [false, 'ユーザー名または電子メール アドレスが必要です。']
} else if(値.長さ < 3 || 値.長さ > 30) {
return [false, 'ユーザー名または電子メール アドレスは少なくとも 3 文字、最大 30 文字である必要があります'];
} それ以外 {
戻り値 [true、null];
}
}、
onValidityChange:loginValidityChangeHandler、
onReset:resetLoginInputHandler
}}
/>
<フォームグループ
id="パスワード"
ラベル=「パスワード」
サイドラベル要素={
<リンク先="/password-reset">
パスワードをお忘れですか?
</リンク>
}
inputProps={{
タイプ: 「パスワード」、
名前: "パスワード"、
有効性: (値) => {
値 = 値.trim();
if(!値) {
return [false, 'パスワードが必要です。']
} else if(値.長さ < 4 || 値.長さ > 1024) {
return [false, 'パスワードは 4 文字以上、最大 1024 文字でなければなりません。'];
} それ以外 {
戻り値 [true、null];
}
}、
onValidityChange:passwordValidityChangeHandler、
onReset:resetPasswordInputHandler
}}/>
<div className="text-center">
<ボタンクラス名="w-100" type="送信">
ログイン
</ボタン>
<span className="login__or">
または
</span>
<ボタンクラス名="w-100" onClick={switchToSignupHandler}>
サインアップ
</ボタン>
</div>
</フォーム>
</カード>
</div>
);
}
デフォルトのログインページをエクスポートします。
</pre>
<p>上記のコードでわかるように、FormGroup コンポーネントを使用し、<code>onValidityChange</code> プロパティと <code>onReset</code> プロパティを渡して <code>isValid</ を取得します。 code> value の更新された値。フォーム送信後の入力をリセットする機能の変更・リセット機能等カスタム フック useInput を使用して、入力コンポーネントに <code>isValid</code> 関数と <code>reset</code> 関数を作成します。値が変更されたときに isValid 値を渡し、FormGroup コンポーネントで定義された props を使用して入力コンポーネントからリセット関数を渡します。また、ログイン ページで <code>isLoginValid</code> および <code>isPasswordValid</code> 状態定義を使用して、子の入力から渡された更新された <code>isValid</code> 状態値を保存しています。成分。そこで、入力コンポーネントで状態を定義し、props を使用してそれらを親コンポーネントに渡し、その値をその親コンポーネントで作成された他の状態に保存しました。進行中の支柱の穴あけ作業は少し不快な気分になりました。 </p>
<p>状態は入力コンポーネント内で管理されます。次の状態があります: </p>
<li><strong>値: </strong>要素の値を入力します。 </li>
<li><strong>isInputTouched</strong>: ユーザーが入力をタッチ/フォーカスしたかどうかを判断して、検証エラー メッセージ (存在する場合) を表示するかどうかを決定します。 </li>
</ul>
<p>いくつかの関数 (入力コンポーネントに渡される検証関数など) を組み合わせてこれら 2 つの状態に適用し、他の変数値を作成して、入力とその有効性 (値が有効かどうかなど) に関する情報を収集します。 (isValid)、メッセージ検証 (message) の有無、入力が有効な場合 (<code>isInputValid = isValid || !isInputTouched</code>) で検証メッセージを表示するかどうかを決定します。</p>
<p>これらの状態と値は、次のように、私が作成したカスタム フック <code>useInput</code> で管理されます。
<p><em>hooks/use-state.js</em></p>
<pre class="brush:js;toolbar:false;">import { useState, useCallback } from 'react';
関数 useInput(validityFn) {
const [値, setValue] = useState('');
const [isInputTouched, setIsInputTouched] = useState(false);
const [isValid, message] = typeof validityFn === '関数' ? validityFn(value) : [true, null];
const isInputValid = isValid || !isInputTouched;
const inputChangeHandler = useCallback(event => {
setValue(event.target.value);
if(!isInputTouched) {
setIsInputTouched(true);
}
}, [isInputTouched]);
const inputBlurHandler = useCallback(() => {
setIsInputTouched(true);
}、[]);
constリセット = useCallback(() => {
setValue('');
setIsInputTouched(false);
}、[]);
戻る {
価値、
有効です、
入力有効です、
メッセージ、
inputChangeHandler、
inputBlurHandler、
リセット
};
}
デフォルトの useInput をエクスポートします。
</pre>
<p>現在、Input.js でこのカスタム フックを次のように使用しています。</p>
<p><em>components/UI/Input.js</em></p>
<pre class="brush:js;toolbar:false;">import { useEffect } from 'react';
useInput を '../../hooks/use-input' からインポートします。
インポート './Input.scss';
関数入力(小道具) {
定数{
価値、
有効です、
入力有効です、
メッセージ、
inputChangeHandler、
inputBlurHandler、
リセット
= useInput(props.validity);
定数{
onIsInputValidOrMessageChange、
onValidityChange、
オンリセット
} = 小道具;
let className = 'フォームコントロール';
if(!isInputValid) {
className = `${className} フォームコントロール --invalid`;
}
if(props.className) {
className = `${className} ${props.className}`;
}
useEffect(() => {
if(onIsInputValidOrMessageChange && typeof onIsInputValidOrMessageChange === '関数') {
onIsInputValidOrMessageChange(isInputValid, メッセージ);
}
}, [onIsInputValidOrMessageChange, isInputValid, メッセージ]);
useEffect(() => {
if(onValidityChange && typeof onValidityChange === '関数') {
onValidityChange(isValid);
}
}, [onValidityChange, isValid]);
useEffect(() => {
if(onReset && typeof onReset === '関数') {
onReset(リセット);
}
}, [onReset、リセット]);
戻る (
<入力
{...小道具}
クラス名={クラス名}
値={値}onChange={inputChangeHandler}
onBlur={inputBlurHandler}
/>
);
}
デフォルトの入力をエクスポートします。
</pre>
<p>入力コンポーネントで、<code>isInputValid</code> 状態を直接使用して、無効な CSS クラスを入力に追加します。ただし、<code>isInputValid</code>、<code>message</code>、<code>isValid</code> ステータス、および <code>reset</code> 関数も親コンポーネントに渡します。その中で使用されます。これらの状態と関数を渡すには、プロパティで定義された <code>onIsInputValidOrMessageChange</code>、<code>onValidityChange</code>、<code>onReset</code> 関数を使用します (プロパティはドリルダウンしますが、代わりに、子から親へ)。 </p>
<p>これは FormGroup コンポーネントの定義と、FormGroup 内の入力状態を使用して検証メッセージ (存在する場合) を表示する方法です。 </p>
<p><em>components/UI/FormGroup.js</em></p>
<pre class="brush:js;toolbar:false;">import { useState } from 'react';
'./Input' から入力をインポートします。
インポート './FormGroup.scss';
関数 FormGroup(props) {
const [メッセージ、setMessage] = useState(null);
const [isInputValid, setIsInputValid] = useState(false);
let className = 'フォームグループ';
if(props.className) {
className = `フォームグループ ${props.className}`;
}
labelCmp = (
<label htmlFor={props.id}>
{props.label}
</ラベル>
);
if(props.sideLabelElement) {
labelCmp = (
<div className="フォームラベルグループ">
{ラベルCmp}
{props.sideLabelElement}
</div>
);
}
関数 isInputValidOrMessageChangeHandler(changedIsInputValid,ChangedMessage) {
setIsInputValid(changedIsInputValid);
setMessage(changedMessage);
}
戻る (
<div クラス名={クラス名}>
{ラベルCmp}
<入力
id={props.id}
onIsInputValidOrMessageChange={isInputValidOrMessageChangeHandler}
{...props.inputProps}
/>
{!isInputValid && <p>{メッセージ}</p>}
</div>
);
}
デフォルトの FormGroup をエクスポートします。
</pre>
<p>上記のコードからわかるように、更新された <code>message</code> を保存するために <code>message</code> および <code>isInputValid</code> 状態を定義しました。 <code>isInputValid</code> code> 入力コンポーネントから渡された状態。これらの値を保持するために入力コンポーネントで 2 つの状態を定義しましたが、更新されて渡された値を入力コンポーネントに保存するには、このコンポーネントで別の 2 つの状態を定義する必要があります。これは少し奇妙で、私にとって最善の方法とは思えません。 </p>
<p><strong>質問は次のとおりです: </strong>ここでのプロップドリルの問題を解決するには、React Context (useContext) または React Redux を使用できると思います。しかし、現在の状態管理が悪く、React Context または React Redux を使用して改善できるかどうかはわかりません。なぜなら、私が理解しているところによると、React Context は状態が頻繁に変化する状況ではひどいものになる可能性がありますが、Context がアプリケーション全体で使用されている場合は機能するからです。ここで、フォーム全体を保存および更新するためのコンテキストを作成し、フォーム全体の拡張を可能にします。一方、React Redux はサイロに最適ではない可能性があり、少しやりすぎである可能性があります。どう思いますか?この特定の状況に対して、より良い代替手段は何でしょうか? </p>
<p><strong>注: </strong>私は React を初めて使用するため、単純な間違いから一般的な間違いまで、すべてのコーディングに関するあらゆる提案を歓迎します。ありがとう! </p>
React の状態管理については、制御された状態と制御されていない状態の 2 つの主な考え方があります。制御されたフォームは、どこからでも値にアクセスして反応性を提供できる React コンテキストを使用して制御できます。ただし、制御された入力は、特に各入力でフォーム全体を更新する場合にパフォーマンスの問題を引き起こす可能性があります。ここで、制御されていない形式が登場します。このパラダイムでは、すべての状態管理でブラウザのネイティブ機能を利用して状態を表示する必要があります。このアプローチの主な問題は、フォームの React の側面が失われ、送信時にフォーム データを手動で収集する必要があり、そのための複数の参照を維持するのが面倒になる可能性があることです。
制御された入力は次のようになります:
リーリーEDIT: @Arkellys が指摘したように、フォーム データを収集するために参照は必ずしも必要ありません。これは
を使用した例です。FormData
そして制御不能:
リーリーこれら 2 つの例から、どちらの方法を使用しても複数コンポーネントのフォームを維持するのは面倒であることが明らかであるため、フォームの管理を支援するためにライブラリがよく使用されます。私は個人的に React Hook Form を、十分にテストされ、メンテナンスが行き届いた、使いやすいフォーム ライブラリとして推奨します。最適なパフォーマンスを実現するために制御されていない形式を採用しながらも、単一の入力を監視してリアクティブ レンダリングを行うことができます。
Redux、React コンテキスト、またはその他の状態管理システムのいずれを使用するかについては、正しく実装されていると仮定すると、パフォーマンスの点で通常は違いはありません。 flux アーキテクチャ が好きなら、ぜひ Redux を使用してください。ただし、ほとんどの場合、React コンテキストはパフォーマンスが高く、十分です。
あなたの
useInput
カスタム フックは、問題を解決しようとする勇敢だが見当違いの試みのように見えますreact-hook-form
およびreact-final-form
コード > すでに解決されています。この抽象化により、不必要な複雑さと予測不可能な 副作用が生じます。さらに、ミラー小道具 a> これは、React ではアンチパターンであることがよくあります。独自のフォーム ロジックを本当に実装したい場合 (教育目的でない限り、実装しないことをお勧めします)、次のガイドラインに従うことができます。
真実の情報源を最も共通の祖先に保つ-
状態のミラーリングとコピーを避ける-
useMemo- と
useRef
を使用して、再レンダリングを最小限に抑えますこれは、Redux のようなパブリッシュ/サブスクライブ ライブラリを使用するか、コンポーネント ツリーを通じて状態を伝播するかを決定するために使用する簡単な側面です。
2 つのコンポーネントに親子関係があり、互いに最大 2 エッジ離れている場合、子の状態を親に伝播します
親 -> 子 1 レベル 1 -> 子 1 レベル 2 ------ OK
親 -> 子1-レベル1 ------ OK
親 -> 子 1 レベル 1 -> 子 1 レベル 2 -> 子 1 レベル 3 --> ステータスを子 1 レベル 3 から親に変更するにはトリップが多すぎます
導入以来