react-router-redux路由状态验证:使用Yup和Formik处理表单路由

【免费下载链接】react-router-redux Ruthlessly simple bindings to keep react-router and redux in sync 【免费下载链接】react-router-redux 项目地址: https://gitcode.com/gh_mirrors/re/react-router-redux

你还在为React应用中的表单验证与路由跳转不同步而烦恼吗?当用户填写表单时意外刷新页面导致数据丢失,或者提交后路由跳转但状态未更新?本文将带你用react-router-redux结合Yup和Formik,构建一个既安全又流畅的表单路由系统,让你轻松解决这些常见问题。

读完本文你将学到:

  • 如何用react-router-redux实现路由状态与Redux的同步
  • 利用Formik处理复杂表单状态管理
  • 使用Yup进行表单数据验证
  • 实现表单提交与路由跳转的无缝衔接
  • 解决表单验证失败时的路由拦截问题

为什么需要路由状态验证?

在单页应用(SPA)开发中,表单处理与路由管理是两个核心环节。当用户在表单页面进行操作时,可能会遇到以下问题:

  • 填写一半的表单数据因意外路由跳转而丢失
  • 表单验证失败却依然能跳转到下一页
  • 提交成功后无法正确重定向到结果页
  • 浏览器后退按钮导致表单状态异常

react-router-redux通过将路由状态同步到Redux store中,让我们可以像管理其他应用状态一样管理路由。结合Formik的表单状态管理和Yup的验证能力,就能构建出健壮的表单路由系统。

核心概念与环境准备

技术栈简介

  • react-router-redux:保持react-router和redux同步的绑定库,核心功能在src/index.js中导出
  • Formik:处理表单状态、验证和提交的完整解决方案
  • Yup:声明式表单验证库,与Formik无缝集成

项目结构

我们将基于examples/basic示例进行扩展,主要涉及以下文件:

安装依赖

首先确保项目环境正确配置:

git clone https://gitcode.com/gh_mirrors/re/react-router-redux
cd react-router-redux/examples/basic
npm install formik yup

实现步骤

1. 配置路由与Redux同步

react-router-redux的核心功能是通过syncHistoryWithStore将路由历史与Redux store同步。在examples/basic/app.js中,我们可以看到基本配置:

import { syncHistoryWithStore } from 'react-router-redux'
import { createStore, combineReducers } from 'redux'
import { Router, Route } from 'react-router'
import { routerReducer } from 'react-router-redux'

// 组合reducers,包含路由reducer
const reducer = combineReducers({
  ...reducers,
  routing: routerReducer  // 路由状态将存储在store.routing中
})

const store = createStore(reducer)
const history = syncHistoryWithStore(browserHistory, store)

// 使用同步后的history创建Router
<Router history={history}>
  <Route path="/" component={App}>
    <IndexRoute component={Home}/>
    <Route path="form" component={FormPage}/>  {/* 添加表单页面路由 */}
    <Route path="success" component={SuccessPage}/>  {/* 添加成功页面路由 */}
  </Route>
</Router>

2. 创建带验证的表单组件

examples/basic/components/目录下创建FormPage.js

import React from 'react';
import { Formik, Form, Field, ErrorMessage } from 'formik';
import * as Yup from 'yup';
import { push } from 'react-router-redux';
import { connect } from 'react-redux';

// 使用Yup定义验证模式
const FormSchema = Yup.object().shape({
  email: Yup.string()
    .email('请输入有效的邮箱地址')
    .required('邮箱不能为空'),
  password: Yup.string()
    .min(6, '密码至少需要6个字符')
    .required('密码不能为空'),
  confirmPassword: Yup.string()
    .oneOf([Yup.ref('password')], '两次密码输入不一致')
    .required('请确认密码')
});

const FormPage = ({ dispatch }) => {
  return (
    <div className="form-page">
      <h2>用户注册</h2>
      <Formik
        initialValues={{ email: '', password: '', confirmPassword: '' }}
        validationSchema={FormSchema}
        onSubmit={(values, { setSubmitting }) => {
          // 模拟API提交
          setTimeout(() => {
            // 提交成功后跳转到成功页面
            dispatch(push('/success'));
            setSubmitting(false);
          }, 1000);
        }}
      >
        {({ isSubmitting, errors, touched }) => (
          <Form>
            <div className="form-group">
              <label>邮箱</label>
              <Field type="email" name="email" className="form-control" />
              <ErrorMessage name="email" component="div" className="error" />
            </div>
            
            <div className="form-group">
              <label>密码</label>
              <Field type="password" name="password" className="form-control" />
              <ErrorMessage name="password" component="div" className="error" />
            </div>
            
            <div className="form-group">
              <label>确认密码</label>
              <Field type="password" name="confirmPassword" className="form-control" />
              <ErrorMessage name="confirmPassword" component="div" className="error" />
            </div>
            
            <button 
              type="submit" 
              disabled={isSubmitting}
              className="btn btn-primary"
            >
              {isSubmitting ? '提交中...' : '注册'}
            </button>
          </Form>
        )}
      </Formik>
    </div>
  );
};

