React-Redux 使用 Fetch 实现简单商城购物车

在上一篇 React-Redux 中使用中间件用 fetch 进行网络请求的例子基础上,继续来看使用 React-Redux 商城购物车的实现。主要的两个功能:

  • 获取所有商品列表
  • 将商品添加进购物车

添加服务器 Server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
var http = require("http");
var url = require("url");
var goods = [
{id: "10001", summary: "iPhone5S", price: 1345},
{id: "10002", summary: "iPhone6S", price: 4345},
{id: "10003", summary: "iPhone7", price: 5345}
];
var order = {
userId: "110",
goods: []
};
http.createServer((req, resp)=> {
var url = req.url;
console.log(url);
var data = null;
var statusCode = 0;
var statusMsg = "";
switch (url) {
case "/user":
data = {
id: "110",
name: "admin",
age: 26
};
break;
case "/goods":
data = goods;
break;
case "/order":
data = order;
break;
default:
if (url.indexOf("/order/add/") > -1) {
var id = url.replace("/order/add/", "");
var filteredGoods = goods.filter(good=> {
return good.id === id;
});
if (filteredGoods.length) { // add success
data = filteredGoods[0];
order.goods.push(filteredGoods[0]);
} else { // add fail
data = {};
statusCode = -1;
statusMsg = "Not Exists";
}
break;
}
if (url.indexOf("/order/rm/") > -1) {
var id = url.replace("/order/rm/", "");
var filteredGoods = order.goods.filter(good=> {
return good.id === id;
});
if (filteredGoods.length) { // remove success
var newGoods = order.goods.filter(good=> {
return good.id != id;
});
data = filteredGoods[0];
order.goods = newGoods;
} else { // remove fail
data = {};
statusCode = -1;
statusMsg = "Not Exists";
}
break;
}
data = goods;
break;
}
var obj = {
statusCode: statusCode,
statusMsg: statusMsg,
data: data
}
resp.setHeader("Access-Control-Allow-Origin", "*");
setTimeout(()=>resp.end(JSON.stringify(obj)), 1200);
}).listen(3000);

上面的模拟服务器提供了5个接口:

  • /user : 模拟登录
  • /goods : 获取所有的商品列表
  • /order : 获取购物车
  • /order/add/ : 添加商品进购物车
  • /order/rm/ : 从购物车中移除商品

服务器返回的 json 格式为:

1
2
3
4
5
{
statusCode: statusCode, // 状态码,成功?失败
statusMsg: statusMsg,
data: data // 数据
}

step1: 定义 state 结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
state = {
netstatus: {
isLoading: false,
isSuccess: true
},
user: {
id: "110",
name: "",
age: 26
},
order: {
userId: "110",
goods: [
{id: "10001", summary: "iPhone5S", price: 1345},
// ...
]
},
goods: [
{id: "10001", summary: "iPhone5S", price: 1345},
// ...
]
}

整个应用程序的结构如上

  • netstatus : 网络请求状态,isLoading 表示是否正在请求,isSuccess 表示是否请求成功;
  • user : 用户对象;
  • order : 购物车,包含用户id 和 商品列表;
  • goods : 所有商品列表;

step2: 定义 action

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
const netBegin = (url) =>({
type: "NET_BEGIN",
url
});
const netEndSuccess = (url) =>({
type: "NET_OVER_SUCCESS",
url
});
const netEndFail = (url) =>({
type: "NET_OVER_FAIL",
url
});
// ------------- netstatus -------------
export const userLogin = (data) => ({
type: "LOGIN",
data
});
// ------------- user -------------
export const getOrder = (data) => ({
type: "GET_ORDER",
data
});
export const orderAdd = (data) =>({
type: "ORDER_ADD",
data
});
export const orderRM = (data) =>({
type: "ORDER_RM",
data
});
// ------------- order -------------
export const getGoods = (data) => ({
type: "GET_GOODS",
data
});
// ------------- goods -------------
import fetch from 'isomorphic-fetch';
// 使用 fetch 进行网络请求
export const fetchData = function (url, actionCallback, cookie = {}) {
return function (dispatch) {
dispatch(netBegin(url));
return fetch(url, cookie)
.then(response=> response.json())
.then(json => {
dispatch(netEndSuccess(url));
dispatch(actionCallback(json.data));
})
.catch(err=> {
console.log(err);
dispatch(netEndFail(url))
});
};
};

定义了普通的 action,以及用于异步网络请求的 action fetchData。在请求开始的时候,fetchData 会发一个普通 action netBegin(),请求结束之后,请求成功和请求失败的分别发布 netEndSuccess()netEndFail(),如果成功还会额外将请求数据通过第二个参数 actionCallback 发布一个相应的同步 action

