【问题标题】:How to properly make REST calls from ReactJS + Redux application?如何正确地从 ReactJS + Redux 应用程序进行 REST 调用?
【发布时间】:2016-12-08 07:21:56
【问题描述】:

我正在使用 ReactJS + Redux,以及 Express 和 Webpack。构建了一个 API,我希望能够从客户端进行 REST 调用——GET、POST、PUT、DELETE。

使用 Redux 架构如何以及正确的方法是什么?在 reducer、action creators、store 和 react 路由方面,任何好的流程示例都会非常有帮助。

提前谢谢你!

【问题讨论】:

    标签: javascript html reactjs redux react-jsx


    【解决方案1】:

    最简单的方法是使用redux-thunk 包。这个包是一个 redux 中间件,所以首先你应该把它连接到 redux:

    import { createStore, applyMiddleware } from 'redux';
    import thunk from 'redux-thunk';
    import rootReducer from './reducers/index';
    
    const store = createStore(
      rootReducer,
      applyMiddleware(thunk)
    );
    

    这允许您调度 async 操作以及常规 sync 操作。让我们创建一个:

    // actions.js
    
    export function fetchTodos() {
      // Instead of plain objects, we are returning function.
      return function(dispatch) {
        // Dispatching REQUEST action, which tells our app, that we are started requesting todos.
        dispatch({
          type: 'FETCH_TODOS_REQUEST'
        });
        return fetch('/api/todos')
          // Here, we are getting json body(in our case it will contain `todos` or `error` prop, depending on request was failed or not) from server response
          // And providing `response` and `body` variables to the next chain.
          .then(response => response.json().then(body => ({ response, body })))
          .then(({ response, body }) => {
            if (!response.ok) {
              // If request was failed, dispatching FAILURE action.
              dispatch({
                type: 'FETCH_TODOS_FAILURE',
                error: body.error
              });
            } else {
              // When everything is ok, dispatching SUCCESS action.
              dispatch({
                type: 'FETCH_TODOS_SUCCESS',
                todos: body.todos
              });
            }
          });
      }
    }
    

    我更喜欢将 React 组件与展示组件和容器组件分开。这种方法在this article 中得到了完美的描述。

    接下来,我们应该创建TodosContainer 组件,该组件将为展示性Todos 组件提供数据。在这里,我们使用react-redux 库:

    // TodosContainer.js
    
    import React, { Component } from 'react';
    import { connect } from 'react-redux';
    import { fetchTodos } from '../actions';
    
    class TodosContainer extends Component {
      componentDidMount() {
        // When container was mounted, we need to start fetching todos.
        this.props.fetchTodos();
      }
    
      render() {
        // In some simple cases, it is not necessary to create separate `Todos` component. You can put todos markup directly here.
        return <Todos items={this.props.todos} />
      }
    }
    
    // This function is used to convert redux global state to desired props.
    function mapStateToProps(state) {
      // `state` variable contains whole redux state.
      return {
        // I assume, you have `todos` state variable.
        // Todos will be available in container component as `this.props.todos`
        todos: state.todos
      };
    }
    
    // This function is used to provide callbacks to container component.
    function mapDispatchToProps(dispatch) {
      return {
        // This function will be available in component as `this.props.fetchTodos`
        fetchTodos: function() {
          dispatch(fetchTodos());
        }
      };
    }
    
    // We are using `connect` function to wrap our component with special component, which will provide to container all needed data.
    export default connect(mapStateToProps, mapDispatchToProps)(TodosContainer);
    

    此外,您应该创建todosReducer,它将处理FETCH_TODOS_SUCCESS 操作,如果您想显示加载程序/错误消息,还应处理其他 2 个操作。

    // reducers.js
    
    import { combineReducers } from 'redux';
    
    const INITIAL_STATE = {
      items: [],
      isFetching: false,
      error: undefined
    };
    
    function todosReducer(state = INITIAL_STATE, action) {
      switch (action.type) {
        case 'FETCH_TODOS_REQUEST':
          // This time, you may want to display loader in the UI.
          return Object.assign({}, state, {
            isFetching: true
          });
        case 'FETCH_TODOS_SUCCESS':
          // Adding derived todos to state
          return Object.assign({}, state, {
            isFetching: false,
            todos: action.todos
          });
        case 'FETCH_TODOS_FAILURE':
          // Providing error message to state, to be able display it in UI.
          return Object.assign({}, state, {
            isFetching: false,
            error: action.error
          });
        default:
          return state;
      }
    }
    
    export default combineReducers({
      todos: todosReducer
    });
    

    对于CREATEUPDATEDELETE 等其他操作没有什么特别之处,它们的实现方式相同。

    【讨论】:

    • 非常感谢您的帮助。仍然试图掌握这个概念。您如何以及在何处调用组件中的操作?您能否进一步说明.then(response =&gt; response.json().then(body =&gt; ({ response, body }))) .then(({ response, body }) =&gt; { 在做什么?再次感谢
    • 抱歉,bodyresponse.ok 等被任意命名?或者这就是它们在 API 中的命名方式?最后,我正在尝试使用以下 API "http://www.omdbapi.com?s=star&amp;y=&amp;r=json&amp;plot=short"response 进行测试,想要访问诸如 movie.title 之类的东西。你能举个例子吗?真的很有帮助!
    • bodyresponse.ok 是变量,它们与任何 API 无关。所有必要的响应数据都将在 body 变量中,而不是在 response 电影数组中,可在 body.Search 获得。
    • 非常感谢!接受了答案。你碰巧知道 React Native 吗?
    • @JoKo,是的,我有一些经验。
    【解决方案2】:

    简短的回答是:

    1. redux 不是架构
    2. 您可以使用任何库。现在很多人直接使用 fetch API。
    3. 为了能够将 redux 与异步操作(AJAX 需要)集成,您需要使用库来提供帮助。正如其他人所说,最受欢迎的两个是 redux-thunkredux-saga

    对于可以放入您的 redux 应用程序的脑死简单库,您可以尝试redux-crud-store。免责声明:我写的。如果您有兴趣将 fetch API 或其他 API 客户端与 redux-saga 集成,您还可以阅读 redux-crud-store 的源代码

    【讨论】:

      【解决方案3】:

      这是 redux-thunkredux-sagaredux-observable 等库的主要用例。

      redux-thunk 是最简单的,你可以这样做:

      import fetch from 'isomorphic-fetch'
      
      export const REQUEST_POSTS = 'REQUEST_POSTS'
      function requestPosts(subreddit) {
        return {
          type: REQUEST_POSTS,
          subreddit
        }
      }
      
      export const RECEIVE_POSTS = 'RECEIVE_POSTS'
      function receivePosts(subreddit, json) {
        return {
          type: RECEIVE_POSTS,
          subreddit,
          posts: json.data.children.map(child => child.data),
          receivedAt: Date.now()
        }
      }
      
      // Meet our first thunk action creator!
      // Though its insides are different, you would use it just like any other action creator:
      // store.dispatch(fetchPosts('reactjs'))
      
      export function fetchPosts(subreddit) {
      
        // Thunk middleware knows how to handle functions.
        // It passes the dispatch method as an argument to the function,
        // thus making it able to dispatch actions itself.
      
        return function (dispatch) {
      
          // First dispatch: the app state is updated to inform
          // that the API call is starting.
      
          dispatch(requestPosts(subreddit))
      
          // The function called by the thunk middleware can return a value,
          // that is passed on as the return value of the dispatch method.
      
          // In this case, we return a promise to wait for.
          // This is not required by thunk middleware, but it is convenient for us.
      
          return fetch(`http://www.reddit.com/r/${subreddit}.json`)
            .then(response => response.json())
            .then(json =>
      
              // We can dispatch many times!
              // Here, we update the app state with the results of the API call.
      
              dispatch(receivePosts(subreddit, json))
            )
      
            // In a real world app, you also want to
            // catch any error in the network call.
        }
      }
      

      以上示例直接取自http://redux.js.org/docs/advanced/AsyncActions.html,这确实是您问题答案的权威来源。

      【讨论】:

      • Thunk 到底做了什么这么特别?似乎您可以在没有任何库的情况下简单地对 API 的 URL 执行 fetch()。
      • redux-thunk 在架构上可用于将异步行为集成到同步的 redux 中。 fetch 足以进行网络调用,但这是最简单的部分。当您开始询问如何从 redux 应用程序进行调用时,您需要像 redux-thunk 这样的东西来将该行为合并到您的 redux 架构中。
      • 非常感谢您的澄清! fetch 是不是 GET 正确?那么 POST、PUT 和 DELETE 会是什么?
      • fetch 不只是为了获取。你可以用它提出所有类型的请求。 MDN 上的文档很有用,这里有一篇博文,里面有一些例子:davidwalsh.name/fetch
      猜你喜欢
      • 2018-01-27
      • 1970-01-01
      • 2021-08-22
      • 2016-05-03
      • 1970-01-01
      • 1970-01-01
      • 2020-05-05
      • 1970-01-01
      • 2020-06-14
      相关资源
      最近更新 更多