带有React&Redux II的书店:带有Thunk的异步请求

Previously, we got ourselves started with React with Redux fundamentals and touched all the core concepts of Redux including Actions, Reducers and Stores. We also had a look at the features of React-Redux library including connect and Provider.

以前 ,我们从Redux基础知识入手React,并触及了Redux的所有核心概念,包括动作,Reducer和Stores。 我们还了解了React-Redux库的功能,包括connectProvider

Book Page

What we can do now is move further to complexity and reality by fleshing out the application we already started building to have more consumable features and tackle real situations.

现在,我们可以做的是通过充实已经开始构建的应用程序,使其具有更多易用的功能并解决实际情况,从而进一步向复杂性和现实性发展。

Redux Thunk ( Redux Thunk )

Redux Thunk is a middleware for Redux written by Dan, the father of Redux. Normally, your action creators return an object of payload but with Thunk, you can return a defer-able function. This means, you can handle asynchronous requests in a React Redux environment using Redux Thunk.

Redux Thunk是Redux的父亲Dan编写的Redux中间件。 通常,动作创建者返回有效载荷的对象,但是使用Thunk时,您可以返回可延迟的函数。 这意味着,您可以使用Redux Thunk在React Redux环境中处理异步请求。

We can apply the middleware to our store during configuration as discussed in the previous article:

如上一篇文章所述,我们可以在配置过程中将中间件应用于商店:

// ./src/store/configureStore.js
import {createStore, compose, applyMiddleware} from 'redux';
// Import thunk middleware
import thunk from 'redux-thunk';
import rootReducer from '../reducers';

export default function configureStore(initialState) {
  return createStore(rootReducer, initialState,
    // Apply to store
    applyMiddleware(thunk)
  );
}

That sets up everything for Redux Thunk. Now, let's make use of it in our existing app.

这就为Redux Thunk设置了一切。 现在,让我们在现有应用程序中使用它。

我们的第一个大家伙 ( Our First Thunk )

We already had a React Redux application from our previous post on this tutorial. At the moment, the sample is based on a static array for its data source. We can use Mock API to create some sample API data and see how we can use thunks to consume this data and make them available in our React application. The following is the endpoint and it is public:

在本教程的上一篇文章中,我们已经有一个React Redux应用程序。 目前,样本基于其数据源的静态数组。 我们可以使用Mock API创建一些示例API数据,并了解如何使用thunk来使用这些数据并使它们在React应用程序中可用。 以下是端点,它是公共的:

http://57c62fdcc1fc8711008f2a7e.mockapi.io/api/book

I populated the API with mock data so we can have something fake to test with.

我用模拟数据填充了API,因此我们可以进行一些伪造测试。

创建异步操作 (Create Async Action)

As we discussed, actions that returns object are not good enough for async operations because the the payload is used as a later time different from when the action was dispatched. Rather, it returns a function that takes dispatch as its only argument and dispatches an action when th: promise resolves:

正如我们所讨论的那样,返回对象的操作对于异步操作来说还不够好,因为有效负载被用作稍后的时间,与调度该操作的时间不同。 而是返回一个函数,该函数将dispatch作为其唯一参数,并在th:promise解析时调度一个动作:

// ./src/actions/bookActions.js
// API URL
const apiUrl = 'http://57c62fdcc1fc8711008f2a7e.mockapi.io/api/book';
// Sync Action
export const fetchBooksSuccess = (books) => {
  return {
    type: 'FETCH_BOOKS_SUCCESS',
    books
  }
};
//Async Action
export const fetchBooks = () => {
  // Returns a dispatcher function
  // that dispatches an action at a later time
  return (dispatch) => {
    // Returns a promise
    return Axios.get(apiUrl)
      .then(response => {
        // Dispatch another action
        // to consume data
        dispatch(fetchBooksSuccess(response.data))
      })
      .catch(error => {
        throw(error);
      });
  };
};

The dispatcher function returned by the action creator function must return a promise which when resolved, dispatches another synchronous action to handle the data that was returned.

动作的创造者函数返回的调度功能必须返回一个承诺,其解决的时候,调度另一个同步动作来处理返回的数据。

In Redux, a popular convention to keep track of application state is to append the status at the end of the action. Eg: _SUCCESS is appended to FETCH_BOOKS action to indicate that the books were retrieved successfully.

