使用ReduxForm在React中管理表单状态
介绍
redux-form 是管理由 Redux 驱动的表单的好方法。 它是一个高阶组件 (HOC),它使用 react-redux 来确保 React 中的 HTML 表单使用 Redux 来存储其所有状态。
redux-form 具有以下组件来帮助您构建应用程序:
formReducer()
:这是一个告诉如何根据来自应用程序的更改来更新 Redux 存储的函数; 这些更改由 Redux 操作描述。formReducer
必须在form
的 Redux 状态下挂载。reduxForm()
:reduxForm()
函数是一个高阶组件,它接受一个配置对象,它总是返回一个新函数。 它用于包装表单组件并将用户交互绑定到 Redux 调度操作。<Field/>
组件:存在于包装表单组件中的组件。 它用作将表单中的输入元素连接到redux-form logic
的一种方式。 换句话说,这是我们从用户输入的内容中获取输入的方式。
您可以在其 文档 中阅读有关 redux-form
API 的更多信息。
在本教程中,您将使用 redux-form 构建带有验证的表单并将其连接到 Redux 存储。
先决条件
要完成本教程,您需要:
- Node.js 的本地开发环境。 关注如何安装Node.js并创建本地开发环境
第 1 步 - 创建项目
我们将使用 create-react-app 包构建一个 React 应用程序。 create-react-app
允许您在没有构建配置的情况下创建 React 应用程序。 您可以通过运行以下终端命令来使用 create-react-app
。 它会自动在名为 contact-redux
的文件夹中为您创建一个 React 应用程序。
npx create-react-app contact-redux
请务必注意,npx
仅适用于 5.2 及更高版本的 npm
。 如果您的版本低于该版本,但仍想在您的计算机上使用 create-react-app
。 运行以下终端命令以安装 create-react-app
并启动 React 应用程序。
npm install -g create-react-app create-react-app contact-redux
导航到目录并启动开发服务器以确保一切正常。 运行以下命令以开发模式启动新创建的 React 应用程序:
npm start
您将在浏览器中看到以下内容:
我们现在已经启动并运行了一个 React 应用程序。
执行以下命令以添加表单所需的依赖项。
npm install --save redux react-redux redux-form
- redux - 一个状态容器,它是 redux-form 工作的先决条件。
- react-redux - React Redux 是 Redux 的官方 React 绑定,它也是 redux-form 工作的先决条件
- redux-form - 本教程使用的包。
安装完成后,您可以处理联系表单。
第 2 步 - 创建表单
我们将在 index.html
文件中添加一个 Bulma CDN 链接,以添加一些默认样式。 打开 public/index.html
文件并将以下代码行添加到 head
标签:
公共/index.html
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.6.2/css/bulma.min.css">
我们现在将对 src/App.js
文件进行一些编辑。 打开 src/App.js
文件并在文件顶部添加以下代码行。
src/App.js
import { reduxForm, Field } from 'redux-form';
接下来,进入render()
函数,修改如下代码:
src/App.js
render() { return ( <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <h1 className="App-title">Welcome to React x redux-form</h1> </header> <div className="container"> <p className="App-intro"> Contact Form </p> <SignInForm /> </div> </div> ); }
介绍性文本已更改,最重要的是我们添加了一个 <SignInForm />
组件,我们将在下面创建它。 它将是一个简单的组件,它返回我们需要的表单,并将连接到 redux-form
组件。 在同一个 src/App.js
文件中,在 class App extends Component
的声明之前键入以下代码。
src/App.js
let SignInForm = props => { return <form className="form"> <div className="field"> <div className="control"> <label className="label">First Name</label> <Field className="input" name="firstName" component="input" type="text" placeholder="First Name"/> </div> </div> <div className="field"> <div className="control"> <label className="label">Last Name</label> <Field className="input" name="lastName" component="input" type="text" placeholder="Last Name"/> </div> </div> <div className="field"> <div className="control"> <label className="label">Email</label> <Field className="input" name="email" component="input" type="email" placeholder="Email Address"/> </div> </div> <div className="field"> <div className="control"> <label className="label">Proficiency</label> <div className="select"> <Field className="input" name="proficiency" component="select"> <option /> <option value="beginner">Beginner Dev</option> <option value="intermediate">Intermediate Dev</option> <option value="expert">Expert Dev</option> </Field> </div> </div> </div> <div className="field"> <div className="control"> <label className="label">Age</label> <Field className="input" name="age" component="input" type="number" placeholder="Age"/> </div> </div> <div className="field"> <div className="control"> <label className="checkbox"> <Field name="saveDetails" id="saveDetails" component="input" type="checkbox"/> Save Details </label> </div> </div> <div className="field"> <div className="control"> <label className="label">Message</label> <Field className="textarea" name="message" component="textarea" /> </div> </div> <div className="field"> <div className="control"> <button className="button is-link">Submit</button> </div> </div> </form>; };
在这段代码中,我们设置了一个最小的联系表单,它要求用户提供名字、姓氏和年龄等信息。 这种形式中有趣的部分是 Field
组件。
Field
组件来自 redux-form
包,这就是我们编写 input
字段的方式。 type
属性指示它应该是什么类型的输入,即 radio
输入、checkbox
输入、text
输入或 email
输入。 component
属性决定了它应该是什么类型的输入字段,它可以是 input
、textarea
或 select
标签和 name
prop 将用于识别我们将在下面创建的 redux 存储中的字段状态。
所以为了使用连接到 redux-form 的表单,我们需要已经创建了某种 Redux 存储,这就是我们接下来要做的。
第 3 步 - 设置 Redux 存储
我们需要一个 Redux 存储,我们可以在其中连接我们创建的表单组件 (SignInForm
)。 让我们从导入 redux
包开始。 打开 src/index.js
文件并添加以下代码行,我们基本上将 redux
导入到 React 应用程序中。
src/index.js
import { createStore, combineReducers } from 'redux'; import { Provider } from 'react-redux'; import { reducer as formReducer } from 'redux-form';
第一行代码导入 createStore
和 combineReducers
。 createStore 有助于创建一个 Redux 存储来保存应用程序的完整状态树,combineReducers 有助于将所有 reducer 函数管理到一个辅助函数中,然后可以将其传递到createStore
。 你可以在 Redux API reference 页面上阅读更多关于这些函数的信息。
第二行代码从 react-redux
导入 Provider
。 Provider
有助于将 store 的状态传递给应用程序中的所有容器组件,稍后我们将演示它是如何工作的。
第三行代码将 reducer
导入为 formReducer
,这就是我们将使用它来将表单连接到 Redux 存储。
接下来,我们将创建实际的 Redux 存储,并使用 Provider
组件确保它适用于所有容器组件。 使用以下代码块编辑 src/index.js
文件。
src/index.js
import React from 'react'; import ReactDOM from 'react-dom'; import { createStore, combineReducers } from 'redux'; import { Provider } from 'react-redux'; import { reducer as formReducer } from 'redux-form'; import './index.css'; import App from './App'; import registerServiceWorker from './registerServiceWorker'; const rootReducer = combineReducers({ form: formReducer, }); const store = createStore(rootReducer); ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') ); registerServiceWorker();
在上面的代码块中,我们使用 combineReducers
函数将 formReducer
从表单连接到 Redux 存储。 它基本上用于更新我们的任何状态以响应操作,在这种情况下,是对表单的更改。 下一行代码用于使用 Redux 中的 createStore
创建存储。
这个新创建的商店然后在 Provider
的帮助下可用于应用程序的所有部分,它包裹在 App
组件周围,它还接受商店的道具,即 store
。
让我们回到表单,最后将它连接到商店。
## 第 4 步 – 将表单连接到 redux-form
我们有我们的表单组件,但它还没有连接到 redux-form
。 让我们解决这个问题。 在 class App extends Component
之前和 SignInForm 演示组件声明之后立即键入以下代码块。
src/index.js
SignInForm = reduxForm({ form: 'signIn', })(SignInForm);
在上面的代码块中,使用 reduxForm
高阶组件将 SignInForm
制成 redux 连接的形式。 这意味着我们的表单现在已连接到商店。 需要注意的一点是配置键 form
,它用作标识符,用于为表单组件提供唯一名称。 如果它们是多种形式,那么您需要使用单独的名称以便更好地管理它们的不同状态。
接下来我们要做的是配置当我们点击提交按钮时会发生什么。 在一个理想的应用程序中,您希望将数据发送到远程 API 或某些数据库,但出于演示的目的,我们会将表单数据记录到浏览器控制台中。 为此,我们需要从道具中获取表单数据并将它们存储在某个地方。
在 SignInForm
组件中,在 return
语句的正上方添加以下代码行。
[label src/index.js] const { handleSubmit } = props; return <form **onSubmit={handleSubmit}** className="form">
SignInForm
形式的 props
被解构为 handleSubmit
。 然后,当单击提交按钮时,handleSubmit
函数将在表单中用作 onSubmit
事件的处理程序。
最后,在 src/App.js
文件的 App
组件中,我们将创建一个函数,将表单数据记录到浏览器控制台。 将下面的代码块添加到 render()
函数之前的文件中。
src/App.js
handleSignIn = values => { console.log(values); };
然后添加 handleSignIn
函数作为 SignInForm
组件的事件处理程序。 由于上面的 handleSubmit
函数,redux-form
将自动确定从表单获取的数据,本质上是 SignInForm
组件应该记录到控制台。
src/App.js
<SignInForm onSubmit={this.handleSignIn} />
您现在可以通过运行 npm start
终端命令来启动应用程序。 填写表格,单击提交,您将看到记录到控制台的值。
第 5 步 - 添加验证
验证在构建表单时很重要,redux-form
附带了一些验证功能,我们现在将实现它。 在 src/App.js
文件中,键入下面的代码块。
src/App.js
const validate = val => { const errors = {}; if (!val.firstName) { console.log('First Name is required'); errors.firstName = 'Required'; } if (!val.lastName) { console.log('Last Name is required'); errors.lastName = 'Required'; } if (!val.email) { console.log('email is required'); errors.email = 'Required'; } else if (!/^.+@.+$/i.test(val.email)) { console.log('email is invalid'); errors.email = 'Invalid email address'; } if (!val.age) { errors.age = 'Required' } else if (isNaN(Number(val.age))) { errors.age = 'Must be a number' } else if (Number(val.age) < 18) { errors.age = 'Sorry, you must be at least 18 years old' } return errors; };
validate
函数用于检查表单中的验证错误。 val
参数将用于检查不同字段的验证。 我们首先检查 errors
对象是否为空,空对象显然意味着没有错误。 然后我们使用条件逻辑来检查一个字段是否为空,如果是,则抛出相应的错误。 在上面的代码块中,我们只对 firstName
、lastName
、email
和 age
进行验证。 在 email
验证条件中,我们检查它是否为空,并使用 regex
检查它是否是有效的电子邮件,在 age
验证条件中,我们检查它是否为空,一个数字,如果用户小于 18 岁。
接下来,我们将我们的验证函数注册到 redux-form
以便它可以开始使用它来执行验证测试。 将 validate
函数添加到 redux-form
高阶组件:
src/index.js
SignInForm = reduxForm({ form: 'signIn', validate, })(SignInForm);
现在我们已经准备好验证功能并在 redux-form
HOC 中注册,构建一个可重用的组件,只要有错误就会显示错误,并在我们的表单中使用新创建的组件。
src/index.js
const renderField = ({ input, label, type, meta: { touched, error, warning } }) => ( <div> <div className="control"> <label className="field">{label}</label> <input className="input" {...input} placeholder={label} type={type}/> {touched && ((error && <span>{error}</span>) || (warning && <span>{warning}</span>))} </div> </div> )
renderField
组件接受 input
对象的道具、label
、输入的 type
和 meta
的 redux-form
属性。 {touched && ((error && <span>{error}</span>) || (warning && <span>{warning}</span>))}
行表示当单击/聚焦表单字段时是否有任何错误,应显示错误消息。 此外,如果有任何错误,表格将不会提交。
现在,如果您检查表单并尝试输入任何无效输入或跳过具有验证测试的字段,您应该在表单下方显示错误消息。
结论
在本教程中,您使用 redux-form
构建了一个表单并将其连接到 Redux 存储。 您在表单上添加了同步验证,而不需要外部模式验证器。
您可以在官方网站上阅读更多关于redux-form
的信息,您可以通过他们的示例进一步探索。
您可以在 GitHub 上找到本教程的完整代码。