教程:FullStackOpen/Part 2 - 与服务端通信
后端部分:FullStackOpen:phonebook app / back-end 要点总结
数据库部分:FullStackOpen:phonebook app / MongoDB 要点总结
1. 用 axios 和 REST API 实现 CRUD
Representational State Transfer,又名 REST,是一种架构风格,用于构建可伸缩的 web 应用。
在 RESTful thinking 中,每一个对象被称为 resource。每个 resource 都有一个相关联的 URL,结合resource 类型名称和 resource 的唯一标识符来创建 resource 唯一的地址。
如果将 phonebook 的资源类型定义为 persons,那么标识为10的 persons 资源的地址就是唯一的地址 [root_url]/api/persons/10
。所有 persons 资源的整个集合的 URL 是 [root_url]/api/persons
。
可以用不同的 HTTP method 对资源执行不同的操作。
URL | METHOD | FUNCTION |
---|---|---|
persons/10 | GET | 获取单一资源 |
persons | GET | 获取集合中的所有资源 |
persons | POST | 依据请求数据创建一个新的资源 |
persons/10 | DELETE | 删除指定资源 |
persons/10 | PUT | 使用请求数据替换指定资源 |
persons/10 | PATCH | 使用请求数据替换指定资源的一部分 |
使用 axios 将数据发送到服务器并获取响应,实现 CRUD 操作:
import axios from "axios";
const baseUrl = "/api/persons";
const getAll = () => {
const request = axios.get(baseUrl);
return request.then((response) => response.data);
};
const create = (newPerson) => {
const request = axios.post(baseUrl, newPerson);
return request.then((response) => response.data);
};
const update = (id, newPerson) => {
const request = axios.put(`${baseUrl}/${id}`, newPerson);
return request.then((response) => response.data);
};
const remove = (id) => {
const request = axios.delete(`${baseUrl}/${id}`);
return request.then((response) => response.data);
};
2. 将与后端的通讯提取为独立 service module
在添加了用于与后端服务器通信的代码之后,App 组件变得有些臃肿。本着单一职责原则,应当将这部分代码提取到它自己的模块(module)中。
- 创建一个src/services目录,并添加一个名为 phonebook.js 的文件。
- 该模块返回一个具有多个函数的对象。因为 promise 的 then 方法也返回一个 promise,函数仍然返回一个 promise。
- App 组件使用 import 访问模块。
services/phonebook.js:
import axios from "axios";
const baseUrl = "/api/persons";
const getAll = () => {...};
const create = (newPerson) => {...};
const update = (id, newPerson) => {...};
const remove = (id) => {...};
export default {
getAll,
create,
update,
remove,
};
App.js:
import phonebookService from "./services/phonebook";
const App = () => {
...
useEffect(() => {
phonebookService.getAll().then((data) => {
setPersons(data);
});
}, []);
...
};
3. 获取后端数据后更新 App 组件的状态
初始化数据:
useEffect(() => {
phonebookService.getAll().then((data) => {
setPersons(data);
});
}, []);
添加新数据:
const newPerson = {
name: newName,
number: newNumber,
};
phonebookService.create(newPerson).then((data) => {
setPersons(persons.concat(data));
});
修改原有数据:
const person = persons.find((p) => p.name === newName);
const updatedPerson = { ...person, number: newNumber };
phonebookService.update(person.id, updatedPerson).then((data) => {
setPersons(persons.map((p) => (p.id !== person.id ? p : data)));
});
删除数据:
phonebookService
.remove(person.id)
.then((data) => {
setPersons(persons.filter((p) => p.id !== person.id));
})
4. 处理 promise 的 error
为被 rejected 的 promise 添加 event handler 的常见的做法是使用 catch 方法。
如果请求失败,则调用 catch 方法注册的事件处理程序。catch 方法通常是通过将其置于 promise 链的更深处来使用。
phonebookService
.remove(person.id)
.then((data) => {
setPersons(persons.filter((p) => p.id !== person.id));
})
.catch((error) => {
setErrorMsg(`${person.name} already deleted`);
setErrorMsgClass("error");
});
5. 在 JSX 中添加 CSS 样式
方法一:外联样式
以传统的方式将 CSS 放在一个单独的文件。
- 在 src 目录下添加一个 index.css 文件,通过导入 index.js 将其添加到应用中。
- 使用不同类型的 CSS 选择器匹配元素。在React中,必须使用
className
属性而不是class
属性。
index.js:
import './index.css'
App.js:
const Notification = ({ message, msgClass }) => {
return <div className={msgClass}>{message}</div>;
};
方法二:内联样式
任何 React 组件或元素都可以通过 style
属性作为 JavaScript 对象提供一组 CSS 属性。
每个 CSS 属性都被定义为 JavaScript 对象的一个独立属性,例如像素的数值可以简单地定义为整数。与常规 CSS 相比,一个主要的区别是连字符的 CSS 属性是用 camelCase 编写的。
内联样式有一定的限制,例如,所谓的 pseudo-classes 不能直接使用。
const Footer = () => {
const footerStyle = {
color: 'green',
fontStyle: 'italic',
fontSize: 16
}
return (
<div style={footerStyle}>
...
</div>
)
}