1 效果说明
假设我在网购,有两个商品A、B,它们是独立的组件,当我想购买A商品时,我就进入A商品详情页将其添加到购物车,想购买B商品亦然。
初始页面:localhost:3000
点击A商品,进入A商品详情页面,localhost:3000/a
点击添加购物车,有相应的效果:
点击B商品,进入B商品详情页面,localhost:3000/b:
点击添加购物车,有如下效果:
点击删除可以删除任意商品:
在本案例中,购物车是一个共享组件,此时可以使用Redux对购物车实现统一管理。
2 实现代码
2.1 创建项目
使用WebStorm创建一个React项目,安装redux模块
npm install redux
项目的目录结构如下所示:
2.2 store.js
在该文件中主要通过createStore生成store,创建reducer函数,具体代码如下:
import {createStore} from "redux";
// 定义reducer函数,它有两个参数:state和action
// store是初始的状态,action是派发的动作,goods表示商品
const cartReducer = (state = {goods: []}, action) => {
switch (action.type) {
case "add": // 添加购物车
// 将state中的所有属性遍历出来(只有goods)
// goods数组中遍历出来之前的商品,再把新的商品加进去
return {...state, goods: [...state.goods, action.good]};
case "delete": // 从购物车中删除
// 获取要删除的商品的索引
let deleteGoodIndex = action.good.index;
// 遍历所有的商品信息,并且放在新数组中
let newGoods = [...state.goods];
// 将要删除的商品从该数组中删除
newGoods.splice(deleteGoodIndex, 1);
return {...state, goods: newGoods}; // 删除商品之后的数组覆盖原数组
default:
return state;
}
}
// 创建store对象
let store = createStore(cartReducer);
export default store; // 导出
2.3 A.js
在该文件中主要编写商品A的购物页面,具体代码如下:
import store from "./store";
import React from "react";
// 创建类组件
class A extends React.Component {
constructor(props) {
super(props);
this.state = {
// 设置当前的初始状态length为store的商品的数量
length: store.getState().goods.length
}
}
componentDidMount() { // 页面加载完成后执行该函数
// 订阅store中state的变化,并返回一个可以取消订阅的对象
this.unSubscribe = store.subscribe(() => { // state发生变化后,执行回调函数
this.setState({
length: store.getState().goods.length
})
})
}
componentWillUnmount() {
this.unSubscribe(); // 组件卸载时取消订阅
}
render() {
const {length} = this.state; // 将购买的商品的数量解析出来
return (
<div>
<h2>A商品详情页面</h2>
<p>当前购物车内商品数量:{length}</p>
{/*点击按钮后派发add动作,将商品添加到购物车*/}
<button onClick={() => store.dispatch({
type: "add",
good: {
title: `商品${Date.now()}`, // 将日期作为商品的编号
price: 100
}
})}>添加购物车
</button>
</div>
)
}
}
export default A;
2.4 B.js
编写商品B的商品详情页面,与A.js相同,具体代码如下:
import store from "./store";
import React from "react";
class B extends React.Component {
constructor(props) {
super(props);
this.state = {
length: store.getState().goods.length
}
}
componentDidMount() {
this.unSubscribe = store.subscribe(() => {
this.setState({
length: store.getState().goods.length
})
})
}
componentWillUnmount() {
this.unSubscribe();
}
render() {
const {length} = this.state;
return (
<div>
<h2>B商品详情页面</h2>
<p>当前购物车内商品数量:{length}</p>
<button onClick={() => store.dispatch({
type: "add",
good: {
title: `B商品${Date.now()}`,
price: 150
}
})
}>添加购物车
</button>
</div>
)
}
}
export default B;
2.5 Cart.js
购买商品时,我们不仅能看到商品数量在增加,同时我们也能看到自己买了哪个商品,以及将购买的商品删除,该文件主要实现这个功能,具体代码如下:
import store from "./store";
import React from "react";
class Cart extends React.Component {
constructor(props) {
super(props);
this.state = {
goods: store.getState().goods // 获取当前购买的商品列表
}
}
componentDidMount() { // 页面加载完成后执行该函数
this.unSubscribe = store.subscribe(() => {
// 当state发生变化时执行回调函数
this.setState({
goods: store.getState().goods
})
})
}
componentWillUnmount() {
this.unSubscribe(); // 取消订阅
}
render() {
const {goods} = this.state; // 将商品列表获取出来
return (
<ul>
{/*将商品遍历出来并显示在页面上*/}
{
goods.map((good, index) => {
return (
<li key={index}>
{/*good.title是在A.js和B.js中定义的商品名称*/}
<span>{good.title}</span>
<button onClick={() => store.dispatch({
type: "delete",
good: {index} // 将当前要删除的商品的索引派给good
})}>删除
</button>
</li>
)
})
}
</ul>
)
}
}
export default Cart;
2.6 App.js
在App.js中主要进行路由配置,代码如下:
import logo from './logo.svg';
import './App.css';
import {NavLink, Route} from "react-router-dom";
import B from "./components/B";
import A from "./components/A";
import Cart from "./components/Cart";
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo"/>
<div>
<NavLink to={"/a"}>A商品</NavLink>
<NavLink to={"/b"}>B商品</NavLink>
</div>
<Route path={"/a"} component={A}/>
<Route path={"/b"} component={B}/>
<Cart/>
</header>
</div>
);
}
export default App;
2.7 index.js
由于使用了Navlink,因此需要用BrowserRouter组件将App包起来。
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import {BrowserRouter} from "react-router-dom";
ReactDOM.render(
<BrowserRouter>
<App/>
</BrowserRouter>,
document.getElementById('root')
);
reportWebVitals();