web-dev-qa-db-ja.com

ReactJS + ReduxアプリケーションからRESTを適切に呼び出す方法は?

ExpressとWebpackと共にReactJS + Reduxを使用しています。 APIが構築されており、クライアント側からREST呼び出し-GET、POST、PUT、DELETE-を行えるようにしたい。

Reduxアーキテクチャを使用して適切に実行する方法と方法は何ですか?レデューサー、アクションクリエーター、ストア、およびリアクションルートに関して、フローの良い例は非常に役立ちます。

前もって感謝します!

26
Jo Ko

最も簡単な方法は、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アクションをディスパッチできます。それらの1つを作成しましょう。

// 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
          });
        }
      });
  }
}

プレゼンテーションコンポーネントとコンテナコンポーネントで反応コンポーネントを分離することを好みます。このアプローチは この記事 で完全に説明されています。

次に、プレゼンテーションの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などの他の操作については、特別なことは何もありません。それらは同じ方法で実装されています。

28
1ven

短い答えは次のとおりです。

  1. reduxはアーキテクチャではありません
  2. 任意のライブラリを使用できます。最近では多くの人がフェッチAPIを直接使用しています。
  3. Reduxと非同期アクション(AJAXに必要)を統合できるようにするには、ライブラリを使用して支援する必要があります。最も人気のある2つはredux-thunkおよびredux-saga、他の人が言ったように。

Reduxアプリにドロップできる脳死のシンプルなライブラリの場合、 redux-crud-store を試すことができます。免責事項:私はそれを書きました。フェッチAPIまたは別のAPIクライアントをredux-sagaと統合することに関心がある場合は、redux-crud-storeのソースを読むこともできます。

2
Devin Howard

これは、 redux-thunkredux-saga 、および redux-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 から直接取得したものです。これは、実際に質問に対する答えの決定的なソースです。

1
Nathan Hagen