重构几乎相同的组件,formik反应

时间:2019-01-01 07:14:20

标签: reactjs refactoring higher-order-components formik

我有两个相同的组成部分,只有几个区别(一个)。有很多重复的代码和样板,但是我不确定如何重构它,以便大概只需要提供一个配置即可。

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;

如果您是我,如何重构。

我在想如何,但是我不确定如何称呼传递“孩子”的人。

3 个答案:

答案 0 :(得分:2)

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

例如,仍然可以进行较小的改进。创建一个可重用的UsernameField组件,其用法将很简单,并且在两种情况下均保持一致。还可以考虑withValidation HOC将错误消息添加到字段中。如果您真的想拉伸它,可以为Formik设置withSubmit HOC,将所有道具传递给Formik,渲染子对象(您将通过handleChange prop)和一个提交按钮。我认为表单本身使用上下文将状态传递给ErrorMessage和FastField。

答案 1 :(得分:1)

我可能会错过这里未提到的一些复杂性,但这看起来就像创建一个接受几个传入属性的通用功能组件一样简单。我做了一个比较,您只需要在道具中添加“ title”,“ buttonText”和“ type”即可。您也可以发送initialValues对象作为道具,而不是从'type'派生它。

我的意思是,您尝试过以下吗?

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在两个版本中的放置方式有所不同。我在按钮后 留了它。

答案 2 :(得分:0)

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

例如,您可以将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组件中,因此最终只能更改实现自己的组件,尽管这种情况下的好处很小。