首页 > 解决方案 > 在 React 中填写复杂表单时设置对象属性的最佳方法是什么

问题描述

我是 React 的新手。我有一个看似标准的任务:填写有关用户的表单数据并将其发送到服务器。表单是一组组件,例如:基本信息、护照数据、爱好等+保存按钮。

我是通过以下方式做到的。有一个class描述模型。创建组件时,我classuseRef. 此外,我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>);
};

标签: reactjstypescriptformsstate-management

解决方案


我不会为此推荐 redux。Redux 解决了将数据提供给整个应用程序的问题,但我们的表单是独立的,因此它不是适合这项工作的工具。如果表单开始变得非常复杂,您可能会考虑Context为表单使用 a,但现在没有必要。

您想要的是将表单状态存储在表单的顶层并传递给特定组件。您不希望各个组件管理自己的状态。

class我看不到将 a用于表单数据的好用例。您想将数据存储在interface. 如果你以后需要一个类,你可以从数据接口构造它。

export interface Model {
  firstName: string;
  lastName: string;
  passport: string;
  address: string;
}

我们不使用useRef存储类实例,而是使用useState它最多允许存储更新表单数据。我的第一直觉是状态类型需要是,Partial<Model>因为我们一开始所有字段都是空的。但由于这些都是字段,我们可以为每个属性string创建一个空字符串,并且是一个完整的.initialModelModel

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 是 themodelsetProperty回调。

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>
  );
};

现在为Component1Component2。他们不再管理任何内部状态。
他们可以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>
  );
};

StackBlitz 链接


推荐阅读