step3: 定义 reducer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
import {combineReducers} from "redux";
function netstatus(state = {isLoading: false, isSuccess: true}, action) {
switch (action.type) {
case "NET_BEGIN":
return Object.assign({}, state, {
isLoading: true
});
break;
case "NET_OVER_SUCCESS":
return Object.assign({}, state, {
isLoading: false,
isSuccess: true
});
break;
case "NET_OVER_FAIL":
return Object.assign({}, state, {
isLoading: false,
isSuccess: false
});
break;
}
return state;
}
function user(state = {}, action) {
switch (action.type) {
case "LOGIN":
return Object.assign({}, state, action.data);
break;
}
return state;
}
function order(state = {userId: "", goods: []}, action) {
switch (action.type) {
case "GET_ORDER":
return Object.assign({}, state, action.data);
break;
case "ORDER_ADD":
return Object.assign({}, state, {
goods: [...state.goods, action.data]
});
break;
case "ORDER_RM":
return Object.assign({}, state, {
goods: state.goods.filter(good=> {
return good.id != action.data.id;
})
});
break;
}
return state;
}
function goods(state = [], action) {
switch (action.type) {
case "GET_GOODS":
// return state.concat(action.data);
return [...state, ...action.data]; // 将获取到的结果添加到 goods 数组
break;
}
return state;
}
const reducer = combineReducers({
netstatus,
user,
order,
goods
});
module.exports = reducer;

参见 step1state 的结构,上面的代码定义了四个 reducer,然后使用 combineReducers 将4个子 reducer 合并。

step4: 定义 展示组件 和 容器组件

展示组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import React from "react";
const App = React.createClass({
componentDidMount: function () {
console.log("componentDidMount");
this.props.getUser();
this.props.getGoods();
this.props.getOrder();
},
render: function () {
var items = this.props.goods.map((good, index) => {
return <li onClick={()=> {
this.props.orderAdd(good.id);
}} key={"good_" + good.id + "_" + index}>{good.summary}</li>;
});
var orderItems = this.props.order.goods.map((good, index)=> {
return <li onClick={()=> {
this.props.orderRM(good.id);
}} key={"order_" + good.id + "_" + index}>{good.summary}</li>;
});
return <div>
<h2>{this.props.user.name}</h2>
<h2>Goods</h2>
<ul>{items}</ul>
<h2>Order</h2>
<ul>{orderItems}</ul>
</div>
}
});
module.exports = App;
  • 在组件加载的时候,就去获取用户列表,获取所有的商品列表,以及获取购物车。
  • 获取到商品列表之后,渲染出所有商品,点击某一个商品,则把它添加到购物车。
  • 获取到购物车之后,渲染出购物车中所有商品,点击某一个商品,则把它从购物车中移除。

容器组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import React from "react";
import App from "./App.js";
import {connect} from 'react-redux';
import {userLogin, getOrder, orderAdd, orderRM, getGoods, fetchData} from "./actions.js";
const mapStateToProps = (state, ownProps) => ({
user: state.user,
order: state.order,
netstatus: state.netstatus,
goods: state.goods
});
const mapDispatchToProps = (dispatch, ownProps) => ({
getUser: ()=> {
dispatch(fetchData("http://127.0.0.1:3000/user", userLogin));
},
getOrder: ()=> {
dispatch(fetchData("http://127.0.0.1:3000/order", getOrder));
},
getGoods: ()=> {
dispatch(fetchData("http://127.0.0.1:3000/goods", getGoods));
},
orderAdd: (id)=> {
dispatch(fetchData("http://127.0.0.1:3000/order/add/" + id, orderAdd));
},
orderRM: (id)=> {
dispatch(fetchData("http://127.0.0.1:3000/order/rm/" + id, orderRM));
}
});
const ContainerApp = connect(
mapStateToProps,
mapDispatchToProps
)(App);
export default ContainerApp;

mapDispatchToProps 中所有网络请求都通过 dispatch(fetchData(url, callbackAction)) 来发起,在请求开始,结束(成功、失败)的时候,fetchData 都会自动分发相应的 action

step5: 完成应用程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import React from "react";
import ReactDOM from "react-dom";
import {createStore} from "redux";
import {Provider} from 'react-redux';
import {applyMiddleware} from 'redux';
import thunkMiddleware from "redux-thunk";
import createLogger from 'redux-logger';
import reducers from "./sys/reducer.js";
import ContainerApp from './sys/ContainerApp.js';
const logger = createLogger();
const store = createStore(reducers, applyMiddleware(thunkMiddleware, logger));
ReactDOM.render(
<Provider store={store}>
<ContainerApp />
</Provider>,
document.getElementById("root"));