在Redux中,跟踪应用程序状态的一种流行约定是在操作结束时附加状态。 例如: _SUCCESS附加到FETCH_BOOKS操作中,以指示已成功检索书籍。

If you choose to handle errors you can dispatch FETCH_BOOKS_ERROR in the error callback or FETCH_BOOKS_LOADING before the the request to indicate an ongoing request.

如果您选择处理错误,你可以在请求之前的错误回调或FETCH_BOOKS_LOADING派遣FETCH_BOOKS_ERROR,表示正在进行的请求。

The HTTP request is made with Axios, an npm package. Install via npm by running:

HTTP请求是使用npm软件包Axios发出的。 通过运行以下命令通过npm安装:

npm install axios --save

Include in the calling code, in our case the bookActions.js file:

在我们的示例中,在调用代码中包括bookActions.js文件:

import Axios from 'axios';

更新Reducer以处理Aync动作 (Update Reducer To Handle Aync Actions)

Our reducer just needs to return the state as it is from the server when the the FETCH_BOOK_SUCCESS action is dispatched:

调度FETCH_BOOK_SUCCESS操作时,我们的reducer仅需要从服务器返回状态即可:

// ./src/reducers/bookReducer.js
export default (state = [], action) => {
  switch (action.type) {
    case 'FETCH_BOOKS_SUCCESS':
          return action.books;
    default:
          return state;
  }
};

分页加载 (Dispatch On Page Load)

We need to start the application with some state and the way to go is dispatch the FETCH_BOOKS action on page load. This can be done immediately after configuring the store using the dispatch API:

我们需要以某种状态启动应用程序,而方法是在页面加载时调度FETCH_BOOKS操作。 使用dispatch API配置商店后,可以立即执行以下操作:

// .../src/index.js
// ..preceeding codes
const store = configureStore();
store.dispatch(bookActions.fetchBooks());
// ..rest of code

Of course you must import the actions before they can be dispatched:

当然,必须先导入操作,然后才能分派它们:

import * as bookActions from './actions/bookActions';

First Thunk

Applications that just reads that are not so interesting. Let's try to write by posting to our API endpoint. The thunk to do this is not so different from what we already had for read:

只是读取的应用程序并不那么有趣。 让我们尝试通过发布到我们的API端点进行编写。 可以做到这一点的功能与我们已经阅读的内容没有太大不同:

// ./src/actions/bookActions.js
export const createBook = (book) => {
  return (dispatch) => {
    return Axios.post(apiUrl, book)
      .then(response => {
        // Dispatch a synchronous action
        // to handle data
        dispatch(createBookSuccess(response.data))
      })
      .catch(error => {
        throw(error);
      });
  };
};

Then the corresponding synchronous CREATE_BOOK_SUCCES action:

然后执行相应的同步CREATE_BOOK_SUCCES操作:

// ./src/actions/bookActions.js
export const createBookSuccess = (book) => {
  return {
    type: 'CREATE_BOOK_SUCCESS',
    book
  }
};

The thunk will update the data on the server and return the new created book. For the sake of UX, rather than do a re-fetch of all the data, we just append the single returned to book to the existing books state:

thunk将更新服务器上的数据并返回新创建的 。 为了使用UX,我们无需重新获取所有数据,而是将返回书本的单个追加到现有书本状态:

// ./src/reducers/bookReducer.js
export default (state = [], action) => {
  switch (action.type){
    case 'CREATE_BOOK_SUCCESS':
        return [
          ...state,
          Object.assign({}, action.book)
        ];
    case 'FETCH_BOOKS_SUCCESS':
          return action.books;
    default:
          return state;
  }
};

重构书页 ( Refactor BookPage )

It would be frowned at, if we leave the BookPage component to house the form. What if we have different pages that use this form? It won't be reasonable to re-create it. A better solution is to make the form a component also to improve re-use:

如果我们离开BookPage组件来容纳表单,那将是皱眉。 如果我们有不同的页面使用此表单怎么办? 重新创建它是不合理的。 更好的解决方案是使表单成为组件,以提高重用性:

// ./src/components/book/BookForm.js
import React from 'react';

