一. 书籍购物车说明
1.1. 案例介绍
现在我们来做一个相对综合一点的练习:书籍购物车;
案例效果如下:
~
案例说明:
- 1.在界面上以表格的形式,显示一些书籍的数据;
- 2.在底部显示书籍的总价格;
- 3.点击+或者-可以增加或减少书籍数量(如果为1,那么不能继续-);
- 4.点击移除按钮,可以将书籍移除(当所有的书籍移除完毕时,显示:购物车为空~);
1.2. 项目的搭建
这里,我们使用React将默认的数据先展示出来:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
table {
border: 1px solid #e9e9e9;
border-collapse: collapse;
}
td, th {
border: 1px solid #e9e9e9;
padding: 8px 16px;
}
th {
background-color: #f7f7f7;
color: #5c6b77;
}
.counter {
margin: 0 5px;
}
</style>
</head>
<body>
<div id="app"></div>
<script src="../react/react.development.js"></script>
<script src="../react/react-dom.development.js"></script>
<script src="../react/babel.min.js"></script>
<script type="text/babel">
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
books: [
{
id: 1,
name: '《算法导论》',
date: '2006-9',
price: 85.00,
count: 1
},
{
id: 2,
name: '《UNIX编程艺术》',
date: '2006-2',
price: 59.00,
count: 1
},
{
id: 3,
name: '《编程珠玑》',
date: '2008-10',
price: 39.00,
count: 1
},
{
id: 4,
name: '《代码大全》',
date: '2006-3',
price: 128.00,
count: 1
},
]
}
}
render() {
const { books } = this.state;
return (
<div>
<table>
<thead>
<tr>
<th></th>
<th>书籍名称</th>
<th>出版日期</th>
<th>价格</th>
<th>购买数量</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{
books.map((item, index) => {
return (
<tr>
<td>{index + 1}</td>
<td>{item.name}</td>
<td>{item.date}</td>
<td>{"¥" + item.price}</td>
<td>
<button>-</button>
<span className="counter">{item.count}</span>
<button>+</button>
</td>
<td><button>移除</button></td>
</tr>
)
})
}
</tbody>
</table>
</div>
)
}
}
ReactDOM.render(<App/>, document.getElementById("app"));
</script>
</body>
</html>
二. 书籍购物车功能
1.1. 价格的显示
我们可以封装一个工具函数,用于格式化价格:
function formatPrice(price) {
if (typeof price !== "number") {
price = Number(price) || 0;
}
return "¥" + price.toFixed(2);
}
对之前显示的价格进行格式化:
<td>{formatPrice(item.price)}</td>
封装一个App中的方法,用于获取商品总价格显示的内容:
getTotalPrice() {
let totalPrice = 0;
for (let book of this.state.books) {
totalPrice += book.count * book.price
}
return "总价格:" + formatPrice(totalPrice);
}
使用一个h2元素显示总价格:
<h2> {this.getTotalPrice()}</h2>
1.2. 数量的变化
封装一个方法,用于改变书籍的数量:
- 注意:在React中,要保证数据的不可变性;
- 所以,我们是先复制一份books,对其进行修改,再通过setState更新到最新的状态;
changeItem(index, counter) {
const books = [...this.state.books];
this.setState({
books: books.map((item, indey) => {
if (indey == index) {
item.count += counter;
}
return item;
})
})
}
修改jsx对应位置的代码:
<td>
<button disabled={item.count <= 1} onClick={e => this.changeItem(index, -1)}>-</button>
<span className="counter">{item.count}</span>
<button onClick={e => this.changeItem(index, 1)}>+</button>
</td>
1.3. 移除的操作
封装一个方法,用于移除对应的书籍:
removeItem(index) {
const books = [...this.state.books];
this.setState({
books: books.filter((item, indey) => index !== indey)
})
}
修改对应的移除jsx代码:
<td><button onClick={e => this.removeItem(index)}>移除</button></td>
如果所有的书籍移除完毕,那么要显示购物车为空:
- 封装两个方法,一个用于获取显示购物车的jsx代码(后期会封装成一个组件),一个用于获取显示购物车为空的jsx代码(后期也可以封装为一个组件)
renderBooks() {
const { books } = this.state;
return (
<div>
<table>
<thead>
<tr>
<th></th>
<th>书籍名称</th>
<th>出版日期</th>
<th>价格</th>
<th>购买数量</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{
books.map((item, index) => {
return (
<tr>
<td>{index + 1}</td>
<td>{item.name}</td>
<td>{item.date}</td>
<td>{formatPrice(item.price)}</td>
<td>
<button>-</button>
<span className="counter">{item.count}</span>
<button>+</button>
</td>
<td><button onClick={e => this.removeItem(index)}>移除</button></td>
</tr>
)
})
}
</tbody>
</table>
<h2>
{this.getTotalPrice()}
</h2>
</div>
)
}
renderEmpty() {
return <h2>购物车为空~</h2>
}
重新编写render方法代码:
render() {
const { books } = this.state;
return books.length ? this.renderBooks() : this.renderEmpty();
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
table {
border: 1px solid #eee;
border-collapse: collapse;
}
th,
td {
border: 1px solid #eee;
padding: 10px 16px;
text-align: center;
}
th {
background-color: #ccc;
}
.count {
margin: 0 5px;
}
</style>
</head>
<body>
<div id="app"></div>
<script src="../react/react.development.js"></script>
<script src="../react/react-dom.development.js"></script>
<script src="../react/babel.min.js"></script>
<script src="./format-utils.js"></script>
<script type="text/babel">
// function formatPrice(price) {
// if (typeof price !== "number") {
// price = Number("aaa") || 0;
// }
// return "¥" + price.toFixed(2);
// }
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
books: [
{
id: 1,
name: '《算法导论》',
date: '2006-9',
price: 85.00,
count: 2
},
{
id: 2,
name: '《UNIX编程艺术》',
date: '2006-2',
price: 59.00,
count: 1
},
{
id: 3,
name: '《编程珠玑》',
date: '2008-10',
price: 39.00,
count: 1
},
{
id: 4,
name: '《代码大全》',
date: '2006-3',
price: 128.00,
count: 1
},
]
}
}
// jsx相关的代码写在render函数的上面
renderBooks() {
return (
<div>
<table>
<thead>
<tr>
<th></th>
<th>书籍名称</th>
<th>出版日期</th>
<th>价格</th>
<th>购买数量</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{
this.state.books.map((item, index) => {
return (
<tr>
<td>{index + 1}</td>
<td>{item.name}</td>
<td>{item.date}</td>
<td>{formatPrice(item.price)}</td>
<td>
<button disabled={item.count <= 1} onClick={e => this.changeBookCount(index, -1)}>-</button>
<span className="count">{item.count}</span>
<button onClick={e => this.changeBookCount(index, 1)}>+</button>
</td>
<td><button onClick={e => this.removeBook(index)}>移除</button></td>
</tr>
)
})
}
</tbody>
</table>
<h2>总价格: {this.getTotalPrice()}</h2>
</div>
)
}
renderEmptyTip() {
return <h2>购物车为空~</h2>
}
render() {
return this.state.books.length ? this.renderBooks() : this.renderEmptyTip();
}
changeBookCount(index, count) {
// 扩展运算符,相当于浅拷贝
const newBooks = [...this.state.books];
newBooks[index].count += count;
this.setState({
// newBooks不能写成newBooks[index].count += count
books: newBooks
})
}
removeBook(index) {
// React中设计原则: state中的数据的不可变性;
this.setState({
books: this.state.books.filter((item, indey) => index != indey)
})
}
getTotalPrice() {
// 1.for循环的方式
// let totalPrice = 0;
// for (let item of this.state.books) {
// totalPrice += item.price * item.count;
// }
// return formatPrice(totalPrice);
// 2.filter/map/reduce(归纳为)
// 回调函数的参数:
// 参数一: 上一次回调函数的结果(第一次没有上一次函数的回调函数的结果, 使用初始化值)
const totalPrice = this.state.books.reduce((preValue, item) => {
return preValue + item.count * item.price;
}, 0);
return formatPrice(totalPrice);
}
}
ReactDOM.render(<App />, document.getElementById("app"));
</script>
</body>
</html>