export default connect()(FormPage);

3. 添加路由导航与状态验证

修改examples/basic/components/App.js,添加导航链接:

import { Link } from 'react-router'

const App = () => (
  <div>
    <nav>
      <Link to="/">首页</Link> |
      <Link to="/form">注册表单</Link>
    </nav>
    <div className="content">
      {this.props.children}
    </div>
  </div>
);

4. 实现表单提交与路由跳转

在表单组件中,我们使用了react-router-redux提供的push action creator来实现路由跳转:

import { push } from 'react-router-redux';

// 在表单提交成功后调用
dispatch(push('/success'));

push方法会更新路由状态并同步到Redux store,这意味着我们可以在任何组件中通过mapStateToProps访问当前路由信息:

const mapStateToProps = (state) => ({
  currentPath: state.routing.location.pathname,
  formData: state.formData // 假设我们有一个存储表单数据的reducer
});

5. 添加路由拦截与状态保存

为了防止用户在未提交表单的情况下意外离开页面,我们可以使用Prompt组件和Formik的isDirty状态:

import { Prompt } from 'react-router'

// 在Formik组件中添加
<Formik
  initialValues={{ email: '', password: '', confirmPassword: '' }}
  validationSchema={FormSchema}
  onSubmit={handleSubmit}
>
  {({ isSubmitting, errors, touched, isDirty }) => (
    <div>
      <Prompt
        when={isDirty && !isSubmitting}
        message="您有未保存的更改,确定要离开吗?"
      />
      {/* 表单内容 */}
    </div>
  )}
</Formik>

工作原理

数据流向

mermaid

状态存储结构

路由状态在Redux store中的结构如下:

{
  "routing": {
    "location": {
      "pathname": "/form",
      "search": "",
      "hash": "",
      "state": null,
      "action": "PUSH",
      "key": "abc123"
    }
  },
  // 其他应用状态...
}

常见问题解决

1. 表单验证与路由同步

如果需要在路由变化时保存表单状态,可以创建一个专门的reducer来存储表单数据:

// examples/basic/reducers/formData.js
const initialState = {};

export default function formData(state = initialState, action) {
  switch (action.type) {
    case 'SAVE_FORM_DATA':
      return {
        ...state,
        [action.formName]: action.data
      };
    default:
      return state;
  }
}

然后在Formik的onChange事件中保存表单数据:

onChange={(values) => {
  dispatch({
    type: 'SAVE_FORM_DATA',
    formName: 'registration',
    data: values
  });
}}

2. 集成Redux DevTools

react-router-redux与Redux DevTools完美集成,在examples/basic/app.js中已经配置:

const DevTools = createDevTools(
  <DockMonitor toggleVisibilityKey="ctrl-h" changePositionKey="ctrl-q">
    <LogMonitor theme="tomorrow" preserveScrollTop={false} />
  </DockMonitor>
);

const store = createStore(
  reducer,
  DevTools.instrument()
);

这让我们可以在开发过程中追踪路由变化和表单状态的更新。

总结

通过本文的学习,我们实现了一个结合路由管理、表单处理和数据验证的完整解决方案。关键要点包括:

  1. 使用react-router-redux的routerReducersyncHistoryWithStore实现路由与Redux的同步
  2. 利用Formik简化表单状态管理和提交处理
  3. 通过Yup实现声明式表单验证
  4. 使用push action creator实现基于Redux的路由导航
  5. 添加路由拦截防止意外数据丢失

这种架构不仅解决了表单与路由的同步问题,还提供了良好的用户体验和开发体验。你可以在examples/basic/目录下找到完整的实现代码,并根据实际需求进行扩展。

扩展建议

  • 添加表单数据持久化,使用localStorage保存未提交的表单
  • 实现多步骤表单,通过路由参数控制步骤流转
  • 结合redux-thunk或redux-saga处理异步表单提交
  • 添加更复杂的验证场景,如异步用户名唯一性检查

希望本文能帮助你构建更健壮的React表单应用!如有任何问题,欢迎查阅项目文档或提交issue。

【免费下载链接】react-router-redux Ruthlessly simple bindings to keep react-router and redux in sync 【免费下载链接】react-router-redux 项目地址: https://gitcode.com/gh_mirrors/re/react-router-redux

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