const BookForm = (props) => {
    // Collector variables
    let titleInput, authorInput, priceInput, yearInput = null;
    return (
      <form onSubmit={e => {
            e.preventDefault();
            // Assemble data into object
            var input = {
              title: titleInput.value,
              author: authorInput.value,
              price: priceInput.value,
              year: yearInput.value
            };
            // Call method from parent component
            // to handle submission
            props.submitBook(input);
            // Reset form
            e.target.reset();
          }}
            className="form-horizontal"
      >
        <div className="input-group">
          <label className="col-sm-2 control-label">Title: </label>
          <div className="col-sm-10">
            <input
              type="text"
              name="title"
              ref={node => titleInput = node}
              className="form-control" />
          </div>
        </div>
        <br/>
        <div className="input-group">
          <label className="col-sm-2 control-label">Author: </label>
          <div className="col-sm-10">
            <input
              type="text"
              name="author"
              ref={node => authorInput = node}
              className="form-control" />
          </div>
        </div>
        <br/>
        <div className="input-group">
          <label className="col-sm-2 control-label">Price: </label>
          <div className="col-sm-10">
            <input
              type="number"
              name="price"
              ref={node => priceInput = node}
              className="form-control" />
          </div>
        </div>
        <br/>
        <div className="input-group">
          <label className="col-sm-2 control-label">Year: </label>
          <div className="col-sm-10">
            <input
              type="text"
              name="year"
              ref={node => yearInput = node}
              className="form-control" />
          </div>
        </div>
        <br/>
        <div className="input-group">
          <div className="col-sm-offset-2 col-sm-10">
            <input type="submit" className="btn btn-default"/>
          </div>
        </div>
      </form>
    );
};

export default BookForm;

Not only did we extract this form, we also added more fields including Author, Price and Year.

我们不仅提取了此表单,还添加了更多字段,包括AuthorPriceYear

There is no need for the luxury of a class component so we used a stateless functional component and to manage events, we delegate them to the parent components by passing them down to the children via props. This is what happens with the submitBook method.

不需要豪华的类组件,因此我们使用了无状态功能组件并管理事件,通过将它们通过props传递给子组件,我们将它们委托给父组件。 这就是submitBook方法会发生的情况。

Let's now see what happens with BookPage component after extracting parts of it's JSX:

现在,让我们看一下提取部分JSX之后BookPage组件的情况:

// ./src/components/book/BookPage.js
import React from 'react';
import { connect } from 'react-redux';
import BookForm from './BookForm';
import { Link } from 'react-router';
import * as bookActions from '../../actions/bookActions';

class Book extends React.Component{
  constructor(props){
    super(props);
  }

  submitBook(input){
    this.props.createBook(input);
  }

  render(){
    let titleInput;
    return(
      <div className="row">
        <div className="col-md-6">
          <h3>Books</h3>
          <table className="table">
            <thead>
              <th>
                <td>Title</td>
                <td></td>
              </th>
            </thead>
            <tbody>
            {this.props.books.map((b, i) => <tr key={i}>
              <td>{b.title}</td>
              <td><Link to={`/books/${b.id}`}>View</Link></td>
            </tr> )}
            </tbody>
          </table>
        </div>
        <div className="col-md-6">
          <h3>New Book</h3>
          {/* Import and inject Book form */}
         <BookForm submitBook={this.submitBook.bind(this)} />
        </div>
      </div>
    )
  }
}
// ... rest of the component code

All we just need to do is to import the BookForm component and inject into the BookPage component. The key line is the one below:

我们所需要做的就是导入BookForm组件并将其注入BookPage组件。 关键是下面的一行:

<BookForm submitBook={this.submitBook.bind(this)} />

图书详细信息页面 ( Book Details Page )

We are making progress and it's a good thing. Next thing to cross off our list is the details page by creating. Just like the BookPage component, we create a wrapper container component and a UI presentation component to display the content. Let's start with the wrapper component:

我们正在取得进步,这是一件好事。 接下来要创建的清单是详细信息页面。 就像BookPage组件一样,我们创建一个包装容器组件和一个UI呈现组件来显示内容。 让我们从包装器组件开始:

// ./src/components/book/BookDetailsPage.js
import React, {PropTypes} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import BookDetails from './BookDetails'

class BookDetailsPage extends React.Component {
    constructor(props, context) {
        super(props, context);
    }

