如何在redux中进行AJAX请求

67

据我所知,我需要在动作创建中编写请求。如何在动作中使用promise来提交请求?我在操作中获取数据。然后在reducer中创建新状态。将操作和reducer绑定在connect中。但我不知道如何使用promise进行请求。

动作

import $ from 'jquery';
export const GET_BOOK = 'GET_BOOK';

export default function getBook() {
  return {
    type: GET_BOOK,
    data: $.ajax({
      method: "GET",
      url: "/api/data",
      dataType: "json"
    }).success(function(data){
      return data;
    })
  };
}

减速器

import {GET_BOOK} from '../actions/books';

const booksReducer = (state = initialState, action) => {
  switch (action.type) {
    case GET_BOOK:
      return state;
    default:
      return state;
  }
};

export default booksReducer;

容器 如何在容器中显示数据?

import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import getBook  from '../actions/books';
import Radium from 'radium';
import {Link} from 'react-router';

function mapStateToProps(state) {
  return {
    books: state.data.books,
  };
}

function mapDispatchToProps(dispatch) {
  return {
    getBooks: () => dispatch(getBook()),
  };
}

@Radium
@connect(mapStateToProps, mapDispatchToProps)
class booksPage extends Component {
  static propTypes = {
    getBooks: PropTypes.func.isRequired,
    books: PropTypes.array.isRequired,
  };

  render() {
    const {books} = this.props;
    return (
      <div>
        <Link to={`/authors`}><MUIButton style="flat">All Authors</MUIButton></Link>
        <ul>
          {books.map((book, index) =>
            <li key={index}>
              <Link to={`/book/${book.name}`}><MUIButton style="flat"><div class="mui--text-black mui--text-display4">
                "{book.name}"</div></MUIButton></Link>
              <Link to={`/author/${book.author}`}><MUIButton style="flat"><div class="mui--text-black mui--text-display4">
                {book.author}</div></MUIButton></Link>
            </li>
          )}
        </ul>
      </div>
    );
  }
}

export default booksPage;
5个回答

56

既然你已经在使用redux,你可以应用redux-thunk中间件,它允许你定义异步操作。

安装和使用:Redux-thunk

export function fetchBook(id) {
 return dispatch => {
   dispatch(setLoadingBookState()); // Show a loading spinner
   fetch(`/book/${id}`, (response) => {
     dispatch(doneFetchingBook()); // Hide loading spinner
     if(response.status == 200){
       dispatch(setBook(response.json)); // Use a normal function to set the received state
     }else { 
       dispatch(someError)
     }
   })
 }
}

function setBook(data) {
 return { type: 'SET_BOOK', data: data };
}

30
你应该使用 Redux文档中描述的异步操作
这里是一个用于异步操作的reducer示例。
const booksReducer = (state = {}, action) => {
  switch (action.type) {
    case 'RESOLVED_GET_BOOK':
      return action.data;
    default:
      return state;
  }
};

export default booksReducer;

然后创建异步操作。

export const getBook() {
  return fetch('/api/data')
    .then(response => response.json())
    .then(json => dispatch(resolvedGetBook(json)))
}

export const resolvedGetBook(data) {
  return {
    type: 'RESOLVED_GET_BOOK',
    data: data
  }
}

几点注意事项:

  • 我们可以通过使用redux-thunk中间件,在action中返回Promise(而不是Object)。
  • 不要使用jQuery ajax库。使用专门用于此操作的其他库(例如fetch())。我使用axios http client
  • 请记住,在redux中,您只在reducer中使用纯函数。不要在reducer内进行ajax调用。
  • 阅读redux文档中的完整指南。

14

如果您将 dispatch 作为参数传递,那么您应该能够在回调函数中使用它:

export default function getBook(dispatch) {
  $.ajax({
      method: "GET",
      url: "/api/data",
      dataType: "json"
    }).success(function(data){
      return dispatch({type:'GET_BOOK', data: data});
    });
}

然后,将 dispatch 传递给操作:

function mapDispatchToProps(dispatch) {
  return {
    getBooks: () => getBook(dispatch),
  };
}

现在,在reducer中,您应该可以访问action.data属性:

const booksReducer = (state = initialState, action) => {
  switch (action.type) {
    case GET_BOOK:
      //action.data  <--- here
      return state;
    default:
      return state;
  }
};

