教程:FullStackOpen/Part 2 - 与服务端通信

后端部分:FullStackOpen:phonebook app / back-end 要点总结

数据库部分:FullStackOpen:phonebook app / MongoDB 要点总结

App:React Phonebook App


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)中。

  1. 创建一个src/services目录,并添加一个名为 phonebook.js 的文件。
  2. 该模块返回一个具有多个函数的对象。因为 promise 的 then 方法也返回一个 promise,函数仍然返回一个 promise。
  3. 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 放在一个单独的文件。

  1. 在 src 目录下添加一个 index.css 文件,通过导入 index.js 将其添加到应用中。
  2. 使用不同类型的 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> 
  )
}