    render() {
        return (
            <div>
                <h1>Book Details Page</h1>
                <BookDetails />
            </div>
        );
    }
}

const mapStateToProps = (state, ownProps) => {
    return {
        // state mappings here
    };
}

const mapDispatchToProps = (dispatch) => {
    return {
        // actions mappings here
    };
}

export default connect(mapStateToProps, mapDispatchToProps)(BookDetailsPage);

This is an almost empty component which we will build upon as we move on. The only thing to check out is that the BookDetails component is imported and added to the JSX. Let's create that:

这是一个几乎是空的组件,我们将在此基础上继续进行。 唯一要检查的是BookDetails组件已导入并添加到JSX中。 让我们创建一个:

// ./sec/components/book/BookDetails.js
import React, {PropTypes} from 'react';

const BookDetails = (props) => {
    return (
      <div className="media">
        <div className="media-left">
          <a href="#">
            <img className="media-object" src="http://placehold.it/200/450" alt="Placehold" />
          </a>
        </div>
        <div className="media-body">
          <h4 className="media-heading">Title</h4>
          <ul>
            <li><stron>Author: </stron> Author</li>
            <li><stron>Price: </stron> Price</li>
            <li><stron>Year: </stron> Year</li>
            <br/>
            <button className="btn btn-primary">Buy</button>
          </ul>
        </div>
      </div>
    );
};


export default BookDetails;

Dump Book Details Page

Basically markup and just showing what we are up to. Now let's move further and consult Redux. Let's begin by adding more actions to our actions file:

基本上是标记,只是显示我们在做什么。 现在,让我们继续前进,并咨询Redux。 首先,将更多操作添加到操作文件中:

// ./src/actions/bookActions.js
// Sync Action
export const fetchBookByIdSuccess = (book) => {
  return {
    type: actionTypes.FETCH_BOOK_BY_ID_SUCCESS,
    book
  }
};
// Async Action
export const fetchBookById = (bookId) => {
  return (dispatch) => {
    return Axios.get(apiUrl + '/' +bookId)
      .then(response => {
        // Handle data with sync action
        dispatch(fetchBookByIdSuccess(response.data));
      })
      .catch(error => {
        throw(error);
      });
  };
};

Just like every other async actions we have encountered but this time takes a parameter which is the ID of the book.

就像我们遇到的所有其他异步操作一样,但是这次使用一个参数,即书的ID。

actionTypes contains all our actions and makes it easy for us to re-use rather than hard-code the action name strings every time:

actionTypes包含我们所有的动作,使我们很容易重复使用,而不是每次都硬编码动作名称字符串:

// ./src/actions/actionTypes.js
export const CREATE_BOOK_SUCCESS = 'CREATE_BOOK_SUCCESS';
export const FETCH_BOOKS_SUCCESS = 'FETCH_BOOKS_SUCCESS';
export const FETCH_BOOK_BY_ID_SUCCESS = 'FETCH_BOOK_BY_ID_SUCCESS';

export const ADD_TO_CART_SUCCESS = 'ADD_TO_CART_SUCCESS';
export const FETCH_CART_SUCCESS = 'FETCH_CART_SUCCESS';

There are more than one options to handle state update in the reducer. Either we just filter the already existing books for the book with the ID we are looking for or fetch the book with the ID from the API and handle with a different reducer.

在Reducer中有多个选项可以处理状态更新。 我们只是为要查找的ID的书籍过滤现有的书籍,或者从API中获取具有ID的书籍,然后使用其他reducer处理。

We will go with the second option I listed above just to demonstrate that it is possible to have multiple reducers in your Redux app. So back to bookReducers, update the content with:

我们将使用上面列出的第二个选项,只是为了证明您的Redux应用程序中可能有多个reducer。 然后回到bookReducers ,用以下内容更新内容:

// ./src/reducers/bookReducers.js
// For handling array of books
export const booksReducer = (state = [], action) => {
  switch (action.type) {
    case actionTypes.CREATE_BOOK_SUCCESS:
        return [
          ...state,
          Object.assign({}, action.book)
        ];
    case actionTypes.FETCH_BOOKS_SUCCESS:
          return action.books;
    default:
          return state;
  }
};

