🔥 简单设计一个 Form 表单!

1,171 阅读3分钟

如果让你设计一个简单的 Form 表单组件,不考虑复杂的情况。只需要考虑收集值以及设置默认值。你会如何设计呢?用什么方式收集数据?用什么方式设置默认值?

👁 效果展示

2022-05-22 14.34.33.gif

🤔 实现思路

  • 对于收集数据来说,主要是使用 FormData 进行处理!
  • 对于设置默认值,则是使用 React.Children.map 进行处理!

✍️ 设计组件

🎮 Form 组件

import { Children, cloneElement } from "react";
import { Input } from "./Input";
import { Button } from "./Button";
export const Form = ({ children, onSubmit, initialValue, ...props }) => {
  const handleSubmit = (e) => {
    e.preventDefault();
    const formData = new FormData(e.currentTarget);
    const fieldValues = Object.fromEntries(formData.entries());
    onSubmit(fieldValues);
  };
  return (
    <form onSubmit={handleSubmit} {...props}>
      {Children.map(children, (child) => {
        if (child.type.name !== "Button") {
          return cloneElement(child, {
            ...child.props,
            initialValue: initialValue?.[child.props?.name],
          });
        }
        return child;
      })}
    </form>
  );
};

Form.Input = Input;
Form.Button = Button;

📚 Input 组件

import styled from "styled-components";
import { useState } from "react";
const Input_ = styled.input`
  box-sizing: border-box;
  margin: 0;
  font-variant: tabular-nums;
  list-style: none;
  font-feature-settings: "tnum";
  position: relative;
  display: inline-block;
  width: 100%;
  min-width: 0;
  padding: 4px 11px;
  color: #000000d9;
  font-size: 14px;
  line-height: 1.5715;
  background-color: #fff;
  background-image: none;
  border: 1px solid #d9d9d9;
  border-radius: 2px;
  transition: all 0.3s;
  outline: none;
`;

const Label = styled.label`
  position: relative;
  display: inline-flex;
  align-items: center;
  max-width: 100%;
  height: 32px;
  color: #000000d9;
  font-size: 14px;
  overflow: hidden;
  white-space: nowrap;
  text-align: right;
  vertical-align: middle;
  &::after {
    content: ":";
    position: relative;
    top: -0.5px;
    margin: 0 8px 0 2px;
  }
`;

const ItemContainer = styled.div`
  display: flex;
  flex-flow: row wrap;
  box-sizing: border-box;
  margin: 0 0 24px;
  padding: 0;
  color: #000000d9;
  font-size: 14px;
  font-variant: tabular-nums;
  line-height: 1.5715;
  list-style: none;
  font-feature-settings: "tnum";
  vertical-align: top;
  .item-width {
    display: block;
    flex: 0 0 33.33333333%;
    max-width: 33.33333333%;
    text-align: right;
  }
`;

export const Input = ({ label, name, initialValue, onChange, ...props }) => {
  const [value, setValue] = useState(initialValue ?? "");
  const handleChange = (e) => {
    setValue(e.target.value);
    onChange?.(e);
  };
  return (
    <ItemContainer>
      <div className="item-width">
        <Label htmlFor={`${label}-label`}>{label}</Label>
      </div>
      <div className="item-width">
        <Input_
          id={`${label}-label`}
          value={value}
          onChange={handleChange}
          name={name}
          type="text"
          {...props}
        />
      </div>
    </ItemContainer>
  );
};

🔗 Button 组件

import styled from "styled-components";

const Button_ = styled.button`
  line-height: 1.5715;
  position: relative;
  display: inline-block;
  font-weight: 400;
  white-space: nowrap;
  text-align: center;
  background-image: none;
  border: 1px solid transparent;
  box-shadow: 0 2px #00000004;
  cursor: pointer;
  transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
  user-select: none;
  touch-action: manipulation;
  height: 32px;
  padding: 4px 15px;
  font-size: 14px;
  border-radius: 2px;
  color: #000000d9;
  border-color: #d9d9d9;
  background: #fff;
`;

export const Button = ({ children }) => {
  return <Button_ type="submit">{children}</Button_>;
};

✋🏻 Usage

import { Form } from "./Component/Form";

function App() {
  const handleSubmit = (fieldValues) => {
    console.log(
      "🚀 ~ file: App.js ~ line 5 ~ handleSubmit ~ fieldValues",
      fieldValues
    );
  };

  return (
    <div className="App">
      <Form
        onSubmit={handleSubmit}
        autoComplete="off"
        initialValue={{
          userName: "Destiny__",
          emal: "nicai@nc.com",
          tel: "123456789",
        }}
      >
        <Form.Input label="姓名" name="userName" />
        <Form.Input label="邮件" name="emal" type="email" />
        <Form.Input label="手机" name="tel" type="tel" />
        <Form.Button>提交</Form.Button>
      </Form>
    </div>
  );
}

export default App;

源代码 🔗

🤝 👏 👋 往期精彩