目录

REST

Sending Data to the Server

Changing the importance of notes

Extracting communication with the backend into a separate module

Cleaner syntax for defining object literals

Promises and errors


Get a full fake REST API with zero coding in less than 30 seconds (seriously)

在不到30秒(严肃地)的情况下得到一个完整的模拟 REST API,0编码

REST

在REST 术语将单个数据对象(如应用中的便笺)称为resources。 每个资源都有一个唯一的地址——URL。 我们将能够在资源 URL, 即notes/3上定位某个便笺,其中3是资源的 id。 另一方面, notes url 指向包含所有便笺的资源集合。

通过 HTTP GET 请求从服务器获取资源。 例如,对 URLnotes/3 的 HTTP GET 请求将返回 id 为3的便笺。 对notes URL 的 HTTP GET 请求将返回所有便笺的列表。

通过向notes URL 发出 HTTP POST 请求来创建、存储新的便笺。 新便笺资源的数据在请求的body 中发送。

Json-server 要求以 JSON 格式发送所有数据。 数据必须是格式正确的字符串,并且请求必须包含值为application/json 的Content-Type 请求头。

Sending Data to the Server

【发送数据到服务器】

让我们对负责创建新便笺的事件处理进行如下更改:

addNote = event => {event.preventDefault()const noteObject = {content: newNote,date: new Date(),important: Math.random() < 0.5,}axios.post('http://localhost:3001/notes', noteObject).then(response => {console.log(response)})
}

为便笺创建了一个没有 id 的新对象,因为可以让服务器为资源生成 id!

使用 axios post 方法将对象发送到服务器。

尝试创建一个新的便笺时,控制台会弹出如下输出:

新创建的便笺资源存储在response对象的data 属性值中。

在 Chrome 开发工具Network 选项卡中检查 HTTP 请求:

可以使用检查器来检查 POST 请求中发送的头文件是否符合预期。

由于我们在 POST 请求中发送的数据是一个 JavaScript 对象,axios 会为Content-Type 头设置适当的application/json 值。

新的便笺还没有渲染到屏幕上。 这是因为在创建新便笺时没有更新App 组件的状态。 解决这个问题:

addNote = event => {event.preventDefault()const noteObject = {content: newNote,date: new Date(),important: Math.random() > 0.5,}axios.post('http://localhost:3001/notes', noteObject).then(response => {setNotes(notes.concat(response.data))setNewNote('')})
}

后端服务器返回的新便笺将按照使用 setNotes 函数然后重置便笺创建表单的惯例方式添加到应用状态的便笺列表中。 需要记住的一个 重要细节important detail 是 concat 方法不会改变组件的原始状态,而是创建列表的新副本。

一旦服务器返回的数据开始影响 web 应用的行为,就会立即面临一系列全新的挑战,例如,通信的异步性。 这就需要新的调试策略,控制台日志和其他调试手段,还必须对 JavaScript 运行时和 React 组件的原理有充分的理解。

通过浏览器检查后端服务器的状态:

验证我们发送的所有数据是否已经被服务器接收。

注意: 在当前版本的应用中,浏览器在便笺中添加了创建日期属性。 由于运行浏览器的机器的时钟可能错误地配置,所以最好让后端服务器生成这个时间戳。

Changing the importance of notes

【改变便笺的重要性】

为每个便笺添加一个按钮,用于切换它的重要性。

我们对Note 组件进行如下更改:

const Note = ({ note, toggleImportance }) => {const label = note.important? 'make not important' : 'make important'return (<li>{note.content} <button onClick={toggleImportance}>{label}</button></li>)
}

我们向组件添加一个按钮,并将其事件处理作为toggleImportance函数的 props 传递到组件中。

App组件定义了 toggleImportanceOf事件处理函数的初始版本,并将其传递给每个Note组件:

const App = () => {const [notes, setNotes] = useState([]) const [newNote, setNewNote] = useState('')const [showAll, setShowAll] = useState(true)// ...const toggleImportanceOf = (id) => {console.log('importance of ' + id + ' needs to be toggled')}// ...return (<div><h1>Notes</h1><div><button onClick={() => setShowAll(!showAll)}>show {showAll ? 'important' : 'all' }</button></div>      <ul>{notesToShow.map((note, i) => <Notekey={i}note={note} toggleImportance={() => toggleImportanceOf(note.id)}/>)}</ul>// ...</div>)
}

每个便笺根据唯一的 id 接收事件处理函数。

例如,如果我 note.id 是3, toggleImportance(note.id) 返回的事件处理函数将是:

() => { console.log('importance of 3 needs to be toggled') }

事件处理以类 java 的方式通过加号连接字符串定义字符串:

console.log('importance of ' + id + ' needs to be toggled')

在 ES6中,添加 template string 语法可以用一种更好的方式来编写类似的字符串:

console.log(`importance of ${id} needs to be toggled`)

现在可以使用“ dollar-bracket”语法向字符串中添加内容来计算 JavaScript 表达式,例如变量的值。 注意,模板字符串中使用的反引号与常规 JavaScript 字符串中使用的引号不同。

存储在 json-server 后端中的各个便笺可以通过对便笺的唯一 URL 发出 HTTP 请求,以两种不同的方式进行修改。 可以用 HTTP PUT 请求替换 整个便笺,或者只用 HTTP PATCH 请求更改便笺的一些属性。

事件处理函数的最终形式如下:

const toggleImportanceOf = id => {const url = `http://localhost:3001/notes/${id}`const note = notes.find(n => n.id === id)const changedNote = { ...note, important: !note.important }axios.put(url, changedNote).then(response => {setNotes(notes.map(note => note.id !== id ? note : response.data))})
}

第一行根据每个便笺资源的 id 定义其唯一的 url。

数组的 find方法用于查找要修改的便笺,然后将其分配给note变量。

在此之后,创建一个新对象,除了重要性属性,它完全是旧便笺的副本。

使用对象展开object spread语法创建新对象的代码

const changedNote = { ...note, important: !note.important }

{ ...note } 创建一个新对象,其中包含来自 note 对象的所有属性的副本。 当我们在 spreaded 对象后面的花括号中添加属性时,例如{ ...note, important: true },那么新对象的重要性属性的值将为 true。 这里 important 属性在原始对象中取其先前值的反值。

注意,新对象 changedNote 只是一个所谓的浅拷贝 ,新对象的值与旧对象的值相同。 如果旧对象的值本身就是对象,那么新对象中复制的值将引用旧对象中的相同对象

然后这个新便笺与一个 PUT 请求一起发送到后端,它将在后端替换旧对象。

回调函数将组件的 notes 状态设置为一个新数组,该数组包含前一个notes 数组中的所有条目,但旧的条目被服务器返回的更新版本所替换:

axios.put(url, changedNote).then(response => {setNotes(notes.map(note => note.id !== id ? note : response.data))
})

这是通过 map方法实现的:

notes.map(note => note.id !== id ? note : response.data)

Map 方法通过将旧数组中的每个项映射到新数组中的一个项来创建一个新数组。 这里,新数组被有条件地创建,即如果note.id !== id为true,我们只需将项从旧数组复制到新数组中。 如果条件为 false,则将服务器返回的 note 对象添加到数组中。

Extracting communication with the backend into a separate module

【将与后端的通信提取到单独的模块中】

在添加了用于与后端服务器通信的代码之后,App 组件变得有些臃肿。 现在将这种通信提取到它自己的模块。

创建一个src/services目录,并添加一个名为notes.js 的文件:

import axios from 'axios'
const baseUrl = 'http://localhost:3001/notes'const getAll = () => {return axios.get(baseUrl)
}const create = newObject => {return axios.post(baseUrl, newObject)
}const update = (id, newObject) => {return axios.put(`${baseUrl}/${id}`, newObject)
}export default { getAll: getAll, create: create, update: update
}

该模块返回一个具有三个函数(getAllcreate, and update)的对象,作为其处理便笺的属性。 函数直接返回 axios 方法返回的允诺Promise。

App 组件使用 import访问模块:

