Skip to content

Latest commit

 

History

History
558 lines (428 loc) · 13.8 KB

File metadata and controls

558 lines (428 loc) · 13.8 KB

六、使用 Redux 表单创建表单

本章将介绍以下配方:

  • 使用本地状态创建受控窗体
  • 使用 Redux 表单构建表单
  • 在表单中实现验证

介绍

表单是任何 web 应用的基本组成部分,在下面的菜谱中,我们将学习如何使用带和不带 Redux 表单的表单。

使用本地状态创建受控窗体

对于这个配方,我们将创建一个简单的待办事项列表,以使用使用本地状态的表单。

准备

对于这个配方,我们需要安装uuid包来生成随机 ID,如下代码所示:

npm install uuid

怎么做。。。

让我们按照以下步骤创建受控表单:

  1. 首先,对于 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

  1. 请记住,我们需要将组件添加到我们的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

  1. 如果您转到/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

  1. 现在,我们可以在输入中写入任何内容,如以下屏幕截图所示:

  1. 为了保存输入中写入的项,我们需要在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

  1. 我们的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

  1. 最后,我们需要添加 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

  1. 我们的待办事项列表将如以下屏幕截图所示:

它是如何工作的。。。

如您所见,从输入表单检索值的唯一方法是使用带有onChange函数的本地状态来更新输入值。如果您将一个状态连接到输入值,但没有添加onChange回调来更新它,您将无法写入任何内容,因为虚拟 DOM 没有被更新,唯一的方法是更新本地状态。

使用 Redux 表单构建表单

Redux 表单通常用于大型表单或步骤表单,因为它具有 Redux 状态以在整个表单中保留值。此外,Redux 表单可以方便地验证数据并有效地处理提交。

准备

对于此配方,我们需要安装 Redux 表单,如下所示:

 npm install redux-form

怎么做。。。

对于此配方,我们将制作相同的待办事项列表,但这次使用 Redux 表单:

  1. 一旦安装了 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

  1. 通常,我们用 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

  1. Redux 表单包含许多有用的道具来处理表单中的数据。我推销我们将用于此配方的道具(addTask是从另一个组件传递的道具,因此其中一个不是 Redux 表单的适当部分),您可以通过在渲染方法console.log(this.props);中添加控制台来查看所有道具,如以下屏幕截图所示:

  1. 现在让我们修改我们的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 表单易于实现:

  1. 第一步,我们将 Redux 表单缩减器连接到我们的商店
  2. 在第二步中,我们创建TodoForm组件,在其中呈现表单字段,将表单缩减器连接到存储,并将值发送回addTask回调
  3. 在最后一步中,我们呈现我们的TodoForm并发送addTask回调,该回调处理任务值以将其插入本地状态

最后,我们将看到与上一个配方相同的结果,但现在使用 Redux 表单,如以下屏幕截图所示:

在表单中实现验证

Redux 表单实现的最后一部分是验证。使用前面的方法,让我们添加输入任务的验证。

怎么做。。。

任何形式都需要验证,因此让我们向字段中添加一些验证:

  1. 首先,我们需要修改我们的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

  1. 接下来,我们需要创建一个TodoForm.css来为我们的错误消息添加一些样式,如下代码所示:
.error {
  color: red;
  font-size: small;
  margin-top: 10px;
}

File: src/components/Todo/TodoForm.css

它是如何工作的。。。

如果我们尝试添加一个没有任何值的新任务并按Enter提交表单,我们将看到如下屏幕截图所示的视图: