【问题标题】:Refactoring almost identical components, formik react重构几乎相同的组件,formik react
【发布时间】:2019-01-01 07:14:19
【问题描述】:

我有两个相同的组件,只有很少的差异(一个)。有两个很多重复的代码和样板,但我不确定如何重构它,所以我可能只需要提供一个配置。

LoginPage.js

import React from 'react';
import { Link } from 'react-router-dom';
import { Helmet } from 'react-helmet';
import { Formik, FastField, Form, ErrorMessage } from 'formik';

import PropTypes from 'prop-types';
import { FormDebug } from 'utils/FormDebug';
import { LoginValidationSchema } from 'validations/AuthValidationSchema';

function LoginPage({ username, onChangeUsername, onSubmitForm }) {
  return (
    <div>
      <Helmet>
        <title>Login</title>
      </Helmet>
      <Formik
        initialValues={{ username, password: '' }}
        validationSchema={LoginValidationSchema}
        onSubmit={onSubmitForm}
        render={({ isSubmitting, isValid, handleChange }) => (
          <Form>
            <FastField
              type="text"
              name="username"
              render={({ field }) => (
                <input
                  {...field}
                  onChange={e => {
                    handleChange(e);
                    onChangeUsername(e);
                  }}
                />
              )}
            />
            <ErrorMessage name="username" component="div" aria-live="polite" />
            <FastField type="password" name="password" />
            <ErrorMessage name="password" component="div" aria-live="polite" />
            <button type="submit" disabled={isSubmitting || !isValid}>
              Login
            </button>
            <FormDebug />
          </Form>
        )}
      />
      <Link to="/auth/forgot_password">Forgot Password</Link>
    </div>
  );
}

LoginPage.propTypes = {
  username: PropTypes.string,
  onSubmitForm: PropTypes.func.isRequired,
  onChangeUsername: PropTypes.func.isRequired,
};

export default LoginPage;

ForgotPasswordPage.js

import React from 'react';
import { Helmet } from 'react-helmet';
import { Formik, FastField, Form, ErrorMessage } from 'formik';

import PropTypes from 'prop-types';
import { FormDebug } from 'utils/FormDebug';
import { ForgotPasswordValidationSchema } from 'validations/AuthValidationSchema';

function ForgotPasswordPage({ username, onChangeUsername, onSubmitForm }) {
  return (
    <div>
      <Helmet>
        <title>Forgot Password</title>
      </Helmet>
      <Formik
        initialValues={{ username }}
        validationSchema={ForgotPasswordValidationSchema}
        onSubmit={onSubmitForm}
        render={({ isSubmitting, isValid, handleChange }) => (
          <Form>
            <FastField
              type="text"
              name="username"
              render={({ field }) => (
                <input
                  {...field}
                  onChange={e => {
                    handleChange(e);
                    onChangeUsername(e);
                  }}
                />
              )}
            />
            <ErrorMessage name="username" component="div" aria-live="polite" />
            <FormDebug />
            <button type="submit" disabled={isSubmitting || !isValid}>
              Reset Password
            </button>
          </Form>
        )}
      />
    </div>
  );
}

ForgotPasswordPage.propTypes = {
  username: PropTypes.string,
  onSubmitForm: PropTypes.func.isRequired,
  onChangeUsername: PropTypes.func.isRequired,
};

export default ForgotPasswordPage;

如果你是我,你将如何重构它。

我正在考虑 HOC.,但我不确定如何调用不同的“孩子”

