05-eat-n-split

本文最后更新于 2024年12月20日 下午

eat-n-split(和朋友吃饭a钱的程序)

(一)实现页面及功能

页面:

完整页面

功能:

1.添加新朋友(点击Add或者Close按钮均会关闭添加朋友的组件)。

添加成功界面

2.选择一位朋友与ta分账(只需要输入总花费和我花费的,朋友的花费会自动计算),选择付款人(我和被选择的朋友两种选择),点击Split bill按钮即可把这个结果累加到原本欠的钱,同时分账界面关闭。

点击Split bill按钮前

点击后

【解释】因为Sarah原本欠我30,这次我花费30,她帮我付了,所以我们俩扯平了。

(二)实现代码及分析

用到的组件:

全部组件

App是导出的父组件

FriendList是显示朋友列表的部分(篮框)

Friend是FriendList的子组件(紫框)

FormAddFriend是添加新朋友的组件(绿框)

FormSplitBill是用于分账的组件(红框)

Button组件在多个组件中均有应用

重构后分成多个文件进行分析

(1)App.js

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
82
83
84
85
import { useState } from "react";
import { FriendsList } from "./FriendsList";
import { FormAddFriend } from "./FormAddFriend";
import { FormSplitBill } from "./FormSplitBill";
import { Button } from "./Button";

const initialFriends = [
{
id: 118836,
name: "Clark",
image: "https://i.pravatar.cc/48?u=118836",
balance: -7,
},
{
id: 933372,
name: "Sarah",
image: "https://i.pravatar.cc/48?u=933372",
balance: 20,
},
{
id: 499476,
name: "Anthony",
image: "https://i.pravatar.cc/48?u=499476",
balance: 0,
},
];

export default function App() {
const [showAddFriend, setShowAddFriend] = useState(false);
const [friends, setFriends] = useState(initialFriends);
const [selectedFriend, setSelectedFriend] = useState(null);

function handleShowAddFriend() {
setShowAddFriend((show) => !show);
}

function handleAddFriend(friend) {
setFriends((friends) => [...friends, friend]);
setShowAddFriend(false);
}

function handleSelection(friend) {
// setSelectedFriend(friend);
setSelectedFriend((cur) => (cur?.id === friend.id ? null : friend));
setShowAddFriend(false);
}

function handleSplitBill(value) {
setFriends((friends) =>
friends.map((friend) =>
friend.id === selectedFriend.id
? { ...friend, balance: friend.balance + value }
: friend
)
);

setSelectedFriend(null);
}

return (
<div className="app">
<div className="sidebar">
<FriendsList
friends={friends}
onSelection={handleSelection}
selectedFriend={selectedFriend}
/>

{showAddFriend && <FormAddFriend onAddFriend={handleAddFriend} />}

<Button onClick={handleShowAddFriend}>
{showAddFriend ? "Close" : "Add friend"}
</Button>
</div>

{selectedFriend && (
<FormSplitBill
selectedFriend={selectedFriend}
onSplitBill={handleSplitBill}
/>
)}
</div>
);
}

解释

(2)Button.js

1
2
3
4
5
6
7
8
export function Button({ children, onClick }) {
return (
<button className="button" onClick={onClick}>
{children}
</button>
);
}

解释

(3)FriendList.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { Friend } from "./Friend";

export function FriendsList({ friends, onSelection, selectedFriend }) {
return (
<ul>
{friends.map((friend) => (
<Friend
friend={friend}
key={friend.id}
onSelection={onSelection}
selectedFriend={selectedFriend}
/>
))}
</ul>
);
}

解释

(4)Friend.js

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
import { Button } from "./Button";

export function Friend({ friend, onSelection, selectedFriend }) {
const isSelected = selectedFriend?.id === friend.id;

return (
<li className={isSelected ? "selected" : ""}>
<img src={friend.image} alt={friend.name} />
<h3>{friend.name}</h3>

{friend.balance < 0 && (
<p className="red">
You owe {friend.name} ${Math.abs(friend.balance)}
</p>
)}
{friend.balance > 0 && (
<p className="green">
{friend.name} owes you ${Math.abs(friend.balance)}
</p>
)}
{friend.balance === 0 && <p>You and {friend.name} are even</p>}

<Button onClick={() => onSelection(friend)}>
{isSelected ? "Close" : "Select"}
</Button>
</li>
);
}

解释

(5)FormAddFriend.js

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
import { useState } from "react";
import { Button } from "./Button";

export function FormAddFriend({ onAddFriend }) {
const [name, setName] = useState("");
const [image, setImage] = useState("https://i.pravatar.cc/48");

function handleSubmit(e) {
e.preventDefault();

if (!name || !image) return;

const id = crypto.randomUUID();
const newFriend = {
id,
name,
image: `${image}?u=${id}`,
balance: 0,
};
onAddFriend(newFriend);

setName("");
setImage("https://i.pravatar.cc/48");
}

return (
<form className="form-add-friend" onSubmit={handleSubmit}>
<lable>🧑‍🤝‍🧑 Friend name</lable>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>

<lable>🐻 Image URL</lable>
<input
type="text"
value={image}
onChange={(e) => setImage(e.target.value)}
/>
<Button>Add</Button>
</form>
);
}

解释

(6)FormSplitBill.js

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
import { useState } from "react";
import { Button } from "./Button";

export function FormSplitBill({ selectedFriend, onSplitBill }) {
const [bill, setBill] = useState("");
const [paidByUser, setPaidByUser] = useState("");
const paidByFriend = bill ? bill - paidByUser : "";
const [whoispaying, setWhoIsPaying] = useState("user");

function handleSubmit(e) {
e.preventDefault();
onSplitBill(whoispaying === "user" ? paidByFriend : -paidByUser);
}

return (
<form className="form-split-bill" onSubmit={handleSubmit}>
<h2>Split a bill with {selectedFriend.name}</h2>

<lable>💵Bill value</lable>
<input
type="text"
value={bill}
onChange={(e) => setBill(Number(e.target.value))}
/>

<lable>🐶Your expense</lable>
<input
type="text"
value={paidByUser}
onChange={(e) =>
setPaidByUser(
Number(e.target.value) > bill ? paidByUser : Number(e.target.value)
)
}
/>

<lable>🐱{selectedFriend.name}'s expense</lable>
<input type="text" disabled value={paidByFriend} />

<lable>🤑Who is paying the bill</lable>
<select
value={whoispaying}
onChange={(e) => setWhoIsPaying(e.target.value)}
>
<option value="user">You</option>
<option value="friend">{selectedFriend.name}</option>
</select>
<Button>Split bill</Button>
</form>
);
}

解释


05-eat-n-split
http://sue-channing.github.io/2024/12/04/05-eat-n-split/
作者
Sue-Channing
发布于
2024年12月4日
许可协议