// For handling a single book
export const bookReducer = (state = [], action) => {
  switch (action.type) {
    // Handle fetch by Id
    case actionTypes.FETCH_BOOK_BY_ID_SUCCESS:
      return action.book;
    default:
      return state;
  }
};

This time the reducers are named so we can use named import to gain access to them from the root reducer file:

这次,为reducer命名,因此我们可以使用命名的import从根reducer文件中访问它们:

// ./src/reducers/index.js
import { combineReducers } from 'redux';
import {booksReducer, bookReducer} from './bookReducers'

export default combineReducers({
  books: booksReducer,
  book: bookReducer
});

Now, we can map our state and actions to the props in the BookDetailsPage component. As usual, we create two functions to handle the tasks and connect these functions to the component:

现在,我们可以将状态和动作映射到BookDetailsPage组件中的道具。 与往常一样,我们创建两个函数来处理任务并将这些函数连接到组件:

// ./src/components/book/BookDetailsPage.js
// Map state to props
const mapStateToProps = (state, ownProps) => {
    return {
      book: state.book
    };
};
// Map dispatch to props
const mapDispatchToProps = (dispatch) => {
    return {
      // This dispatch will trigger 
      // the Ajax request we setup
      // in our actions
      fetchBookById: bookId => dispatch(bookActions.fetchBookById(bookId))
    };
};

export default connect(mapStateToProps, mapDispatchToProps)(BookDetailsPage);

We fire the fetchByIdaction method in the component's componentDidMount lifecycle which will start the process immediately the component is ready:

我们在组件的componentDidMount生命周期中触发fetchById操作方法,该方法将在组件准备就绪后立即启动该过程:

componentDidMount(){
   this.props.fetchBookById(this.props.params.id);
}

Next is to pass down the book to BookDetails component via props:

接下来是通过道具将传递到BookDetails组件:

// ./src/components/book/BookDetailsPage.js
<BookDetails book={this.props.book}/>

Then update the BookDetails component to bind the props content to the view:

然后更新BookDetails组件以将props内容绑定到视图:

// ./src/components/book/BookDetails.js
import React from 'react';

const BookDetails = ({book}) => {
    return (
      <div className="media">
        <div className="media-left">
          <a href="#">
            <img className="media-object" src="http://placehold.it/200/550" alt="Placehold" />
          </a>
        </div>
        <div className="media-body">
          <h4 className="media-heading">{book.title}</h4>
          <ul>
            <li><stron>Author: </stron> {book.author}</li>
            <li><stron>Price: </stron> ${book.price}</li>
            <li><stron>Year: </stron> {book.year}</li>
            <br/>
            <button className="btn btn-primary">Buy</button>
          </ul>
        </div>
      </div>
    );
};


export default BookDetails;

持久购物车 ( Persisted Shopping Cart )

Just to go through the process of Async actions, let's treat one more entity, the cart. The logic behind a shopping cart is largely complex compared to just persisting the users cart items on the server but we just need to learn Redux Thunk without considering so much about carts conventions.

只是为了完成异步动作的过程,让我们再处理一个实体,即cart 。 与仅将用户购物车中的商品持久存储在服务器上相比,购物车背后的逻辑非常复杂,但是我们只需要学习Redux Thunk,而无需过多考虑购物车惯例。

Let's iterate through the process as a numbered step so it can serve as a reference for you anytime you have anything to do with React, Redux and Redux Thunk:

让我们以编号的顺序遍历整个过程,以便在您与React,Redux和Redux Thunk有任何关系时可以为您提供参考:

1.创建组件 (1. Create Components)

We need to create a cart page component but before that, let's update BookDetails so as to handle click event for the buy button:

我们需要创建一个购物车页面组件,但是在此之前,让我们更新BookDetails以便处理购买按钮的click事件:

// ./src/components/book/BookDetails.js
import React from 'react';

const BookDetails = ({book, addToCart}) => {
    return (
      <div className="media">
        <div className="media-left">
          <a href="#">
            <img className="media-object" src="http://placehold.it/200x280" alt="Placehold" />
          </a>
        </div>
        <div className="media-body">
          <h4 className="media-heading">{book.title}</h4>
          <ul>
            <li><stron>Author: </stron> {book.author}</li>
            <li><stron>Price: </stron> ${book.price}</li>
            <li><stron>Year: </stron> {book.year}</li>
            <br/>
            {/* onClick event */}
            <button className="btn btn-primary" onClick={e => addToCart(book)}>Buy</button>
          </ul>
        </div>
      </div>
    );
};


