我正在使用ReactJS + Redux以及Express和Webpack。建立了一個API,我希望能夠從客戶端進行REST調用 - GET,POST,PUT,DELETE。如何從ReactJS + Redux應用程序正確地創建REST調用?
在Redux架構下如何以及如何正確實現?就減速器,動作創造者,商店和反應路線而言,流動的任何好例子都是非常有用的。
預先感謝您!
我正在使用ReactJS + Redux以及Express和Webpack。建立了一個API,我希望能夠從客戶端進行REST調用 - GET,POST,PUT,DELETE。如何從ReactJS + Redux應用程序正確地創建REST調用?
在Redux架構下如何以及如何正確實現?就減速器,動作創造者,商店和反應路線而言,流動的任何好例子都是非常有用的。
預先感謝您!
最簡單的方法,就是用redux-thunk
包來做。這個包是一個終極版中間件,所以首先,你應該把它連接到終極版:
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
});
}
});
}
}
我更喜歡在展示和容器組件上分離反應組分。這種方法在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
});
對於其他操作,如CREATE
,UPDATE
,DELETE
沒有什麼特別的,他們正在實施方式相同。
這是庫的主要用例,如redux-thunk
,redux-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直接採取這實在是對你的問題的答案的權威來源。
Thunk究竟做了什麼特別的事情呢?看起來像你可以只是簡單地做到沒有任何庫的API()到API的URL。 –
'redux-thunk'在體系結構上非常有用,可將異步行爲集成到同步的redux中。 'fetch'就足夠網絡電話了,但那很容易。當你開始詢問如何通過redux應用程序進行調用時,你需要像'redux-thunk'這樣的東西將這種行爲合併到你的redux體系結構中。 –
真的很感謝澄清!換句話說,GET是否正確?那麼,POST,PUT和DELETE會是什麼? –
簡短的回答是:
redux-thunk
和redux-saga
。對於您可以放入您的REDX應用程序的大腦死亡簡單的庫,您可以嘗試redux-crud-store。免責聲明:我寫了。您也可以閱讀redux-crud-store的源代碼,如果您有興趣將fetch API或其他API客戶端與redux-saga集成在一起
非常感謝您的幫助。仍然試圖抓住這個概念。你如何以及在哪裏從組件中調用動作? (({response,body})=> {},然後你可以澄清一下'.then(response => response.json()。then(body =>({response,body}))).then '在做什麼?再次感謝 –
@JoKo,是的,我很快就會更新答案。 – 1ven
@JoKo,更新回答 – 1ven