React 替代的支撐鑽探方式(反向,從子級到父級)處理表單
<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 } 從 'react';
import { Link, Form, useNavigate, useSubmit } 從 'react-router-dom';
import FormGroup from '../components/UI/FormGroup';
import Button from '../components/UI/Button';
import Card from '../components/UI/Card';
import './Login.scss';
function LoginPage() {
const navigate = useNavigate();
const submit = useSubmit();
const [isLoginValid, setIsLoginValid] = useState(false);
const [isPasswordValid, setIsPasswordValid] = useState(false);
var resetLoginInput = null;
var resetPasswordInput = null;
let isFormValid = false;
if(isLoginValid && isPasswordValid) {
isFormValid = true;
}
function formSubmitHandler(event) {
event.preventDefault();
if(!isFormValid) {
return;
}
resetLoginInput();
resetPasswordInput();
submit(event.currentTarget);
}
function loginValidityChangeHandler(isValid) {
setIsLoginValid(isValid);
}
function passwordValidityChangeHandler(isValid) {
setIsPasswordValid(isValid);
}
function resetLoginInputHandler(reset) {
resetLoginInput = reset;
}
function resetPasswordInputHandler(reset) {
resetPasswordInput = reset;
}
function switchToSignupHandler() {
navigate('/signup');
}
return (
<div className="login">
<div className="login__logo">
Go Cup
</div>
<p className="login__description">
登入您的 Go Cup 帳戶
</p>
<卡片邊框>
<表單onSubmit={formSubmitHandler}>
<表單組
id=“登入”
label="使用者名稱或電子郵件地址"
輸入屬性={{
類型:“文字”,
名稱:“登入”,
有效性:(值)=> {
值 = value.trim();
如果(!值){
return [false, '需要使用者名稱或電子郵件地址。']
} else if(value.length < 3 || value.length > 30) {
return [false, '使用者名稱或電子郵件地址必須至少有 3 個字符,最多 30 個字符'];
} 別的 {
返回[真,空];
}
},
onValidityChange:loginValidityChangeHandler,
onReset:重置LoginInputHandler
}}
>>
<表單組
id=“密碼”
標籤=“密碼”
sideLabelElement={
<連結到=“/密碼重設”>
忘記密碼?
連結>
}
輸入屬性={{
輸入:“密碼”,
名稱:“密碼”,
有效性:(值)=> {
值 = value.trim();
如果(!值){
return [false, '需要密碼。']
} else if(value.length < 4 || value.length > 1024) {
return [false, '密碼長度必須至少為 4 個或最多 1024 個字元。'];
} 別的 {
返回[真,空];
}
},
onValidityChange:passwordValidityChangeHandler,
onReset:重設密碼輸入處理程序
}}>>
<按鈕類別名稱=“w-100”類型=“提交”>
登入
</按鈕>
或者
</span>
<按鈕類別名稱=“w-100” onClick={switchToSignupHandler}>
報名
</按鈕>
</表格>
</卡>
;
);
}
export default LoginPage;
</pre>
<p>如您在上面的程式碼中所看到的,我使用FormGroup 元件並傳遞<code>onValidityChange</code> 和<code>onReset</code> 屬性來取得<code>isValid< code> 值的更新值。表單提交後重置輸入的變更和重置函數等。使用我的自訂掛鉤 useInput 在輸入元件中建立 <code>isValid</code> 和 <code>reset</code> 函數。我在值變更時傳遞 isValid 值,並使用 FormGroup 元件中定義的 props 從輸入元件傳遞重設函數。我還在登入頁面中使用 <code>isLoginValid</code> 和 <code>isPasswordValid</code> states defiend 來儲存從子輸入元件傳遞的更新的 <code>isValid</code> 狀態值。因此,我已經在輸入元件中定義了狀態,並使用 props 將它們傳遞給父元件,並將它們的值儲存在該父元件中建立的其他狀態中。正在進行的道具鑽孔讓我感覺有點不舒服。 </p>
<p>狀態是在輸入元件內部管理的,我有這些狀態:</p>
<ul>
<li><strong>值:</strong>輸入元素的值。 </li>
<li><strong>isInputTouched</strong>:確定使用者是否已觸摸/聚焦輸入,以確定是否顯示驗證錯誤訊息(如果有)。 </li>
</ul>
<p>我將一些函數(例如傳遞給輸入組件的驗證函數)組合併應用到這兩個狀態,以創建其他變數值來收集有關輸入及其有效性的信息,例如該值是否有效(isValid )、是否有訊息驗證(訊息),如果輸入有效(<code>isInputValid = isValid || !isInputTouched</code>)來決定顯示驗證訊息。</p>
<p>這些狀態和值在我建立的自訂掛鉤 <code>useInput</code> 中管理,如下所示:</p>
<p><em>hooks/use-state.js</em></p>
<pre class="brush:js;toolbar:false;">import { useState, useCallback } 從 'react';
function useInput(validityFn) {
const [value, setValue] = useState('');
const [isInputTouched, setIsInputTouched] = useState(false);
const [isValid, message] = typeof validityFn === 'function' ? 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 reset = useCallback(() => {
setValue('');
setIsInputTouched(false);
}, []);
return {
value,
isValid,
isInputValid,
message,
inputChangeHandler,
inputBlurHandler,
reset
};
}
export default useInput;
</pre>
<p>我目前在 Input.js 中使用這個自訂鉤子,如下所示:</p>
<p><em>components/UI/Input.js</em></p>
<pre class="brush:js;toolbar:false;">import { useEffect } from 'react';
import useInput from '../../hooks/use-input';
import './Input.scss';
function Input(props) {
const {
value,
isValid,
isInputValid,
message,
inputChangeHandler,
inputBlurHandler,
reset
} = useInput(props.validity);
const {
onIsInputValidOrMessageChange,
onValidityChange,
onReset
} = props;
let className = 'form-control';
if(!isInputValid) {
className = `${className} form-control--invalid`;
}
if(props.className) {
className = `${className} ${props.className}`;
}
useEffect(() => {
if(onIsInputValidOrMessageChange && typeof onIsInputValidOrMessageChange === 'function') {
onIsInputValidOrMessageChange(isInputValid, message);
}
}, [onIsInputValidOrMessageChange, isInputValid, message]);
useEffect(() => {
if(onValidityChange && typeof onValidityChange === 'function') {
onValidityChange(isValid);
}
}, [onValidityChange, isValid]);
useEffect(() => {
if(onReset && typeof onReset === 'function') {
onReset(reset);
}
}, [onReset, reset]);
return (
<input
{...props}
className={className}
value={value}onChange={inputChangeHandler}
onBlur={inputBlurHandler}
/>
);
}
export default Input;
</pre>
<p>在輸入元件中,我直接使用 <code>isInputValid</code> 狀態將無效的 CSS 類別加入輸入。但我也會<code>isInputValid</code>、<code>message</code>、<code>isValid</code> 狀態和<code>reset</code>/code> 狀態和<code>reset</code>在其中使用。為了傳遞這些狀態和函數,我使用在props 中定義的<code>onIsInputValidOrMessageChange</code>、<code>onValidityChange</code>、<code>onReset</code>/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 } 從 'react';
import Input from './Input';
import './FormGroup.scss';
function FormGroup(props) {
const [message, setMessage] = useState(null);
const [isInputValid, setIsInputValid] = useState(false);
let className = 'form-group';
if(props.className) {
className = `form-group ${props.className}`;
}
let labelCmp = (
<label htmlFor={props.id}>
{props.label}
</label>
);
if(props.sideLabelElement) {
labelCmp = (
<div className="form-label-group">
{labelCmp}
{props.sideLabelElement}
</div>
);
}
function isInputValidOrMessageChangeHandler(changedIsInputValid, changedMessage) {
setIsInputValid(changedIsInputValid);
setMessage(changedMessage);
}
return (
<div className={className}>
{labelCmp}
<Input
id={props.id}
onIsInputValidOrMessageChange={isInputValidOrMessageChangeHandler}
{...props.inputProps}
/>
{!isInputValid && <p>{message}</p>}
</div>
);
}
export default FormGroup;
</pre>
<p>從上面的程式碼可以看到,我定義了<code>message</code> 和<code>isInputValid</code> 狀態來儲存更新的<code>message</code> 和; 狀態來儲存更新的<code>message</code> 和; <code>isInputValid</code> code> 從輸入元件傳遞的狀態。我已經在輸入元件中定義了 2 個狀態來保存這些值,但我需要在此元件中定義另外 2 個狀態來儲存輸入元件中更新和傳遞的值。這有點奇怪,對我來說似乎不是最好的方式。 </p>
<p><strong>問題是:</strong>我想我可以使用 React Context (useContext) 或 React Redux 來解決這裡的 prop 鑽取問題。但我不確定我目前的狀態管理是否不好,是否可以使用 React Context 或 React Redux 來改善。因為根據我的了解,React Context 在狀態頻繁變化的情況下可能會很糟糕,但如果 Context 在應用程式範圍內使用,那麼這是有效的。在這裡,我可以建立一個上下文來儲存和更新整個表單,從而實現表單範圍內的擴充。另一方面,React Redux 可能不是最適合的孤島,並且可能有點矯枉過正。你們有什麼感想?對於這種特定情況,什麼可能是更好的替代方案? </p>
<p><strong>注意:</strong>由於我是 React 的新手,因此我願意接受您關於我所有編碼的所有建議,從簡單的錯誤到一般的錯誤。謝謝! </p>