reactjs - 在 React 中填写复杂表单时设置对象属性的最佳方法是什么
问题描述
我是 React 的新手。我有一个看似标准的任务:填写有关用户的表单数据并将其发送到服务器。表单是一组组件,例如:基本信息、护照数据、爱好等+保存按钮。
我是通过以下方式做到的。有一个class
描述模型。创建组件时,我class
在useRef
. 此外,我model
通过它们的道具将此变量传递给所有子组件。模型属性填充在组件中。所以当我点击Save
按钮时,我已经填写了模型属性。这是一个例子。
请告诉我,这是填充复杂对象数据的好方法吗?也许以不同的方式做会更好?有没有最佳实践?也许我应该使用 redux 来完成这项任务?
模型.ts
class Model {
// Component 1
firstName: string;
lastName: string;
// Component 2
passport: string;
address: string;
}
interface IComponent {
model: Model;
}
export { Model, IComponent };
索引.tsx
export const App: React.FunctionComponent<{}> = () =>
{
const model = useRef<Model>(new Model());
const save = () =>{
console.log(model.current);
}
return (
<React.Fragment>
<Component1 model={model.current} />
<Component2 model={model.current} />
<button onClick={save}>Сохранить</button>
</React.Fragment>
);
}
render(<App />, document.getElementById('root'));
组件1.tsx
export const Component1: React.FunctionComponent<IComponent> = ({ model }) => {
const [firstNameValue, setFirstNameValue] = useState(model.firstName);
const [lastNameValue, setLastNameValue] = useState(model.lastName);
const changeFirstName = (e: React.ChangeEvent<HTMLInputElement>) => {
model.firstName = e.target.value;
setFirstNameValue(e.target.value);
}
const changeLastName = (e: React.ChangeEvent<HTMLInputElement>) => {
model.lastName = e.target.value;
setLastNameValue(e.target.value);
}
return (
<React.Fragment>
<div>
<label htmlFor="firstName">FirstName:</label>
<input name="firstName" value={firstNameValue} onChange={changeFirstName} />
</div>
<div>
<label htmlFor="lastName">LastName:</label>
<input name="lastName" value={lastNameValue} onChange={changeLastName}/>
</div>
</React.Fragment>);
};
组件2.tsx
export const Component2: React.FunctionComponent<IComponent> = ({ model }) => {
const [passportValue, setPassportValue] = useState(model.passport);
const [addressValue, setAddressValue] = useState(model.address);
const changePassport = (e: React.ChangeEvent<HTMLInputElement>) => {
model.passport = e.target.value;
setPassportValue(e.target.value);
}
const changeAddress = (e: React.ChangeEvent<HTMLInputElement>) => {
model.address = e.target.value;
setAddressValue(e.target.value);
}
return (
<React.Fragment>
<div>
<label htmlFor="passport">Passport:</label>
<input name="passport" value={passportValue} onChange={changePassport} />
</div>
<div>
<label htmlFor="address">Address:</label>
<input name="address" value={addressValue} onChange={changeAddress}/>
</div>
</React.Fragment>);
};
解决方案
我不会为此推荐 redux。Redux 解决了将数据提供给整个应用程序的问题,但我们的表单是独立的,因此它不是适合这项工作的工具。如果表单开始变得非常复杂,您可能会考虑Context
为表单使用 a,但现在没有必要。
您想要的是将表单状态存储在表单的顶层并传递给特定组件。您不希望各个组件管理自己的状态。
class
我看不到将 a用于表单数据的好用例。您想将数据存储在interface
. 如果你以后需要一个类,你可以从数据接口构造它。
export interface Model {
firstName: string;
lastName: string;
passport: string;
address: string;
}
我们不使用useRef
存储类实例,而是使用useState
它最多允许存储更新表单数据。我的第一直觉是状态类型需要是,Partial<Model>
因为我们一开始所有字段都是空的。但由于这些都是字段,我们可以为每个属性string
创建一个空字符串,并且是一个完整的.initialModel
Model
const initialModel: Model = {
firstName: "",
lastName: "",
passport: "",
address: ""
};
const [model, setModel] = useState<Model>(initialModel);
Component1
并且Component2
需要获取当前model
作为道具,他们还需要获得更新模型的方法。我们可以作为道具传递setModel
,这很好,但我们最终会重复逻辑,因为{...model,
每次调用setModel
更新单个属性时我们都必须编写。
相反,我们可以创建并传递一个更新单个属性的辅助函数。此函数采用我们设置property
的新名称和新名称。value
const setProperty = (property, value) => {
setModel({
...model,
[property]: value
});
};
我不知道你的打字稿知识有多先进。如果Model
不同的字段具有不同的值类型,我们会希望使用泛型来确保值的类型与属性匹配。
export type SetPropertyFunction = <T extends keyof Model>(
property: T,
value: Model[T]
) => void;
但是由于我们所有的价值观都string
在这里,我们可以摆脱一些更简单的事情。
export type SetPropertyFunction = (
property: keyof Model,
value: string
) => void;
我们的子组件将收到的 props 是 themodel
和setProperty
回调。
export interface ComponentProps {
model: Model;
setProperty: SetPropertyFunction;
}
export type FormComponent = React.FunctionComponent<ComponentProps>;
完整的应用组件
export const App: React.FunctionComponent<{}> = () => {
const [model, setModel] = useState<Model>(initialModel);
const setProperty: SetPropertyFunction = (property, value) => {
setModel({
...model,
[property]: value
});
};
const save = () => {
console.log(model);
};
return (
<React.Fragment>
<Component1 model={model} setProperty={setProperty} />
<Component2 model={model} setProperty={setProperty} />
<button onClick={save}>Сохранить</button>
</React.Fragment>
);
};
现在为Component1
和Component2
。他们不再管理任何内部状态。
他们可以value
从模型中获取每个字段的:model.firstName
. 在onChange
处理程序中,我们setProperty
使用property
.
const changeFirstName = (e: React.ChangeEvent<HTMLInputElement>) => {
setProperty("firstName", e.target.value);
};
const changeLastName = (e: React.ChangeEvent<HTMLInputElement>) => {
setProperty("lastName", e.target.value);
};
这些回调非常简单,我们也可以内联编写它们。
onChange={e => setProperty("firstName", e.target.value)}
我们的组件现在只处理演示文稿。这是关注点分离,这很好!
完成组件 1
export const Component1: FormComponent = ({ model, setProperty }) => {
return (
<React.Fragment>
<div>
<label htmlFor="firstName">FirstName:</label>
<input
name="firstName"
value={model.firstName}
onChange={e => setProperty("firstName", e.target.value)}
/>
</div>
<div>
<label htmlFor="lastName">LastName:</label>
<input
name="lastName"
value={model.lastName}
onChange={e => setProperty("lastName", e.target.value)}
/>
</div>
</React.Fragment>
);
};
推荐阅读
- android - 离线时 React Native 冷启动较慢
- javascript - vue-router 中的多个选项键值
- java - EL1004E: 方法调用:在 MethodSecurityExpressionRoot 类型上找不到方法 isPermitted(java.lang.String)
- android - ViewModel/Repository 显示空白
- azure - Azure Kubernetes Service 和 Service Fabric 免费层的定价详细信息
- google-sheets - 是否有一个公式可以计算单元格中使用“@”的次数?
- r - 无法在 Windows 10 的 Rstudio 中编译 rstan 或 Rcpp 代码
- arrays - 在 C 中处理用户输入字符串
- react-native - ActivityIndicator 与 Expo Camera 进行 takePictureAsync
- javascript - 如何将我的 html 屏幕的背景更改为图像?