export default BookDetails;

The event is handled by a method on the props which is passed down by the parent container so we destructure to get the method. The method is called passing it a book instance. Add the method to BookDetailsPage so it won't be undefined:

该事件由props上的方法处理,该方法由父容器向下传递,因此我们进行结构分解以获取该方法。 该方法称为将书实例传递给它。 将方法添加到BookDetailsPage以便不会被未定义:

addToCart(book){
      const item = {
        title: book.title,
        price: book.price
      };
      this.props.addToCart(item);
    }

Next we create a CartPage to handle items in the cart:

接下来,我们创建一个CartPage来处理购物车中的物品:

// ./src/components/cart/CartPage.js
import React, {PropTypes} from 'react';
import {connect} from 'react-redux';
import * as bookActions from '../../actions/bookActions';


class CartPage extends React.Component {
    constructor(props, context) {
        super(props, context);
    }

  componentDidMount(){
    this.props.fetchCart();
  }

    render() {
        return (
            <div>
                <h1>Cart Page</h1>
                <table className="table">
                  <tr>
                    <th>Title</th>
                    <th>Price</th>
                  </tr>
                  {this.props.items.map((item, index) => {
                    return (
                      <tr key={index}>
                        <td>{item.title}</td>
                        <td>{item.price}</td>
                      </tr>
                    );
                  })}
                </table>
            </div>
        );
    }
}

export default CartPage;

The props will be mapped using React-Redux's connect later but for now, we will stick to the steps. The lifecycle method is also calling a method on the props which is also yet to be mapped but will serve load the cart data once the component mounts.

稍后将使用React-Redux的connect来映射道具,但是现在,我们将坚持步骤。 生命周期方法还在props上调用了一个方法,该方法也有待映射,但是一旦组件安装,它将用于加载购物车数据。

Finally in this step, we add an extra routes to our existing routes to serve the cart page:

最后,在此步骤中,我们在现有路线上添加了一条额外的路线以服务购物车页面:

// ./src/routes.js
<Route path="/" component={App}>
   <IndexRoute component={Home}></IndexRoute>
   <Route path="/about" component={About}></Route>
   <Route path="/books" component={BookPage}></Route>
   <Route path="/books/:id" component={BookDetailsPage}></Route>
   {/* Extra route to serve Cart. Don't forget to import*/}
   <Route path="/cart" component={CartPage}></Route>
 </Route>

2动作与动作创作者 (2 Actions & Action Creators)

We need to adding items and fetching items and each of this process will have an async action which use thunk to do the API fetch and a sync action which is dispatched with async's returned data:

我们需要添加项目并提取项目,并且每个过程都会有一个异步操作,该操作使用thunk来进行API提取,还有一个同步操作,该操作与异步返回的数据一起发送:

// ./src/actions/bookActions.js
// Sync add to cart
export const addToCartSuccess = (item) => {
  return {
    type: 'ADD_TO_CART_SUCCESS',
    item
  }
};
// Async add to cart
export const addToCart = (item) => {
  return (dispatch) => {
    return Axios.post('http://57c64baac1fc8711008f2a82.mockapi.io/Cart', item)
      .then(response => {
        dispatch(addToCartSuccess(response.data))
      })
      .catch(error => {
        throw(error);
      });
  };
};
// Sync load cart
export const fetchCartSuccess = (items) => {
  return {
    type: 'FETCH_CART_SUCCESS',
    items
  }
};
// Async load cart
export const fetchCart = () => {
  return (dispatch) => {
    return Axios.get('http://57c64baac1fc8711008f2a82.mockapi.io/Cart')
      .then(response => {
        dispatch(fetchCartSuccess(response.data))
      })
      .catch(error => {
        throw(error);
      });
  };
};

3.国家采取行动 (3. State Reducers For Actions)

With the actions set, we can set up reducers to update state based on the actions we created. The reducers for cart is simple:

设置好动作后,我们可以设置reducer以根据我们创建的动作来更新状态。 购物车的减速器很简单:

