Connecting to the Backend
useEffect( () => {} )
To execute a piece of code after a component is rendered.
Sending Http Requests
npm i axios@1.3.4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | import axios from 'axios'
interface User {
id: number;
name: string;
}
function App() {
const [users, setUsers] = useState<User[]>([])
useEffect(()=>{
axios.get<User[]>('http://jsonplaceholder.typicode.com/users')
.then(res => setUsers(res.data));
}, [])
return <ul>
{users.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
|
这里 UseState
需要类型标注,否则 users
列表的类型无法得知,其次 axios
返回的数据也需要类型标注,即 axios.get<User[]>
Handling Errors
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 | interface User {
id: number;
name: string;
}
function App() {
const [users, setUsers] = useState<User[]>([]);
const [error, setError] = useState("");
useEffect(() => {
axios
.get<User[]>("http://jsonplaceholder.typicode.com/xusers")
.then((res) => setUsers(res.data))
.catch((err) => setError(err.message));
}, []);
return (
<>
{error && <p className="text-danger">{error}</p>}
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</>
);
|
解决获取数据时的异常报错需要采用 .catch
方法,通过 useState
使其回显到页面上。
Async & Await (Optional)
get
会返回一个 promise
,有两个状态,resolved
和 rejected
。
在 JavaScript 中,如果我们有一个 promise
,我们就可以在前面放 await
来获得结果。
由于 React 不允许我们传递 async
函数给 useEffect
,我们需要在 useEffect
内部再定义一个 async
函数。
万一获取数据抛出异常报错了呢?这个时候需要在 async
函数内部使用 try...catch
来解决。const fetchUsers = async () => { try { const res = await ... } catch {err} }
其中我们无法在 catch
的形参做类型标注,只能在实参中使用 as
关键字(类型断言)setError((err as AxiosError).message)
。React 的这种方法并不优雅,Mosh 更偏向于上一个方法,因为代码更为美观和简洁。
aysnc&await |
---|
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 { useEffect, useState } from "react";
import axios, { AxiosError } from "axios";
interface User {
id: number;
name: string;
}
function App() {
const [users, setUsers] = useState<User[]>([]);
const [error, setError] = useState("");
useEffect(() => {
const fetchUsers = async () => {
try {
const res = await axios.get<User[]>(
"http://jsonplaceholder.typicode.com/xusers"
);
setUsers(res.data);
} catch (err) {
setError((err as AxiosError).message);
}
};
fetchUsers();
}, []);
return (
<>
{error && <p className="text-danger">{error}</p>}
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</>
);
|
Cancelling the Fetch Requests
AbortController
是 JavaScript 中一个用于控制和管理异步操作(如网络请求)的接口。它可以帮助我们在不再需要时安全地取消这些操作,以节省资源和避免不必要的处理。
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 { useEffect, useState } from "react";
import axios, { CanceledError } from "axios";
interface User {
id: number;
name: string;
}
function App() {
const [users, setUsers] = useState<User[]>([]);
const [error, setError] = useState("");
useEffect(() => {
const controller = new AbortController();
axios
.get<User[]>("http://jsonplaceholder.typicode.com/users", {
signal: controller.signal,
})
.then((res) => setUsers(res.data))
.catch((err) => {
if (err instanceof CanceledError) return;
setError(err.message);
});
return () => controller.abort();
}, []);
return (
<>
{error && <p className="text-danger">{error}</p>}
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</>
);
|
- 创建 AbortController 实例
| const controller = new AbortController();
|
AbortController
是一个内置的浏览器 API。通过调用它的构造函数,我们创建了一个新的 AbortController
实例。这个实例包含一个 signal
对象,我们可以用它来与异步操作进行交互。
- 传递信号给请求
| axios.get<User[]>("http://jsonplaceholder.typicode.com/users", {
signal: controller.signal,
})
|
在发起 axios
请求时,我们将 controller.signal
作为配置项的一部分传递给请求。这使得该请求与 AbortController
关联起来。
- 取消请求
| return () => controller.abort();
|
useEffect
的返回值是一个清理函数,当组件卸载或 useEffect
再次运行时,该函数将被调用。在这里,我们调用 controller.abort()
,这会触发与之关联的请求取消。
- 处理错误
| .catch((err) => {
if (err instanceof CanceledError) return;
setError(err.message);
})
|
如果请求被取消,axios
会抛出一个 CanceledError
。我们通过检查错误实例是否为 CanceledError
来决定是否处理该错误。如果是请求取消引起的错误,我们直接返回,否则设置错误信息。
结合组件的生命周期:
-
挂载时:
如果不添加 if (err instanceof CanceledError) return;
这行代码,当请求被取消时,会捕获到 CanceledError
,并设置错误消息。这会导致即使请求数据成功,用户列表也不会显示,因为之前的错误消息仍然存在。
Showing a Loading Indicator
在 React 中,Promise
实例的 finally()
方法不起作用,因此只能在 then()
和 catch()
方法内编写两次重复的代码。
那么如何显示加载动画呢?这里使用了 Bootstrap 中类为 spinner-border 的 div,通过 isLoading
状态控制。
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 | import { useEffect, useState } from "react";
import axios, { CanceledError } from "axios";
interface User {
id: number;
name: string;
}
function App() {
const [users, setUsers] = useState<User[]>([]);
const [error, setError] = useState("");
const [isLoading, setLoading] = useState(false);
useEffect(() => {
const controller = new AbortController();
setLoading(true);
axios
.get<User[]>("http://jsonplaceholder.typicode.com/users", {
signal: controller.signal,
})
.then((res) => {
setUsers(res.data);
setLoading(false);
})
.catch((err) => {
if (err instanceof CanceledError) return;
setError(err.message);
setLoading(false);
});
return () => controller.abort();
}, []);
return (
<>
{error && <p className="text-danger">{error}</p>}
{isLoading && <div className="spinner-border"></div>}
<ul>
{users.map ((user) => (
<li key={user.id}>{user. name}</li>
))}
</ul>
</>
);
}
|
Deleting Data
[[3-Managing Component State#Summary of Updating Objects and Array]]
两种更新数据的方式:
- Optimistic update
- Update the UI
- Call the server
- Pessimistic update
- Call the server
- Update the UI
Mosh 偏向第一种,因此呢,操作失败的情况下需要还原列表。
-
添加列表和列表项的类名
- 为列表添加
list-group
类名。
- 为列表项添加
list-group-item
类名。
-
添加按钮
- 为按钮添加
btn btn-outline-danger
类名。
-
使用 Flex 布局
- 为列表项添加
d-flex
类名,以启用 Flex 布局。
- 添加
justify-content-between
类名,使列表项内容左右分布。
-
添加按钮的点击事件
- 给按钮添加
onClick
事件处理程序,触发 deleteUser
函数。
deleteUser
函数首先使用 filter
过滤不与当前用户 ID 一致的用户(等于从列表中删除了该用户),然后发送请求到服务器删除用户。
- 如果请求出错,需要还原列表并显示错误信息。
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 | import { useEffect, useState } from "react";
import axios, { CanceledError } from "axios";
interface User {
id: number;
name: string;
}
function App () {
const [users, setUsers] = useState<User[]>([]);
const [error, setError] = useState ("");
const [isLoading, setLoading] = useState (false);
useEffect (() => {
const controller = new AbortController ();
setLoading (true);
axios
.get<User[]>(" http://jsonplaceholder.typicode.com/users" , {
signal: controller. signal,
})
.then ((res) => {
setUsers (res. data);
setLoading (false);
})
.catch ((err) => {
if (err instanceof CanceledError) return;
setError (err. message);
setLoading (false);
});
return () => controller.abort ();
}, []);
const deleteUser = (user: User) => {
const originalUsers = [... users];
setUsers (users.filter ((u) => u.id !== user. id));
axios
.delete (" http://jsonplaceholder.typicode.com/users/" + user. id)
.catch ((err) => {
setError (err. message);
// 由于我们先操作 UI,如果没有删除服务器上的数据,出现了异常,应该还原列表。
setUsers (originalUsers);
});
};
return (
<>
{error && <p className="text-danger">{error}</p>}
{isLoading && <div className="spinner-border"></div>}
<ul className="list-group">
{users.map ((user) => (
<li
key={user. id}
className="list-group-item d-flex justify-content-between"
>
{user. name}
<button
className="btn btn-outline-danger"
onClick={() => deleteUser (user)}
>
Delete
</button>
</li>
))}
</ul>
</>
);
|
Creating Data
第 55 行 { data: savedUser }
中 {data}
是解构的属性,而 savedUser
不是类型标注,而是别名。res
代表整个响应对象,不能直接使用。
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
86
87
88 | import { useEffect, useState } from "react";
import axios, { CanceledError } from "axios";
interface User {
id: number;
name: string;
}
function App () {
const [users, setUsers] = useState<User[]>([]);
const [error, setError] = useState ("");
const [isLoading, setLoading] = useState (false);
useEffect (() => {
const controller = new AbortController ();
setLoading (true);
axios
.get<User[]>(" http://jsonplaceholder.typicode.com/users" , {
signal: controller. signal,
})
.then ((res) => {
setUsers (res. data);
setLoading (false);
})
.catch ((err) => {
if (err instanceof CanceledError) return;
setError (err. message);
setLoading (false);
});
return () => controller.abort ();
}, []);
const deleteUser = (user: User) => {
const originalUsers = [... users];
setUsers (users.filter ((u) => u.id !== user. id));
axios
.delete (" http://jsonplaceholder.typicode.com/users/" + user. id)
.catch ((err) => {
setError (err. message);
// 由于我们先操作 UI,如果没有删除服务器上的数据,出现了异常,应该还原列表。
setUsers (originalUsers);
});
};
const addUser = () => {
const newUser = { id: 0, name: "Mosh" };
const originalUsers = [... users];
setUsers ([newUser, ... users]);
axios
.post (" http://jsonplaceholder.typicode.com/users/" , newUser)
// .then ((res) => setUsers ([res. data, ... users]));
.then (({ data: savedUser }) => setUsers ([savedUser, ... users]))
.catch ((err) => {
setUsers (originalUsers);
setError (err. message);
});
};
return (
<>
{error && <p className="text-danger">{error}</p>}
{isLoading && <div className="spinner-border"></div>}
{! isLoading && (
<button className="btn btn-primary mb-3" onClick={() => addUser ()}>
Add
</button>
)}
<ul className="list-group">
{users.map ((user) => (
<li
key={user. id}
className="list-group-item d-flex justify-content-between"
>
{user. name}
<button
className="btn btn-outline-danger"
onClick={() => deleteUser (user)}
>
Delete
</button>
</li>
))}
</ul>
</>
);
|
Updating data
增加了一个更新按钮,其中类名 mx-1
增加左右边距。由于使用了 flex 布局,而且是 space-between
,所以需要把两个按钮添加到一个 div
中,。
对于更新数据,可以使用 axios
的 put()
或者 patch()
方法,这取决于后端的选型。由于这里只更新了对象的一个属性,所以使用了 patch()
方法。
回顾:
这里对 React
如何更新对象不太熟悉,应该是先定义一个常量用来保存手动更改的对象,再使用 setUsers
方法通过 map()
和三元运算符去更新对象。
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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112 | import { useEffect, useState } from "react";
import axios, { CanceledError } from "axios";
interface User {
id: number;
name: string;
}
function App() {
const [users, setUsers] = useState<User[]>([]);
const [error, setError] = useState("");
const [isLoading, setLoading] = useState(false);
useEffect(() => {
const controller = new AbortController();
setLoading(true);
axios
.get<User[]>("http://jsonplaceholder.typicode.com/users", {
signal: controller.signal,
})
.then((res) => {
setUsers(res.data);
setLoading(false);
})
.catch((err) => {
if (err instanceof CanceledError) return;
setError(err.message);
setLoading(false);
});
return () => controller.abort();
}, []);
const deleteUser = (user: User) => {
const originalUsers = [...users];
setUsers(users.filter((u) => u.id !== user.id));
axios
.delete("http://jsonplaceholder.typicode.com/users/" + user.id)
.catch((err) => {
setError(err.message);
// 由于我们先操作 UI,如果没有删除服务器上的数据,出现了异常,应该还原列表。
setUsers(originalUsers);
});
};
const addUser = () => {
const newUser = { id: 0, name: "Mosh" };
const originalUsers = [...users];
setUsers([newUser, ...users]);
axios
.post("http://jsonplaceholder.typicode.com/users/", newUser)
// .then((res) => setUsers([res.data, ...users]));
.then(({ data: savedUser }) => setUsers([savedUser, ...users]))
.catch((err) => {
setUsers(originalUsers);
setError(err.message);
});
};
const updateUser = (user: User) => {
const updatedUser = { ...user, name: user.name + "!" };
const originalUsers = [...users];
setUsers(users.map((u) => (u.id === user.id ? updatedUser : u)));
axios
.patch("http://jsonplaceholder.typicode.com/users/" + user.id, {
updatedUser,
})
.catch((err) => {
setError(err.message);
setUsers(originalUsers);
});
};
return (
<>
{error && <p className="text-danger">{error}</p>}
{isLoading && <div className="spinner-border"></div>}
{!isLoading && (
<button className="btn btn-primary mb-3" onClick={() => addUser()}>
Add
</button>
)}
<ul className="list-group">
{users.map((user) => (
<li
key={user.id}
className="list-group-item d-flex justify-content-between"
>
{user.name}
<div>
<button
className="btn btn-outline-secondary mx-1"
onClick={() => updateUser(user)}
>
Update
</button>
<button
className="btn btn-outline-danger"
onClick={() => deleteUser(user)}
>
Delete
</button>
</div>
</li>
))}
</ul>
</>
);
|
| └── 📁src
└── App.tsx
└── 📁assets
└── 📁components
└── index.css
└── main.tsx
└── 📁services
└── api-client.ts
└── vite-env.d.ts
|
新建 api-client.ts
在 src
文件夹下创建一个 services
文件夹,用于配置 HTTP 请求,并在文件夹内新建一个 api-client.ts
用于定制 axios
和 user-service.ts
用于处理用户相关逻辑。
api-client.ts |
---|
| import axios, { CanceledError } from "axios";
export default axios.create({
baseURL: "http://jsonplaceholder.typicode.com",
headers: {
// 'api-key': 'xxx'
},
});
export { CanceledError };
|
从 axios
模块导入 axios
和 CanceledError
,这样 main.tsx
不需要再次导入 axios
。但是需要把 axios
替换为 apiClient
。
修改 App.tsx
App.tsx |
---|
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 | import { useEffect, useState } from "react";
import apiClient, { CanceledError } from "./services/api-client";
interface User {
id: number;
name: string;
}
function App() {
const [users, setUsers] = useState<User[]>([]);
const [error, setError] = useState("");
const [isLoading, setLoading] = useState(false);
useEffect(() => {
const controller = new AbortController();
setLoading(true);
apiClient
.get<User[]>("/users", {
signal: controller.signal,
})
.then((res) => {
setUsers(res.data);
setLoading(false);
})
.catch((err) => {
if (err instanceof CanceledError) return;
setError(err.message);
setLoading(false);
});
return () => controller.abort();
}, []);
const deleteUser = (user: User) => {
const originalUsers = [...users];
setUsers(users.filter((u) => u.id !== user.id));
apiClient.delete("/users/" + user.id).catch((err) => {
setError(err.message);
// 由于我们先操作 UI,如果没有删除服务器上的数据,出现了异常,应该还原列表。
setUsers(originalUsers);
});
};
const addUser = () => {
const newUser = { id: 0, name: "Mosh" };
const originalUsers = [...users];
setUsers([newUser, ...users]);
apiClient
.post("/users", newUser)
// .then((res) => setUsers([res.data, ...users]));
.then(({ data: savedUser }) => setUsers([savedUser, ...users]))
.catch((err) => {
setUsers(originalUsers);
setError(err.message);
});
};
const updateUser = (user: User) => {
const updatedUser = { ...user, name: user.name + "!" };
const originalUsers = [...users];
setUsers(users.map((u) => (u.id === user.id ? updatedUser : u)));
apiClient
.patch("http://jsonplaceholder.typicode.com/users/" + user.id, {
updatedUser,
})
.catch((err) => {
setError(err.message);
setUsers(originalUsers);
});
};
return (
<>
......
</>
);
|
新建 user-service.ts
在 service
文件夹内新建一个 user-service.ts
用于处理用户相关服务,并导入 user-service.ts
,把 App.tsx
内与用户相关的代码整合到当中,主要包括一个用户服务类,包含了获取所有用户、删除、创建和更新用户的方法,最后导出一个类的实例。
方法是一个 Promise,需要返回状态,因此一定要 return
。
user-service.ts |
---|
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 apiClient from "./api-client";
export interface User {
id: number;
name: string;
}
class UserService {
getAllUser() {
const controller = new AbortController();
const request = apiClient.get<User[]>("/users", {
signal: controller.signal,
});
return { request, cancel: () => controller.abort() };
}
deleteUser(id: number) {
return apiClient.delete("/users/" + id);
}
createUser(newUser: User) {
return apiClient.post("/users", newUser);
}
updateUser(user: User) {
return apiClient.patch(
"http://jsonplaceholder.typicode.com/users/" + user.id,
user
);
}
}
export default new UserService();
|
修改 App.tsx
把 user-service.ts
的类实例和类型接口导入到 App.tsx
中,把 apiClient
替换为 userService
的各个方法。
默认导出(随意修改命名)和命名导出(不能修改命名):import userService, { User } from "./services/user-service";
由于 user-service.ts
导入了 apiClient
,所以 App.jsx
不需要再重复导入。
App.tsx |
---|
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 | import { useEffect, useState } from "react";
import { CanceledError } from "./services/api-client";
import userService, { User } from "./services/user-service";
function App() {
const [users, setUsers] = useState<User[]>([]);
const [error, setError] = useState("");
const [isLoading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
const { request, cancel } = userService.getAllUser();
request
.then((res) => {
setUsers(res.data);
setLoading(false);
})
.catch((err) => {
if (err instanceof CanceledError) return;
setError(err.message);
setLoading(false);
});
return () => cancel();
}, []);
const deleteUser = (user: User) => {
const originalUsers = [...users];
setUsers(users.filter((u) => u.id !== user.id));
userService.deleteUser(user.id).catch((err) => {
setError(err.message);
// 由于我们先操作 UI,如果没有删除服务器上的数据,出现了异常,应该还原列表。
setUsers(originalUsers);
});
};
const addUser = () => {
const newUser = { id: 0, name: "Mosh" };
const originalUsers = [...users];
setUsers([newUser, ...users]);
userService
.creatUser(newUser)
// .then((res) => setUsers([res.data, ...users]));
.then(({ data: savedUser }) => setUsers([savedUser, ...users]))
.catch((err) => {
setUsers(originalUsers);
setError(err.message);
});
};
const updateUser = (user: User) => {
const updatedUser = { ...user, name: user.name + "!" };
const originalUsers = [...users];
setUsers(users.map((u) => (u.id === user.id ? updatedUser : u)));
userService.updateUser(updatedUser).catch((err) => {
setError(err.message);
setUsers(originalUsers);
});
};
return (
<>
.....
</>
);
}
|
新建 http-service.ts
| └── 📁src
└── App.tsx
└── 📁assets
└── 📁components
└── index.css
└── main.tsx
└── 📁services
└── api-client.ts
└── http-service.ts
└── user-service.ts
└── vite-env.d.ts
|
http-service.ts |
---|
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 | import apiClient from "./api-client";
interface Entity {
id: number;
}
class HttpService {
endpoint: string;
constructor(endpoint: string) {
this.endpoint = endpoint;
}
getAll<T>() {
const controller = new AbortController();
const request = apiClient.get<T[]>(this.endpoint, {
signal: controller.signal,
});
return { request, cancel: () => controller.abort() };
}
delete(id: number) {
return apiClient.delete(this.endpoint + "/" + id);
}
create<T>(entity: T) {
return apiClient.post(this.endpoint, entity);
}
update<T extends Entity>(entity: T) {
return apiClient.patch(
"http://jsonplaceholder.typicode.com/users/" + entity.id,
entity
);
}
}
const create = (endpoint: string) => new HttpService(endpoint);
export default create;
|
修改 user-service.ts
user-service.ts |
---|
| import create from "./http-service";
export interface User {
id: number;
name: string;
}
export default create("/users");
|
修改 App.tsx
由于在 http-service.ts
中 getAll()
方法使用的是通用的类型标注,因此 userService.getAll();
需要添加类型标注,即 userService.getAll<User>()
。
此处导入 user-service.ts
时重新命名为 userService
了。(默认导出 create("/user")
)
App.tsx |
---|
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 | import { useEffect, useState } from "react";
import { CanceledError } from "./services/api-client";
import userService, { User } from "./services/user-service";
function App() {
const [users, setUsers] = useState<User[]>([]);
const [error, setError] = useState("");
const [isLoading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
const { request, cancel } = userService.getAll<User>();
request
.then((res) => {
setUsers(res.data);
setLoading(false);
})
.catch((err) => {
if (err instanceof CanceledError) return;
setError(err.message);
setLoading(false);
});
return () => cancel();
}, []);
const deleteUser = (user: User) => {
const originalUsers = [...users];
setUsers(users.filter((u) => u.id !== user.id));
userService.delete(user.id).catch((err) => {
setError(err.message);
// 由于我们先操作 UI,如果没有删除服务器上的数据,出现了异常,应该还原列表。
setUsers(originalUsers);
});
};
const addUser = () => {
const newUser = { id: 0, name: "Mosh" };
const originalUsers = [...users];
setUsers([newUser, ...users]);
userService
.create(newUser)
.then(({ data: savedUser }) => setUsers([savedUser, ...users]))
.catch((err) => {
setUsers(originalUsers);
setError(err.message);
});
};
const updateUser = (user: User) => {
const updatedUser = { ...user, name: user.name + "!" };
const originalUsers = [...users];
setUsers(users.map((u) => (u.id === user.id ? updatedUser : u)));
userService.update(updatedUser).catch((err) => {
setError(err.message);
setUsers(originalUsers);
});
};
return (
<>
......
</>
);
}
|
Creating a Custom Data Fetching Hook
为了复用和模块化,所以需要封装 useEffect
钩子。(能跑就行 ^ ^ 防御型编程)
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 | import { useEffect, useState } from "react";
import { CanceledError } from "../services/api-client";
import userService, { User } from "../services/user-service";
const useUsers = () => {
const [users, setUsers] = useState<User[]>([]);
const [error, setError] = useState("");
const [isLoading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
const { request, cancel } = userService.getAll<User>();
request
.then((res) => {
setUsers(res.data);
setLoading(false);
})
.catch((err) => {
if (err instanceof CanceledError) return;
setError(err.message);
setLoading(false);
});
return () => cancel();
}, []);
return { users, error, isLoading, setUsers, setError };
};
export default useUsers;
|
如何使用呢?使用函数解构。
| const { users, error, isLoading, setUsers, setError } = useUsers();
|
2024-07-06 00:35 2024-07-10 23:53