import noteService from './services/notes'
const App = () => {

该模块的功能可以直接与导入的变量 noteService 一起使用,具体如下:

const App = () => {// ...useEffect(() => {noteService.getAll().then(response => {setNotes(response.data)})}, [])const toggleImportanceOf = id => {const note = notes.find(n => n.id === id)const changedNote = { ...note, important: !note.important }noteService.update(id, changedNote).then(response => {setNotes(notes.map(note => note.id !== id ? note : response.data))})}const addNote = (event) => {event.preventDefault()const noteObject = {content: newNote,date: new Date().toISOString(),important: Math.random() > 0.5}noteService.create(noteObject).then(response => {setNotes(notes.concat(response.data))setNewNote('')})}// ...
}export default App

App 组件使用这些函数时,它接收到一个包含 HTTP 请求的整个响应的对象:

noteService.getAll().then(response => {setNotes(response.data)})

App 组件只使用response对象的 response.data 属性。

如果我们只获得响应数据,而不是整个 HTTP 响应,那么使用这个模块:

noteService.getAll().then(initialNotes => {setNotes(initialNotes)})

我们可以通过如下变更模块中的代码来实现 :

import axios from 'axios'
const baseUrl = 'http://localhost:3001/notes'const getAll = () => {const request = axios.get(baseUrl)return request.then(response => response.data)
}const create = newObject => {const request = axios.post(baseUrl, newObject)return request.then(response => response.data)
}const update = (id, newObject) => {const request = axios.put(`${baseUrl}/${id}`, newObject)return request.then(response => response.data)
}export default { getAll: getAll, create: create, update: update
}

我们将 promise 分配给 request 变量,并调用它的then 方法:

const getAll = () => {const request = axios.get(baseUrl)return request.then(response => response.data)
}

修改后的getAll 函数仍然返回一个 promise,因为 promise 的 then 方法也返回一个 promise。

在定义了then 方法的参数直接返回response.data 之后, 当 HTTP 请求成功时,promise 将返回从后端响应中发送回来的数据。

更新App 组件来处理对模块所做的更改。 修复作为参数给予noteService对象方法的回调函数,以便它们使用直接返回的响应数据:

const App = () => {// ...useEffect(() => {noteService.getAll().then(initialNotes => {setNotes(initialNotes)})}, [])const toggleImportanceOf = id => {const note = notes.find(n => n.id === id)const changedNote = { ...note, important: !note.important }noteService.update(id, changedNote).then(returnedNote => {setNotes(notes.map(note => note.id !== id ? note : returnedNote))})}const addNote = (event) => {event.preventDefault()const noteObject = {content: newNote,date: new Date().toISOString(),important: Math.random() > 0.5}noteService.create(noteObject).then(returnedNote => {setNotes(notes.concat(returnedNote))setNewNote('')})}// ...
}

Promise是现代 JavaScript 开发的核心,强烈建议投入合理的时间来理解。

Cleaner syntax for defining object literals

【用于定义对象字面量的更清晰的语法】

定义便笺相关服务的模块,导出一个具有属性getAllcreate 和update 的对象,这些属性分配给处理便笺的函数。

模块的定义是:

import axios from 'axios'
const baseUrl = 'http://localhost:3001/notes'const getAll = () => {const request = axios.get(baseUrl)return request.then(response => response.data)
}const create = newObject => {const request = axios.post(baseUrl, newObject)return request.then(response => response.data)
}const update = (id, newObject) => {const request = axios.put(`${baseUrl}/${id}`, newObject)return request.then(response => response.data)
}export default { getAll: getAll, create: create, update: update
}

该模块导出下面这个奇怪的对象:

{ getAll: getAll, create: create, update: update
}

在对象定义中,冒号左侧的标签是对象的,而它右侧的标签是在模块内部定义的variables

由于键和赋值变量的名称是相同的,可以用更简洁的语法来编写对象定义:

{ getAll, create, update
}

因此,模块定义被简化为如下形式:

import axios from 'axios'
const baseUrl = 'http://localhost:3001/notes'const getAll = () => {const request = axios.get(baseUrl)return request.then(response => response.data)
}const create = newObject => {const request = axios.post(baseUrl, newObject)return request.then(response => response.data)
}const update = (id, newObject) => {const request = axios.put(`${baseUrl}/${id}`, newObject)return request.then(response => response.data)
}export default { getAll, create, update }

这种较短的符号定义对象利用了通过 ES6引入到 JavaScript 中的一个new feature ,使得使用变量定义对象的方法更加简洁。

考虑这样一种情况: 我们给变量赋值如下:

const name = 'Leevi'
const age = 0

在旧版本的 JavaScript 中,必须这样定义一个对象:

const person = {name: name,age: age
}

然而,由于对象中的属性字段和变量名称都是相同的,只需在 ES6 JavaScript 中写入如下内容就足够了:

const person = { name, age }

两个表达式的结果是相同的。 它们都创建了一个值为Leevi 的name 属性和值为0 的age 属性的对象。

Promises and errors

【承诺和错误】

如果应用允许用户删除便笺,那么可能会出现这样的情况: 用户试图更改已经从系统中删除的便笺的重要性。

通过使 note 服务的getAll 函数返回一个“硬编码”的便笺来模拟这种情况,这个便笺实际上并不存在于后端服务器中:

const getAll = () => {const request = axios.get(baseUrl)const nonExisting = {id: 10000,content: 'This note is not saved to server',date: '2019-05-30T17:30:31.098Z',important: true,}return request.then(response => response.data.concat(nonExisting))
}

当我们试图更改硬编码说明的重要性时,在控制台中看到如下错误消息。后端服务器用状态码404not found 响应了我们的 HTTP PUT 请求。

应用应该能够很好地处理这些类型的错误情况。 除非用户碰巧打开了自己的控制台,否则无法判断错误确实发生了。

一个 promise 有三种不同的状态,当 HTTP 请求失败时是rejected。 当前的代码没有以任何方式处理这种拒绝。

rejected 通过给then 方法提供第二个回调函数来处理的,handled 在承诺被拒绝的情况下被调用。

rejected 添加处理程序的更常见的方法是使用catch方法,定义如下:

axios.get('http://example.com/probably_will_fail').then(response => {console.log('success!')}).catch(error => {console.log('fail')})

如果请求失败,则调用catch 方法注册的事件处理程序。

catch 方法通常置于Promise链的深处使用。

应用发出的HTTP 请求实际上是在创建一个 promise chain:

axios.put(`${baseUrl}/${id}`, newObject).then(response => response.data).then(changedNote => {// ...})

catch 方法可用于在承诺链的末尾定义一个处理程序函数,一旦 promise 链中的任何承诺抛出错误,承诺就变成rejected,就会调用该函数。

axios.put(`${baseUrl}/${id}`, newObject).then(response => response.data).then(changedNote => {// ...}).catch(error => {console.log('fail')})

使用这个特性并在App 组件中注册一个错误处理程序:

const toggleImportanceOf = id => {const note = notes.find(n => n.id === id)const changedNote = { ...note, important: !note.important }noteService.update(id, changedNote).then(returnedNote => {setNotes(notes.map(note => note.id !== id ? note : returnedNote))}).catch(error => {alert(`the note '${note.content}' was already deleted from server`)setNotes(notes.filter(n => n.id !== id))})
}

错误消息会通过弹出alert对话框显示给用户,并且已删除的便笺会从状态中过滤掉。

从应用的状态中删除已经删除的便笺是通过数组的 filter方法完成的,该方法返回一个新的数组,其中只包含列表中的项目,作为参数传递的函数返回 true :

notes.filter(n => n.id !== id)

alert这样简单的、经过实战检验的方法可以作为一个起点。可以在以后添加一个更高级的方法。

Web全栈开发学习笔记—Part2 与服务端通信—d.在服务端将数据Alert出来相关推荐

  1. Web全栈开发学习笔记—Part4 测试 Express 服务端程序, 以及用户管理—d.密钥认证

    目录 Limiting creating new notes to logged in users Error handling 现在将让后端支持基于令牌的认证下面的时序图描述了基于令牌认证的原理: ...

  2. Django全栈开发学习笔记(十二)——数据的增、删、改、查

    数据表操作 数据表操作主要为增.删.改.查.执行SQL语句和实现数据库事务等操作 数据新增:有模型实例化对象调用内置方法实现数据新增 数据修改必须执行一次数据查询,在对查询结果进行修改操作,常用方法有 ...

  3. Web全栈开发学习(1)

    一. 实验目的 掌握react的基本使用 掌握JSX基本语法 掌握组件的基本概念 二.实验环境 VScode 三.实验内容 react基本使用 1.react脚手架的使用   实验内容: 用react ...

  4. web全栈开发项目搭建整体思路和学习路线

    web全栈开发 全栈开发技术介绍: 全栈技术指可以完整整个项目搭建的有效集合. 包括:网站的设计,web前端开发,web后端开发,数据库设计,接口和组件,移动端开发,产品设计,系统架构,产品的理念和用 ...

  5. 【融职教育】Web全栈开发就业班核心优势

    IT技能培训行业现在是一片红海,在红海中求生存和发展就要具有一定的特色和竞争优势.本质上都是为学员提供更好的服务,提高教学品质,让学员可以学会技术,掌握足够工作技能,具有向企业交付的能力,让学员不仅可 ...

  6. Web全栈开发训练营

    Web全栈开发训练营 学习从头开始构建可发现的.引人入胜的渐进式 Web 应用程序 此视频教程共2.0小时,中英双语字幕,画质清晰无水印,源码附件全 课程英文名:Complete Progressiv ...

  7. 小白都能看懂的实战教程 手把手教你Python Web全栈开发(DAY 3)

    小白都能看懂的实战教程 手把手教你Python Web全栈开发 Flask(Python Web)实战系列之在线论坛系统 第三讲 这是小白都能看懂的实战教程 手把手教你Python Web全栈开发 的 ...

  8. 苏州木渎计算机课程培训,苏州木渎H5/web全栈开发培训

    课程名称:H5/web全栈开发 课程类型:网页设计 班型:全日制/周末班/晚班 适用校区:全校区 课程详情: 由于网络的发展以及人们使用网络的频繁性,企业需要通过网页呈现产品.服务.理念.文化,或向大 ...

  9. 【哈士奇赠书活动 - 18期】-〖Flask Web全栈开发实战〗

    文章目录 ⭐️ 赠书活动 - <Flask Web全栈开发实战> ⭐️ 编辑推荐 ⭐️ 内容提要 ⭐️ 赠书活动 → 获奖名单 ⭐️ 赠书活动 - <Flask Web全栈开发实战& ...

最新文章

  1. Ubuntu~Tensorflow~GPU
  2. POJ 1061扩展欧几里得
  3. [css] inline、block、inline-block这三个属性值有什么区别?
  4. 一道笔试题引发的Promise笔记
  5. jQuery remove()与jQuery empty()的区别
  6. asc怎么用 linux zip_linux的asc文件怎么打开
  7. ThinkPHP3.2开启静态缓存
  8. Oracle的权限角色及用户
  9. mysql 修改密码_经验总结 | MYSQL修改密码脚本,值得收藏
  10. 亿安科技作手_亿安科技作手(2)-李B
  11. 台湾19大IT业营收连衰 全球产业景气警报先兆
  12. C++ 野指针和悬空指针
  13. 数据库的数据保护:数据的安全性和完整性
  14. shell--bash变量
  15. 经典作品推荐CLANNAD(含下载、汉化、周边和攻略,真正全语音)
  16. ios 侧滑返回停顿_iOS侧滑卡死解决方法
  17. silk lobe资源公众号_小说免费阅读公众号:恐怖玄幻资源
  18. 用户主要通过计算机软件与计算机进行交流,大学计算机基础(2015版)蔡绍稷,吉根林习题三-答案讲述.doc...
  19. 基于51单片机的温控系统
  20. ghost系统之家Ghost XP SP3加强版V8.0_2010.4[NTFS版]

热门文章

  1. (阿里妈妈)淘宝客解析淘口令获取商品ID淘宝联盟解析淘口令获取num_iid
  2. 网狐登陆服务器启动失败
  3. 健康贴示(尽力每日更新)2007-02-12
  4. Oracle中select SEQ_YX.nextval from dual是什么意思
  5. java编译器源码分析之语法分析器
  6. HTML之淘宝界面案例
  7. 麦克风阵列仿真环境的搭建
  8. 第四课 尚硅谷Scala语言学习-面向对象
  9. 【无为则无心Python基础】— 18、Python字符串的格式化输出
  10. 手撕自动驾驶算法——多目标追踪:imm交互式多模型