谢谢,但现在我收到了警告:在“booksPage”中未指定所需的prop“books”。请检查“Connect(booksPage)”的渲染方法。警告@(program):45 (program):45 警告:getDefaultProps仅用于经典的React.createClass定义。请改用名为“defaultProps”的静态属性。 - Disa Skolzin
你把 action.data 减少到状态中了吗? - Davin Tryon
Object.assign({}, state, { books: action.data.books, authors: action.data.authors }); - Disa Skolzin
如果你这样缩减,那么 state.data.books 将无法在你的 mapStateToProps 中找到。在那里使用 console.log 并查看 state 是什么。 - Davin Tryon
我使用了console.log,并且state.data等于0。这意味着GET_BOOK情况不起作用。但是为什么? - Disa Skolzin
我使用了console.log(action.type)并得到了@@redux/INIT,@@redux/PROBE_UNKNOWN_ACTION_o.4.d.n.q.g.d.s.4.i,@@INIT和@@reduxReactRouter/routerDidChange。 - Disa Skolzin

7
你可能希望分开关注点,让操作创建者保持“纯净”。解决方法是编写一些中间件。以使用 Superagent 为例。
import Request from 'superagent';

const successHandler = (store,action,data) => {

    const options = action.agent;
    const dispatchObject = {};
    dispatchObject.type = action.type + '_SUCCESS';
    dispatchObject[options.resourceName || 'data'] = data;
    store.dispatch(dispatchObject);
};

const errorHandler = (store,action,err) => {

    store.dispatch({
        type: action.type + '_ERROR',
        error: err
    });
};

const request = (store,action) => {

    const options = action.agent;
    const { user } = store.getState().auth;
    let method = Request[options.method];

    method = method.call(undefined, options.url)

    if (user && user.get('token')) {
        // This example uses jwt token
        method = method.set('Authorization', 'Bearer ' + user.get('token'));
    }

    method.send(options.params)
    .end( (err,response) => {
        if (err) {
            return errorHandler(store,action,err);
        }
        successHandler(store,action,response.body);
    });
};

export const reduxAgentMiddleware = store => next => action => {

    const { agent } = action;

    if (agent) {
        request(store, action);
    }
    return next(action);
};

将所有内容放入一个模块中。
现在,您可能有一个名为“auth”的操作创建者:
export const auth = (username,password) => {

    return {
        type: 'AUTHENTICATE',
        agent: {
            url: '/auth',
            method: 'post',
            resourceName: 'user',
            params: {
                username,
                password
            }
        }
    };
};

中间件将获取'agent'属性,发送构建好的请求到网络,并将收到的结果分发给您的存储位置。

在您定义钩子后,您的reducer将处理所有这些操作:

import { Record } from 'immutable';

const initialState = Record({
    user: null,
    error: null
})();

export default function auth(state = initialState, action) {

    switch (action.type) {

        case 'AUTHENTICATE':

            return state;

        case 'AUTHENTICATE_SUCCESS':

            return state.merge({ user: action.user, error: null });

        case 'AUTHENTICATE_ERROR':

            return state.merge({ user: null, error: action.error });

        default:

            return state;
    }
};

现在将所有这些内容注入您的视图逻辑中。我以react为例。
import React from 'react';
import ReactDOM from 'react-dom';

/* Redux + React utils */
import { createStore, applyMiddleware, bindActionCreators } from 'redux';
import { Provider, connect } from 'react-redux';

// thunk is needed for returning functions instead 
// of plain objects in your actions.
import thunkMiddleware from 'redux-thunk';

// the logger middleware is useful for inspecting data flow
import createLogger from 'redux-logger';

// Here, your new vital middleware is imported
import { myNetMiddleware } from '<your written middleware>';

/* vanilla index component */
import _Index from './components';

/* Redux reducers */
import reducers from './reducers';

/* Redux actions*/
import actionCreators from './actions/auth';


/* create store */
const store = createStore(
    reducers,
    applyMiddleware(
        thunkMiddleware,
        myNetMiddleware
    )
);

/* Taint that component with store and actions */
/* If all goes well props should have 'auth', after we are done */
const Index = connect( (state) => {

    const { auth } = state;

    return {
        auth
    };
}, (dispatch) => {

    return bindActionCreators(actionCreators, dispatch);
})(_Index);

const provider = (
    <Provider store={store}>
        <Index />
    </Provider>
);

const entryElement = document.getElementById('app');
ReactDOM.render(provider, entryElement);

这意味着您已经使用Webpack、Rollup或其他工具设置了一个流水线,将ES2015和React转换为Vanilla JS。


-1

考虑使用新的thunk API

export const load = createAsyncThunk(
  'example/api',
  async (arg, thunkApi) => {
    const response = await fetch('http://example.api.com/api')

    if (response.status === 200) {
      const json = await response.json()
      return json
  },
)

此外,在新的redux模板应用程序中,操作是减速器/切片的一部分,您可以使用extraReducers响应与异步操作状态相关的事件。使用redux这种方式要简单得多。
在这里查看异步thunk的文档:https://redux.js.org/usage/writing-logic-thunks

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接