【问题讨论】:

    标签: reactjs refactoring higher-order-components formik


    【解决方案1】:

    抱歉,如果您不是在寻找一般性的答案,但我认为您不会通过概括看似相关的组件来提高可维护性。我希望随着您的应用程序成熟,这些组件会进一步分开,例如通过添加社交登录、“记住我”选项、验证码、通过电子邮件检索用户名和密码的选项、检索密码与登录时对未知用户名的不同处理等。此外,这是你真正的组件的一部分不想弄错,所以 KISS 和所有。最后,考虑这种半通用的登录或检索密码表单组件是否真的有第三个用例。

    不过,可以通过例如创建一个可重用的 UsernameField 组件,它的使用对于这两种情况都将是简单且一致的。还要考虑一个 withValidation HOC 向字段添加错误消息。如果你真的想拉伸它,你可以为 Formik 提供一个 withSubmit HOC,将所有道具传递给 Formik,渲染子项(你将传递 handleChange 道具)和一个提交按钮。我假设表单本身使用上下文将状态传递给 ErrorMessage 和 FastField。

    【讨论】:

      【解决方案2】:

      我可能遗漏了一些此处未说明的复杂性,但这看起来就像创建一个接受更多传入属性的通用功能组件一样简单。我做了一个差异,你只需要添加'title','buttonText',如果你愿意,当然可以在你的道具中添加'type'。您还可以将 initialValues 对象作为道具发送,而不是从“类型”派生。

      我的意思是,您是否尝试过以下操作?

      import React from 'react';
      import { Link } from 'react-router-dom';
      import { Helmet } from 'react-helmet';
      import { Formik, FastField, Form, ErrorMessage } from 'formik';
      
      import PropTypes from 'prop-types';
      import { FormDebug } from 'utils/FormDebug';
      import * as schema from 'validations/AuthValidationSchema';
      
      function AuthPage({ buttonText, initialValues, title, type, username,
                          onChangeUsername, onSubmitForm }) {
        const authSchema = type === 'login' 
              ? schema.LoginValidationSchema 
              : schema.ForgotPasswordValidationSchema;
      
        return (
          <div>
            <Helmet>
              <title>{title}</title>
            </Helmet>
            <Formik
              initialValues={initialValues}
              validationSchema={authSchema}
              onSubmit={onSubmitForm}
              render={({ isSubmitting, isValid, handleChange }) => (
                <Form>
                  <FastField
                    type="text"
                    name="username"
                    render={({ field }) => (
                      <input
                        {...field}
                        onChange={e => {
                          handleChange(e);
                          onChangeUsername(e);
                        }}
                      />
                    )}
                  />
                  <ErrorMessage name="username" component="div" aria-live="polite" />
                  {type === 'forgot' &&
                    <FastField type="password" name="password" />
                    <ErrorMessage name="password" component="div" aria-live="polite" />
                  }
                  <button type="submit" disabled={isSubmitting || !isValid}>
                    {buttonText}
                  </button>
                  <FormDebug />
                </Form>
              )}
            />
            <Link to="/auth/forgot_password">Forgot Password</Link>
          </div>
        );
      }
      
      AuthPage.propTypes = {
        buttonText: PropTypes.string,
        initialValues: PropTypes.object,
        title: PropTypes.string,
        type: PropTypes.oneOf(['login', 'forgot'])
        username: PropTypes.string,
        onSubmitForm: PropTypes.func.isRequired,
        onChangeUsername: PropTypes.func.isRequired,
      };
      
      export default AuthPage;
      

      (我只记得密码字段的条件渲染及其 ErrorMessage 是否需要包装在 div 中以使其成为一个元素)

      如果你不想传入初始值:

        const initialVals = type === 'login'
              ? { username, password: ''}
              : { username }
        ...
        initialValues={initialVals}
      

      并将其从 propTypes 中删除

      我不确定的唯一另一件事是为什么 FormDebug 在两个版本中的放置方式不同。我把它放在了按钮之后

      【讨论】:

        【解决方案3】:

        这两个页​​面有不同的关注点,因此我不建议提取可用于这两种情况的逻辑,因为即使确实删除了一些重复的行,也会通过将代码进一步分开来增加复杂性。在这种情况下,一个好的策略可能是考虑在组件中重用的东西。

        例如,您可以将 formik 组件包装在您自己的包装器中,然后再包装一个适用于您的新表单的输入组件。减少样板的一个很好的练习可能是针对这种最终结果的一些变体

        <CoolForm 
          initialValues={{ username, password: '' }}
          validationSchema={LoginValidationSchema}
        >
          <CoolFormInputWithError someProps={{howeverYou: 'want to design this'}}>
          <CoolFormInputWithError someProps={{howeverYou: 'want to design this'}}>
          <CoolFormSubmit>
            Login
          </CoolFormSubmit>
          <FormDebug />
        </CoolForm>
        

        这只是一个想法,但这个策略的好处是,如果你想重构 formik 出于任何原因,它真的很简单,因为你的所有代码现在都包装在 CoolForm 组件中,所以你最终只会改变实现您自己的组件,尽管在这种情况下这种好处非常小。

        【讨论】:

          猜你喜欢
          • 2020-01-13
          • 1970-01-01
          • 2021-12-10
          • 1970-01-01
          • 1970-01-01
          • 2023-03-27
          • 1970-01-01
          • 1970-01-01
          • 2023-02-04
          相关资源
          最近更新 更多