// ./src/reducers/cartReducers.js
export default (state = [], action) => {
  switch (action.type) {
    case 'ADD_TO_CART_SUCCESS':
      return action.item;
    case 'FETCH_CART_SUCCESS':
      return action.items;
    default:
      return state;
  }
};

4.连接组件 (4. Connect Component)

Most boilerplate task like configuring and creating store was skipped because we already did that in the previous article. We just need to connect our CartPage component and then map states and actions to it's props:

跳过了诸如配置和创建存储之类的大多数样板任务,因为我们在上一篇文章中已经做了。 我们只需要连接CartPage组件,然后将状态和动作映射到其props:

// ./src/components/cart/CartPage.js
// ...rest of component body
const mapStateToProps = (state, ownProps) => {
    return {
      items: state.cart
    };
};

const mapDispatchToProps = (dispatch) => {
    return {
      fetchCart: bookId => dispatch(bookActions.fetchCart()),
    };
};

export default connect(mapStateToProps, mapDispatchToProps)(CartPage);

Now the items and fetchCart props which we were making use of in the component body are now defined and has a value.

现在,我们已经定义了我们在组件主体中使用的itemsfetchCart道具并具有值。

Cart Page

结语 ( Wrap up )

Glad to we got to the end of this tutorial. Redux Thunk has alternatives like Redux Saga which makes use of ES6 generators and you can check out.

很高兴我们到了本教程的结尾。 Redux Thunk具有Redux Saga之类的替代方案,该替代方案利用了ES6生成器,您可以签出。

You could observe the the app is not frictionless and shows stale data for a while before updating while navigating. What you could do is use a loading indicator to delay display until the new data is ready.

您可能会发现该应用并非一帆风顺,并在导航之前进行更新之前显示了一段时间的数据。 您可以使用加载指示器来延迟显示,直到准备好新数据为止。

Also checkout React-Rouer-Redux which keeps everything in sync between React, Redux and React Router.

还要签出React-Rouer-Redux ,使React,Redux和React Router之间的一切保持同步。

翻译自: https://scotch.io/tutorials/bookshop-with-react-redux-ii-async-requests-with-thunks

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ReactRedux是两个独立的库,但它们可以很好地结合使用以管理异步数据。Redux是一个状态管理库,它可以帮助你存储和管理你的应用程序的状态。而React是一个用于构建用户界面的库。 在React中使用Redux来处理异步数据的一种常见模式是使用Redux Thunk中间件。Thunk是一个允许你在Redux中处理异步逻辑的函数,它可以帮助你在Redux中分发异步操作。 首先,你需要安装ReduxRedux Thunk库: ``` npm install redux npm install redux-thunk ``` 然后,你需要创建Redux store,引入ReduxRedux Thunk,并将Redux Thunk作为Redux的中间件使用: ```javascript import { createStore, applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; import rootReducer from './reducers'; const store = createStore(rootReducer, applyMiddleware(thunk)); ``` 在你的Redux应用程序中,你可以创建一个action来处理异步操作。例如,你可以创建一个用于获取数据的异步操作: ```javascript export const fetchData = () => { return (dispatch) => { dispatch(fetchDataRequest()); // 异步操作,例如通过API获取数据 fetch('https://api.example.com/data') .then(response => response.json()) .then(data => { dispatch(fetchDataSuccess(data)); }) .catch(error => { dispatch(fetchDataFailure(error)); }); }; }; const fetchDataRequest = () => { return { type: 'FETCH_DATA_REQUEST' }; }; const fetchDataSuccess = (data) => { return { type: 'FETCH_DATA_SUCCESS', payload: data }; }; const fetchDataFailure = (error) => { return { type: 'FETCH_DATA_FAILURE', payload: error }; }; ``` 在上面的示例中,`fetchData`是一个异步操作,它使用了Redux Thunk来处理异步逻辑。它首先分发一个`fetchDataRequest` action,然后执行异步操作(例如通过API获取数据),最后分发一个`fetchDataSuccess`或`fetchDataFailure` action,具体取决于异步操作的结果。 你可以在React组件中使用`connect`函数将这些action和Redux store连接起来,并将它们映射到组件的props上。这样,你就可以在组件中调用这些action来处理异步数据了。 希望这个例子能帮助你理解如何在ReactRedux中处理异步数据。如果你还有其他问题,请随时提问!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值