本章将介绍以下配方:
- 使用本地状态创建受控窗体
- 使用 Redux 表单构建表单
- 在表单中实现验证
表单是任何 web 应用的基本组成部分,在下面的菜谱中,我们将学习如何使用带和不带 Redux 表单的表单。
对于这个配方,我们将创建一个简单的待办事项列表,以使用使用本地状态的表单。
对于这个配方,我们需要安装uuid
包来生成随机 ID,如下代码所示:
npm install uuid
让我们按照以下步骤创建受控表单:
- 首先,对于 Todo 列表,我们将在
src/components/Todo/index.jsx
中创建一个名为Todo
的新组件。我们将使用的骨架如以下代码所示:
import React, { Component } from 'react';
import uuidv4 from 'uuid/v4';
import './Todo.css';
class Todo extends Component {
constructor() {
super();
// Initial state...
this.state = {
task: '',
items: []
};
}
render() {
return (
<div className="Todo">
<h1>New Task:</h1>
<form onSubmit={this.handleOnSubmit}>
<input value={this.state.task} />
</form>
</div>
);
}
}
export default Todo;
File: src/components/Todo/index.jsx
- 请记住,我们需要将组件添加到我们的
src/routes.jsx
中,如下代码所示:
// Dependencies
import React from 'react';
import { Route, Switch } from 'react-router-dom';
// Components
import App from './components/App';
import About from './components/About';
import Coins from './components/Coins';
import Contact from './components/Contact';
import Home from './components/Home';
import Notes from './components/Notes';
import Todo from './components/Todo';
import Error404 from './components/Error/404';
const AppRoutes = () => (
<App>
<Switch>
<Route path="/" component={Home} exact />
<Route path="/about" component={About} exact />
<Route path="/coins" component={Coins} exact />
<Route path="/contact" component={Contact} exact />
<Route path="/notes" component={Notes} exact />
<Route path="/notes/:noteId" component={Notes} exact />
<Route path="/todo" component={Todo} exact />
<Route component={Error404} />
</Switch>
</App>
);
export default AppRoutes;
File: src/routes.jsx
- 如果您转到
/todo
,您将看到输入,但您可能会注意到无法在其上写入任何内容,这是因为我们正在将本地状态(this.state.task
连接到输入值,但我们需要onChange
函数来更新状态,如以下代码所示:
import React, { Component } from 'react';
import uuidv4 from 'uuid/v4';
import './Todo.css';
class Todo extends Component {
constructor() {
super();
// Initial state...
this.state = {
task: '',
items: []
};
}
handleOnChange = e => {
const { target: { value } } = e;
// Updating our task state with the input value...
this.setState({
task: value
});
}
render() {
return (
<div className="Todo">
<h1>New Task:</h1>
<form onSubmit={this.handleOnSubmit}>
<input
value={this.state.task}
onChange={this.handleOnChange}
/>
</form>
</div>
);
}
}
export default Todo;
File: src/components/Todo/index.jsx
- 现在,我们可以在输入中写入任何内容,如以下屏幕截图所示:
- 为了保存输入中写入的项,我们需要在
form
标记中添加一个onSubmit
函数,在这里我们需要更新本地状态以将项推送到items
数组。此外,我们还需要包括我们的List
组件,我们将在其中显示所有项目。完整代码如下:
import React, { Component } from 'react';
import uuidv4 from 'uuid/v4';
import List from './List';
import './Todo.css';
class Todo extends Component {
constructor() {
super();
// Initial state...
this.state = {
task: '',
items: []
};
}
handleOnChange = e => {
const { target: { value } } = e;
// Updating our task state with the input value...
this.setState({
task: value
});
}
handleOnSubmit = e => {
// Prevent default to avoid the actual form submit...
e.preventDefault();
// Once is submitted we reset the task value and we push
// task to the items array.
this.setState({
task: '',
items: [
...this.state.items,
{
id: uuidv4(),
task: this.state.task,
complete: false
}
]
});
}
render() {
return (
<div className="Todo">
<h1>New Task:</h1>
<form onSubmit={this.handleOnSubmit}>
<input
value={this.state.task}
onChange={this.handleOnChange}
/>
</form>
<List items={this.state.items} />
</div>
);
}
}
export default Todo;
File: src/components/Todo/index.jsx
- 我们的
List
组件将是一个功能组件,我们将在其中呈现项目列表,如以下代码所示:
import React from 'react';
const List = props => (
<ul>
{props.items.map((item, key) => (
<li key={key}>
{item.task}
</li>
))}
</ul>
);
export default List;
File: src/components/Todo/List.jsx
- 最后,我们需要添加 CSS 文件,如下代码所示:
.Todo {
background-color: #f5f5f5;
border-radius: 4px;
border: 1px solid #e3e3e3;
box-shadow: inset 0 1px 1px rgba(0,0,0,.05);
margin-bottom: 20px;
margin: 50px auto;
min-height: 20px;
padding: 19px;
text-align: left;
width: 70%;
}
.Todo ul {
margin: 20px 0px;
padding: 0;
list-style: none;
}
.Todo ul li {
background-color: #fff;
border: 1px solid #ddd;
display: flex;
justify-content: space-between;
margin-bottom: -1px;
padding: 10px 15px;
position: relative;
}
.Todo form input {
background-color: #fff;
border-radius: 4px;
border: 1px solid #ccc;
box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
color: #555;
font-size: 14px;
height: 34px;
line-height: 34px;
padding: 6px 12px;
width: 40%;
}
.Todo form button {
background: #2ba6cb;
border: 1px solid #1e728c;
box-shadow: 0 1px 0 rgba(255, 255, 255, 0.5) inset;
color: white;
cursor: pointer;
display: block;
font-size: 14px;
font-weight: bold;
line-height: 1;
margin: 20px auto;
padding: 10px 20px 11px;
position: relative;
text-align: center;
text-decoration: none;
}
File: src/components/Todo/Todo.css
- 我们的待办事项列表将如以下屏幕截图所示:
如您所见,从输入表单检索值的唯一方法是使用带有onChange
函数的本地状态来更新输入值。如果您将一个状态连接到输入值,但没有添加onChange
回调来更新它,您将无法写入任何内容,因为虚拟 DOM 没有被更新,唯一的方法是更新本地状态。
Redux 表单通常用于大型表单或步骤表单,因为它具有 Redux 状态以在整个表单中保留值。此外,Redux 表单可以方便地验证数据并有效地处理提交。
对于此配方,我们需要安装 Redux 表单,如下所示:
npm install redux-form
对于此配方,我们将制作相同的待办事项列表,但这次使用 Redux 表单:
- 一旦安装了 Redux 表单,我们需要对上一个配方的代码进行一些修改,以实现 Redux 表单。我们需要做的第一件事是为表单添加一个减缩器。为此,我们需要从
redux-form
中导入一个 reducer,我们可以将变量的名称改为formReducer
更加明确,然后将 reducer 作为表单添加到我们的combineReducers
中,如下代码所示:
// Dependencies
import { combineReducers } from 'redux';
import { reducer as formReducer } from 'redux-form';
// Components Reducers
import coins from '../../reducers/coinsReducer';
// Shared Reducers
import device from './deviceReducer';
const rootReducer = combineReducers({
coins,
device,
form: formReducer
});
export default rootReducer;
File: src/shared/reducers/index.js
- 通常,我们用 Redux 表单创建的所有表单都需要它们的组件,这意味着我们需要创建一个组件来处理 Todo 表单。由于我们需要在
Todo
文件夹中创建一个名为TodoForm.jsx
的文件,我们组件的代码如下:
import React, { Component } from 'react';
import { Field, reduxForm } from 'redux-form';
class TodoForm extends Component {
// Functional component to render an input...
renderInput = ({ input }) => <input {...input} type="text" />;
// This function is useful to handle our
onSubmit = values => {
const { addTask, dispatch, reset } = this.props;
// Resetting our form, this will clear our input...
dispatch(reset('todo'));
// Executing our addTask method and
// passing the form values.
addTask(values);
}
render() {
// handleSubmit is part of Redux Form
// to handle the onSubmit event
const { handleSubmit } = this.props;
return (
<form onSubmit={handleSubmit(this.onSubmit)}>
{/* Field is a Redux Form Component, we need to pass the
name of the input and the component we are using to
render it */}
<Field
name="task"
component={this.renderInput}
/>
</form>
)
}
}
// With this we named our form reducer for this specific form (todo).
export default reduxForm({
form: 'todo'
})(TodoForm);
File: src/components/Todo/TodoForm.jsx
- Redux 表单包含许多有用的道具来处理表单中的数据。我推销我们将用于此配方的道具(
addTask
是从另一个组件传递的道具,因此其中一个不是 Redux 表单的适当部分),您可以通过在渲染方法console.log(this.props);
中添加控制台来查看所有道具,如以下屏幕截图所示:
- 现在让我们修改我们的
Todo
组件,将TodoForm
组件包括在内,并通过我们的addTask
方法接收数据,如下代码所示:
import React, { Component } from 'react';
import uuidv4 from 'uuid/v4';
import List from './List';
import TodoForm from './TodoForm';
import './Todo.css';
class Todo extends Component {
constructor() {
super();
// Initial state...
this.state = {
items: []
};
}
addTask = values => {
// This values are coming from our
// onSubmit method in our TodoForm.
const { task } = values;
this.setState({
items: [
...this.state.items,
{
id: uuidv4(),
task,
complete: false
}
]
});
}
render() {
return (
<div className="Todo">
<h1>New Task:</h1>
<TodoForm addTask={this.addTask} />
<List items={this.state.items} />
</div>
);
}
}
export default Todo;
如您所见,Redux 表单易于实现:
- 第一步,我们将 Redux 表单缩减器连接到我们的商店
- 在第二步中,我们创建
TodoForm
组件,在其中呈现表单字段,将表单缩减器连接到存储,并将值发送回addTask
回调 - 在最后一步中,我们呈现我们的
TodoForm
并发送addTask
回调,该回调处理任务值以将其插入本地状态
最后,我们将看到与上一个配方相同的结果,但现在使用 Redux 表单,如以下屏幕截图所示:
Redux 表单实现的最后一部分是验证。使用前面的方法,让我们添加输入任务的验证。
任何形式都需要验证,因此让我们向字段中添加一些验证:
- 首先,我们需要修改我们的
TodoForm.jsx
,我们需要创建一个validate
函数,在这里我们需要验证我们的任务是否为空。然后,如果我们试图添加一个空任务,我们需要创建一个renderError
方法来呈现错误消息,如下代码所示:
import React, { Component } from 'react';
import { Field, reduxForm } from 'redux-form';
import './TodoForm.css';
class TodoForm extends Component {
renderInput = ({ input }) => <input {...input} type="text" />;
onSubmit = values => {
const { addTask, dispatch, reset } = this.props;
// Resetting our form...
dispatch(reset('todo'));
addTask(values);
}
renderError(field) {
const { meta: { submitFailed, error } } = field;
if (submitFailed && error) {
return (
<div className="error">
{error}
</div>
);
}
return null;
}
render() {
const { handleSubmit, submitting } = this.props;
return (
<form onSubmit={handleSubmit(this.onSubmit)}>
<Field name="task" component={this.renderInput} />
<Field name="task" component={this.renderError} />
</form>
);
}
}
const validate = values => {
const errors = {};
if (!values.task) {
errors.task = 'Task cannot be empty!';
}
return errors;
}
export default reduxForm({
validate,
form: 'todo'
})(TodoForm);
File: src/components/Todo/TodoForm.jsx
- 接下来,我们需要创建一个
TodoForm.css
来为我们的错误消息添加一些样式,如下代码所示:
.error {
color: red;
font-size: small;
margin-top: 10px;
}
File: src/components/Todo/TodoForm.css
如果我们尝试添加一个没有任何值的新任务并按Enter提交表单,我们将看到如下屏幕截图所示的视图: