在 React 中填写复杂表单时设置对象属性的最佳方法是什么
What is the best way to set object properties when filling a complex form in React
我是 React 新手。我有一个看似标准的任务:填写有关用户的表单数据并将其发送到服务器。表单是一组组件,如:基本信息、护照数据、兴趣爱好等。+保存按钮。
我是按照下面的方法做的。有一个 class
描述模型。创建组件时,我在 useRef
中创建了此 class
的实例。此外,我通过它们的道具将这个 model
变量传递给所有子组件。模型属性填充在组件中。因此,当我单击 Save
按钮时,我已经填充了模型属性。Here's an example.
请问,这种填充复杂对象数据的方法好吗?也许换一种方式更好?有什么最佳实践吗?也许我应该使用 redux 来完成这项任务?
model.ts
class Model {
// Component 1
firstName: string;
lastName: string;
// Component 2
passport: string;
address: string;
}
interface IComponent {
model: Model;
}
export { Model, IComponent };
index.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'));
Component1.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>);
};
Component2.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 解决了让数据对整个应用程序可用的问题,但我们的表单是 self-contained 所以它不是完成这项工作的正确工具。如果表单开始变得非常复杂,您可能会考虑对表单使用 Context
,但现在没有必要。
您想要的是将表单状态存储在表单的 top-level 并传递给特定组件。您不希望各个组件管理自己的状态。
我看不到对表单数据使用 class
的好的用例。您希望将数据存储在 interface
中。如果以后需要class,可以从data接口构造
export interface Model {
firstName: string;
lastName: string;
passport: string;
address: string;
}
我们使用 useState
代替使用 useRef
来存储 class 实例,它允许同时存储和更新表单数据。我的第一直觉是状态类型需要是 Partial<Model>
因为我们开始时所有字段都是空的。但由于这些都是 string
字段,我们可以创建一个 initialModel
,每个 属性 都有空字符串,并且是一个完整的 Model
.
const initialModel: Model = {
firstName: "",
lastName: "",
passport: "",
address: ""
};
const [model, setModel] = useState<Model>(initialModel);
Component1
和 Component2
需要获取当前的 model
作为道具,他们还需要获取更新模型的方法。我们可以将 setModel
作为 prop 传递,这很好,但我们最终会重复逻辑,因为每次调用 setModel
更新单个 属性.
相反,我们可以创建并传递一个更新单个 属性 的辅助函数。此函数采用 property
的名称和我们将其设置为的新 value
。
const setProperty = (property, value) => {
setModel({
...model,
[property]: value
});
};
我不知道你的打字稿知识有多高深。如果 Model
具有具有不同值类型的不同字段,我们希望使用 generic 来确保值的类型与 属性.
相匹配
export type SetPropertyFunction = <T extends keyof Model>(
property: T,
value: Model[T]
) => void;
但由于我们所有的值都在这里 string
,我们可以通过更简单的方法来解决问题。
export type SetPropertyFunction = (
property: keyof Model,
value: string
) => void;
我们的 sub-components 将收到的道具是 model
和 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
处理程序中,我们使用 property
.
的名称调用我们的 setProperty
助手
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)}
我们的组件现在只处理演示。这是separation of concerns,不错!
完成组件 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>
);
};
我是 React 新手。我有一个看似标准的任务:填写有关用户的表单数据并将其发送到服务器。表单是一组组件,如:基本信息、护照数据、兴趣爱好等。+保存按钮。
我是按照下面的方法做的。有一个 class
描述模型。创建组件时,我在 useRef
中创建了此 class
的实例。此外,我通过它们的道具将这个 model
变量传递给所有子组件。模型属性填充在组件中。因此,当我单击 Save
按钮时,我已经填充了模型属性。Here's an example.
请问,这种填充复杂对象数据的方法好吗?也许换一种方式更好?有什么最佳实践吗?也许我应该使用 redux 来完成这项任务?
model.ts
class Model {
// Component 1
firstName: string;
lastName: string;
// Component 2
passport: string;
address: string;
}
interface IComponent {
model: Model;
}
export { Model, IComponent };
index.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'));
Component1.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>);
};
Component2.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 解决了让数据对整个应用程序可用的问题,但我们的表单是 self-contained 所以它不是完成这项工作的正确工具。如果表单开始变得非常复杂,您可能会考虑对表单使用 Context
,但现在没有必要。
您想要的是将表单状态存储在表单的 top-level 并传递给特定组件。您不希望各个组件管理自己的状态。
我看不到对表单数据使用 class
的好的用例。您希望将数据存储在 interface
中。如果以后需要class,可以从data接口构造
export interface Model {
firstName: string;
lastName: string;
passport: string;
address: string;
}
我们使用 useState
代替使用 useRef
来存储 class 实例,它允许同时存储和更新表单数据。我的第一直觉是状态类型需要是 Partial<Model>
因为我们开始时所有字段都是空的。但由于这些都是 string
字段,我们可以创建一个 initialModel
,每个 属性 都有空字符串,并且是一个完整的 Model
.
const initialModel: Model = {
firstName: "",
lastName: "",
passport: "",
address: ""
};
const [model, setModel] = useState<Model>(initialModel);
Component1
和 Component2
需要获取当前的 model
作为道具,他们还需要获取更新模型的方法。我们可以将 setModel
作为 prop 传递,这很好,但我们最终会重复逻辑,因为每次调用 setModel
更新单个 属性.
相反,我们可以创建并传递一个更新单个 属性 的辅助函数。此函数采用 property
的名称和我们将其设置为的新 value
。
const setProperty = (property, value) => {
setModel({
...model,
[property]: value
});
};
我不知道你的打字稿知识有多高深。如果 Model
具有具有不同值类型的不同字段,我们希望使用 generic 来确保值的类型与 属性.
export type SetPropertyFunction = <T extends keyof Model>(
property: T,
value: Model[T]
) => void;
但由于我们所有的值都在这里 string
,我们可以通过更简单的方法来解决问题。
export type SetPropertyFunction = (
property: keyof Model,
value: string
) => void;
我们的 sub-components 将收到的道具是 model
和 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
处理程序中,我们使用 property
.
setProperty
助手
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)}
我们的组件现在只处理演示。这是separation of concerns,不错!
完成组件 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>
);
};