第1章 准备:搭建React Node.js
开发环境 工欲善其事,必先利其器。学习任何一门新技术、新语言,都需要从*基础的环境搭建开始。本章将从零开始搭建一个适合初学者的React Node.js开发环境。 本章的主要知识点包括: l 环境优势:聊一聊选择React Node.js技术的原因; l 环境搭建:详细介绍React Node.js开发环境的搭建过程; l 开发工具:介绍在日常开发中使用的IDE(Visual Studio Code)和调试工具(Chrome、Postman等)。 小知识:IDE(Integrated Development Environment,集成开发环境),是提供程序开发环境的应用程序,通常包括编辑器、编译器、调试器和图形用户界面等功能模块。熟练掌握IDE的使用,可以大大提升开发效率。 1.1 为什么选择React Node.js 开发技术和框架那么多,为什么选择React.js和Node.js技术呢?或者说,React.js和Node.js技术有哪些优势呢? %说明:为方便描述,本书后面除标题外的部分都以Node来代替Node.js,以React来代替React.js。 1.1.1 React的优势 React(https://zh-hans.reactjs.org/)是用于构建用户界面的JavaScript库。只需要对HTML和JavaScript有简单了解就可以使用React进行开发,因此,React作为前端开发工具越来越受到***的欢迎。 与其他框架相比,React具备以下优点: (1)快速学习曲线:React是一个非常简单且轻量的库,它只处理视图层。任何有JavaScript经验的开发人员都可以理解其基础知识,在阅读完官方教程后,基本上就可以开发Web应用程序。 (2)可重复使用的组件:React提供基于组件的结构,组件相当于积木,***可以创建按钮、复选框、列表等小组件,并组合这些组件形成较复杂的组件,然后再继续组合,直到根组件为止,这些组件就构成了开发的应用程序。 (3)基于虚拟DOM的快速渲染:当开发复杂用户交互的Web应用程序时,需要频繁操作DOM,而操作DOM的代价较高,因为频繁操作DOM会导致浏览器的重绘和重排,进而影响性能。 这也正是React要解决的核心问题之一,它使用虚拟DOM来解决这个问题。任何视图的更改首先反馈到虚拟DOM,然后通过算法比较虚拟DOM的先前和当前状态,得出状态变化和差异,*后将这些更改应用于DOM。 虚拟DOM大大减少了操作DOM的次数和修改DOM的范围,这也是React之所以高性能的主要原因。 1.1.2 Node.js的优势 Node(https://nodejs.org/zh-cn/)是一个基于Chrome V8引擎的JavaScript运行时环境。它是一种轻量级、可扩展、跨平台的代码执行方式。 小知识:Chrome V8是一个由Google开发的开源JavaScript引擎,用于Google Chrome及Chromium中。Chrome V8在运行之前会将JavaScript代码编译成机器代码而非字节码,以此提升程序性能。更进一步,Chrome V8使用了如内联缓存(Inline Caching)等方法来提高性能。有了这些功能,JavaScript程序与Chrome V8引擎的运行速度可媲美二进制编译的程序。 选择Node进行开发的优点主要包括: l 使用JavaScript语言开发,便于前端***快速学习和掌握; l 易于快速构建实时应用程序(例如,开发聊天室应用),并基于Express(https://expressjs. com/zh-cn/)、socket.io(https://socket.io/)等技术开发; l 快速发展的NPM扩展包(https://www.npmjs.com/)提供了丰富的工具和模块,极大地提高了开发效率; l 基于事件驱动的非阻塞I/O模型,使其具有很高的并发执行效率。 1.1.3 React Node.js组合的优势 基于React开发前端,再配合Node开发服务端应用,优势如下: l JavaScript语言可以同时为客户端和服务端编码,这也让前后端开发变得容易,扫清了开发语言上的障碍; l 使用JavaScript语言及Node开发环境和生态,让团队的技术栈能够实现*大化的共享,减少了协作沟通的代价; l 随着技术学习和迁移难度的降低,企业招聘、培训和用人等综合成本也开始下降。 综上所述,本书选择React和Node技术进行讲解,以便让更多的读者掌握全栈开发技术,具备完整项目的前后端问题解决能力。 下面正式开启React和Node的学习与开发之旅。 1.2 搭建Node.js环境 本节将搭建Node开发环境,搭建完成后通过一个简单示例来展示效果,使读者对Node有一个初步的认识。 %提示:关于Node开发的相关知识,会在第3章中详细介绍。 1.2.1 安装Node.js Node的安装有如下几种方式: l 通过源码编译安装; l 通过安装包安装; l 通过系统包管理器安装; l 通过Node版本管理工具安装。 其中,下载源码然后编译安装的方法比较复杂,通常情况下,选择其他方式安装Node即可满足开发需求。因此,下面将**介绍其他3种安装方法。 安装Node前,需要确定所安装的Node版本,笔者**安装*新的LTS版本。在本书写作时,Node的*新LTS版本是v12.14.*。 小知识:LTS(Long-Term Support,长期支持)是一种软件的产品生命周期政策,特别是对于开源软件,它增加了软件开发过程及软件版本周期的可靠度。 1.安装包 访问Node官网的下载地址(https://nodejs.org/zh-cn/download/),下载指定系统的安装包,如图1.1所示。 图1.1 访问Node官网下载安装包 下面以macOS系统为例(其他系统安装方式类似),介绍通过安装包安装Node的过程。 (1)下载macOS安装包文件node-v12.14.1.pkg。 (2)单击安装包开始安装,效果如图1.2所示。 图1.2 开始安装Node (3)按照提示依次单击Continue、Agree及Install按钮,直到安装成功,效果如图1.3所示。 图1.3 通过安装包成功安装Node (4)安装成功后,查看当前已安装的Node版本,验证安装是否成功,命令如下: node --version v12.14.1 在安装Node的同时还会安装Node包管理器NPM(Node Package Manager),查看其版本,命令如下: npm --version 6.13.4 小知识:软件包管理器是指自动安装、配置、卸载和升级软件包的工具组合。NPM是Node默认的以JavaScript编写的软件包管理器。 2.系统包管理 除了使用安装包安装Node之外,还可以使用当前操作系统的软件包管理器来安装Node。 l Windows系统的包管理器为Chocolatey(https://chocolatey.org/); l Linux的Ubuntu发行版的包管理器为APT(Advanced Packaging Tools); l macOS系统的包管理器为Homebrew(https://brew.sh/)。 下面以macOS系统为例(其他系统包管理器类似),介绍如何使用包管理器安装Node。 小知识:Homebrew是macOS系统默认的软件包管理器,使用Homebrew可以安装Apple没有预装但***需要的工具。更多关于Homebrew的介绍,可以访问其官网https://brew.sh/。 (1)安装Homebrew的命令很简单,具体如下: /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/ install/master/install)" (2)验证已安装的Homebrew工具,命令如下: brew version Homebrew 2.1.6 (3)使用安装好的Homebrew来搜索和安装Node,命令如下: brew search node brew install node 3.版本管理器 除了上述安装方法外,更加灵活的方法是使用Node版本管理器进行安装。使用版本管理器可以实现在同一台机器上安装和切换不同版本的Node环境。 常见的Node版本管理器主要有: l nvm(https://github.com/nvm-sh/nvm); l n(https://github.com/tj/n)。 小知识:n的作者是TJ Holowaychuk,这是Node圈内的一位重量级人物,不仅开发了Node版本管理器n,还是Koa、Co、Express、Jade、Mocha、node-canvas和commander.js等知名开源项目的创建者和贡献者。 nvm和n的功能类似。下面就以nvm为例来介绍Node版本管理器的使用。 (1)安装nvm,安装命令很简单,具体如下: curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.2/install.sh | bash 也可以使用如下命令进行安装: wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.2/install.sh | bash (2)根据提示进行如下操作: => Close and reopen your terminal to start using nvm or run the following to use it now: export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion (3)重启当前终端或直接运行上述命令。当然,*方便的办法是将上述命令添加到当前Shell的配置文件中,以macOS为例,可以添加到“~/.bashrc”文件中。 配置成功后便可以使用nvm命令安装并管理Node。 (4)查看可以安装的所有LTS版本,命令如下: nvm ls-remote --lts (5)安装*新的LTS版本v12.14.1,命令如下: nvm install v12.14.1 (6)查看本地已安装的Node版本,以及默认的Node版本等信息,命令如下: nvm ls (7)如果安装了多个Node版本,可以设置默认的Node版本,命令如下: nvm alias default v12.14.1 %提示:nvm命令和功能还有很多,读者可以参考其官网文档,网址为https://github.com/ nvm-sh/nvm。 1.2.2 常用工具1:NPM、CNPM和NRM 在Node环境搭建和开发的过程中,经常需要用到的工具还有NPM。因此,在学习Node开发之前,还需要熟练掌握包管理器的使用。 1.NPM 安装Node时同时会安装NPM,它主要有如下命令: # 初始化Node项目,生成package.json文件 npm init # 查看本地安装目录 npm root # 安装本地依赖包 npm install # 安装运行依赖包,并且将其保存至package.json文件中 npm install --save # 安装开发依赖包,并且将其保存至package.json文件中 npm install --save-dev # 更新本地依赖包 npm update # 查看本地依赖包 npm ls # 卸载本地依赖包 npm uninstall # 查看全局安装目录 npm root -g # 安装全局依赖包 npm install -g # 更新全局依赖包 npm update -g # 查看全局依赖包 npm ls -g # 卸载全局依赖包 npm uninstall -g # 查看依赖包信息 npm info # 执行scripts配置的命令 npm run 如果前期记不住这么多命令的话,可以使用NPM的帮助命令,具体如下: npm help 或 npm h 2.CNPM NPM安装包是从国外服务器上下载的,受网络因素影响较大,可能会出现异常。因此,国内的淘宝团队同步NPM实现了NPM的国内源。我们可以在使用NPM安装包时配置淘宝的国内源,命令如下: npm --registry=https://registry.npm.taobao.org 为了方便使用,淘宝团队不仅提供了上述镜像源,还开发了一个更易用的工具CNPM(https://npm.taobao.org/),不仅自动使用国内源,而且还支持gzip压缩。 因此可以安装CNPM来替代NPM,命令如下: npm install -g cnpm --registry=https://registry.npm.taobao.org 3.NRM 如果想要使用其他非淘宝的镜像源,还可以安装一款名为NRM(https://github.com/ Pana/nrm)的镜像源管理工具,命令如下: npm install -g nrm 然后查看所有可用的镜像源,命令如下: nrm ls npm -------- https://registry.npmjs.org/ yarn ------- https://registry.yarnpkg.com/ cnpm ------- http://r.cnpmjs.org/ * taobao ----- https://registry.npm.taobao.org/ nj --------- https://registry.nodejitsu.com/ npmMirror -- https://skimdb.npmjs.com/registry/ edunpm ----- http://registry.enpmjs.org/ *后设置想要使用的镜像源,命令如下: nrm use cnpm 1.2.3 常用工具2:YARN 除了NPM外,还可以使用一款叫作YARN的替代工具。 YARN(https://yarnpkg.com/)是Facebook(https://about.fb.com/)等公司开发的用于替换NPM的包管理工具。那么YARN有哪些优势足以替代NPM呢? l 速度超快:YARN缓存了每个曾经下载过的包,所以再次使用这些包时无须重复下载。同时,利用并行下载使资源利用率*大化,因此安装速度更快。 l ****:在执行代码之前,YARN会通过算法校验每个安装包的完整性。 l **可靠:使用详细、简洁的锁文件(yarn.lock)格式和明确的安装依赖包的算法,YARN能够保证在不同系统上无差异地工作。 %提示:除了上述优点,YARN还有许多有用的特性,读者可以自行参考官方文档,网址是https://yarn.bootcss.com/。 YARN的安装很简单,这里仍然以macOS系统为例,使用Homebrew包管理器来安装,具体命令如下: brew install yarn yarn --version 1.17.0 YARN的使用和NPM一样,非常容易上手,从NPM迁移到YARN的命令对照如表1.1所示。 表1.1 从NPM迁移到YARN的命令对照表 操 作 NPM命令 YARN命令 初始化Node项目 npm init yarn init 安装本地依赖包 npm install yarn 安装运行依赖包,并且保存至package.json文件中 npm install --save yarn add 安装开发依赖包,并且保存至package.json文件中 npm install --save-dev yarn add --dev 更新本地依赖包 npm update yarn upgrade 卸载本地依赖包 npm uninstall yarn remove 安装全局依赖包 npm install -g yarn global add 更新全局依赖包 npm update -g yarn global upgrade 查看全局依赖包 npm ls -g yarn global list 卸载全局依赖包 npm uninstall -g yarn global remove 1.2.4 常用工具3:npx和npm scripts 1.NPM自带的包执行器——npx npx是什么?可能很多Node***对这个小工具并没有太多关注。npx是NPM自带的一个包执行器。npx要解决的主要问题是调用项目内部安装的模块。就像NPM极大地提升了安装和管理包依赖的体验,在NPM的基础之上,npx让NPM包中的命令行工具和其他可执行文件在使用上变得更加简单。 由于安装NPM时已经自带npx,因此只需要验证当前的npx版本即可,具体命令如下: npx --version 6.13.4 下面以测试工具Mocha(https://mochajs.org/)为例,介绍npx的用法。 在本地安装Mocha依赖包,命令如下: npm install mocha 使用如下方式执行Mocha命令: ./node_modules/.bin/mocha --version 7.0.1 此时可以使用npx代替上述方式: npx mocha --version 7.0.1 %提示:除了上述命令,npx还有许多有用的特性,读者可以自行参考官方文档,网址为https://github.com/npm/npx。 2.npm scripts简介 NPM还有一个常用的命令工具,即npm scripts。 npm scripts是指在package.json文件中使用scripts字段定义的脚本命令,例如: 01 { 02 "scripts": { 03 "start": "node ./bin/www" 04 } 05 } 此时如果想要运行项目,可以直接执行以下命令: npm run start 同时,start作为一个常用命令,还支持如下简写: npm start npm scripts的用法还包括: l 项目的相关脚本,可以集中在一个地方。 l 不同项目的脚本命令,只要功能相同,就可以使用相同的npm scripts。例如,启动项目统一使用npm run start命令。 l 此外,还可以利用NPM提供的很多辅助功能。对于NPM的辅助功能,这里以npm scripts的钩子功能为例进行介绍。 npm scripts有pre和post两个钩子,如start脚本命令的钩子是prestart和poststart。当执行npm run start时,会自动按照下面的顺序执行: npm run prestart && npm run start && npm run poststart 因此,可以在这两个钩子中完成一些前置工作和后续工作,例如: 01 { 02 "scripts": { 03 "prestart": "npm run build", 04 "start": "node ./bin/www", 05 "poststart": "echo node server started" 06 } 07 } 除此之外,NPM默认还提供下面这些钩子: prepublish,postpublish preinstall,postinstall preuninstall,postuninstall preversion,postversion pretest,posttest prestop,poststop prestart,poststart prerestart,postrestart %提示:npm scripts除了上述介绍的功能之外,还有许多有用的特性,读者可以自行参考官方文档,网址为https://docs.npmjs.com/misc/scripts。 1.2.5 **个Node.js示例 前面已经将Node开发环境搭建完成,接下来可以开发**个Node示例。 (1)新建JavaScript文件并命名为HelloWorld.js,代码如下: 01 var http = require("http"); 其中,通过require()引入了Node内置的HTTP模块。 (2)通过http.createServer()方法创建一个HTTP服务,代码如下: 01 var http = require("http"); 02 03 var server = http.createServer((request, response) => { 04 response.end(); 05 }) 06 server.listen(8000); (3)接收请求并响应请求,修改代码如下: 01 var http = require("http"); 02 03 var server = http.createServer((request, response) => { 04 // 发送HTTP头部 05 // HTTP 状态值:200 : OK 06 // 内容类型:text/plain 07 response.writeHead(200, { 'Content-Type': 'text/plain' }); 08 09 // 请求的响应数据 10 response.end('Hello World'); 11 }) 12 server.listen(8000); // 监听8000端口 13 14 console.log('Server running at http://127.0.0.1:8000/') (4)启动Node服务,命令如下: node HelloWorld.js Node服务启动成功,命令窗口输出结果如下: Server running at http://127.0.0.1:8000/ (5)此时,使用浏览器访问http://127.0.0.1:8000/,页面显示Hello World,如图1.4所示。 图1.4 **个Node示例 以上便是基于Node开发的**个示例。回顾这个示例,主要步骤如下: (1)通过required()引入模块。 (2)创建HTTP服务,并监听指定的端口。 (3)接收请求并响应请求。 1.3 搭建React环境 1.2节介绍了Node环境的搭建,并编写了**个基于Node的HTTP服务。本节将介绍React环境的搭建,同时会通过React和Node结合开发一个完整的例子,让读者对二者有一个大概的认识。 1.3.1 安装React 安装React有以下两种方式: l 使用CDN链接; l 使用create-react-app工具。 1.使用CDN链接 React官方提供的CDN链接如下: <script src="https://unpkg.com/react@16/umd/react.development.js"></script> <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"> </script> 需要注意的是,上述CDN链接只适用于开发环境,不适用于生产环境。生产环境中,需要使用压缩等优化处理后的依赖包,以节约带宽,提**率,其链接如下: <script src="https://unpkg.com/react@16/umd/react.production.min.js"> </script> <script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"> </script> 小知识:CDN的全称是Content Delivery Network,即内容分发网络。CDN是构建在现有网络基础之上的智能虚拟网络,依靠部署在各地的边缘服务器,通过**平台的负载均衡、内容分发和调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问的响应速度和命中率。 成功引入React的相关依赖包后,下面通过一个例子来了解React的用法。 (1)新建HTML文件并命名为react_example.html,编写代码如下: 01 <!DOCTYPE html> 02 <html lang="en"> 03 04 <head> 05 <meta charset="UTF-8"> 06 <title>React Example</title> 07 <script src="https://unpkg.com/react@16/umd/react.development.js"> </script> 08 <script src="https://unpkg.com/react-dom@16/umd/react-dom. development.js"></script> 09 </head> 10 11 <body> 12 <div id="app"></div> 13 </body> 14 <script> 15 const e = React.createElement( 16 'h1', 17 null, 18 'Hello React!' 19 ) 20 ReactDOM.render( 21 e, 22 document.getElementById('app') 23 ) 24 </script> 25 26 </html> (2)使用浏览器打开上述HTML文件,可以看到如图1.5所示的效果。 图1.5 使用CDN链接 (3)上述代码中使用的是React的原生写法,为了简化编码,React还提供了一种叫作JSX(JavaScript XML)的写法。想要在React中使用JSX,需要引入Babel的依赖包,命令如下: <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> %提示:第2章会对JSX做详细介绍。 (4)为<script>标签添加type="text/babel"属性。 (5)使用React JSX语法进行编码,具体代码如下: 01 <!DOCTYPE html> 02 <html lang="en"> 03 04 <head> 05 <meta charset="UTF-8"> 06 <title>React Example</title> 07 <script src="https://unpkg.com/react@16/umd/react.development.js"> </script> 08 <script src="https://unpkg.com/react-dom@16/umd/react-dom. development.js"></script> 09 <script src="https://unpkg.com/babel-standalone@6/babel.min.js"> </script> 10 </head> 11 12 <body> 13 <div id="app"></div> 14 </body> 15 <script type="text/babel"> 16 ReactDOM.render( 17 <h1>Hello React!</h1>, 18 document.getElementById('app') 19 ) 20 </script> 21 22 </html> 2.使用create-react-app工具 create-react-app是React团队**的工具,通过该工具无须任何配置就能快速构建React开发环境。它在内部使用Babel和Webpack,但读者无须了解它们的任何细节。要使用该工具,需要确保已安装的Node版本是8.10以上,npm版本是5.6以上。 %提示:第4章会对Webpack做详细介绍。 (1)全局安装create-react-app工具,命令如下: npm install -g create-react-app (2)使用create-react-app工具创建项目,命令如下: create-react-app first-app cd first-app (3)使用如下npm scripts运行该项目: npm start // 或者 yarn start 此时,服务启动后会在浏览器中自动打开http://localhost:3000,效果如图1.6所示。 图1.6 使用create-react-app工具的效果展示 1.3.2 **个React示例 1.3.1节介绍了React的两种安装方式。本节将基于create-react-app工具来开发一个待办事项的应用程序(下面称为TodoList App),通过这个例子来了解React开发的全过程。 这个待办事项的应用程序主要具有以下功能: l 查看待办事项; l 添加待办事项; l 删除待办事项; l 修改待办事项状态。 1.TodoList App 1.0版本 具体操作步骤如下: (1)通过create-react-app工具新建初始项目,命令如下: create-react-app todo-list cd todo-list npm start // 或者用yarn start (2)此时浏览器会自动打开http://localhost:3000。至此,新建项目成功。 (3)修改项目中的文件./src/App.js,完整代码如下: 01 import React from 'react'; 02 03 export default class App extends React.Component { 04 constructor(props) { 05 super(props); 06 } 07 08 render() { 09 return ( 10 <div> 11 <h1>My First React App -- todo-list</h1> 12 </div> 13 ); 14 } 15 } (4)此时会自动刷新浏览器页面,效果如图1.7所示。 图1.7 TodoList初始化效果 %提示:当更新项目代码时,浏览器自动刷新是依赖Webpack实现的。第4章会对Webpack进行介绍。 项目初始化完成后,要进行具体的代码编写。 (5)实现待办事项列表的显示。修改./src/App.js文件的代码如下: 01 import React from 'react'; 02 03 export default class App extends React.Component { 04 constructor(props) { 05 super(props); 06 this.state = { 07 todoItems: [ 08 { id: 0, value: 'React', done: false, delete: false } 09 ] 10 } 11 } 12 13 render() { 14 return ( 15 <div> 16 <h1>TodoList</h1> 17 <div> 18 <input type="text" placeholder="add something..." /> 19 <button type="submit">添加</button> 20 </div> 21 <ul> 22 { 23 this.state.todoItems.map((item) => { 24 if (item.delete) return; 25 return ( 26 <li key={item.id}> 27 <label>{item.value}</label> 28 <button>删除</button> 29 </li> 30 ) 31 }) 32 } 33 </ul> 34 </div> 35 ); 36 } 37 } 此时,浏览器自动刷新页面,效果如图1.8所示。 图1.8 TodoList App列表效果 (6)实现添加和删除待办事项等相关功能。修改./src/App.js文件的代码如下: 01 import React from 'react'; 02 03 export default class App extends React.Component { 04 // 省略了未修改的代码 05 06 addTodoItem = () => { 07 const newTodoItem = { 08 id: this.state.todoItems.length, 09 value: this.refs.todoItemValue.value, 10 done: false, 11 delete: false 12 }; 13 this.setState({ 14 todoItems: [...this.state.todoItems, newTodoItem] 15 }) 16 } 17 18 deleteTodoItem = (item) => { 19 item.delete = true; 20 this.setState({ 21 todoItems: [...this.state.todoItems, item] 22 }) 23 } 24 25 render() { 26 return ( 27 <div> 28 <h1>TodoList</h1> 29 <div> 30 <input 31 type="text" 32 ref="todoItemValue" 33 placeholder="add something..." 34 /> 35 <button 36 type="submit" 37 onClick={this.addTodoItem} 38 > 39 添加 40 </button> 41 </div> 42 <ul> 43 { 44 this.state.todoItems.map((item) => { 45 if (item.delete) return; 46 return ( 47 <li key={item.id}> 48 <label>{item.value}</label> 49 <button 50 onClick={() => this.deleteTodoItem (item)} 51 > 52 删除 53 </button> 54 </li> 55 ) 56 }) 57 } 58 </ul> 59 </div> 60 ); 61 } 62 } (7)修改待办事项状态与删除待办事项的写法类似,这里不再赘述,留给读者自行练习。 至此,一个简单的TodoList App就完成了。 虽然TodoList App的功能已基本实现,但写法却并不“优雅”,没有用到React组件等特性,而可重复使用组件正是React的优势所在。 2.TodoList App 2.0版本 下面将通过封装组件的方式对现有的TodoList App进行优化。 通过对TodoList App现有功能进行分析,大概可以将其分成以下几个组件。 l TodoForm:待办事项表单,包括输入框和添加功能; l TodoList:待办事项列表; l TodoListItem:待办事项列表中的具体内容及相关操作。 具体实现步骤如下: (1)新建文件并命名为./src/TodoForm.js,编写代码如下: 01 import React from 'react'; 02 03 export default class TodoForm extends React.Component { 04 addTodoItem = () => { 05 this.props.addTodoItem(this.refs.todoItemValue.value); 06 } 07 08 render() { 09 return ( 10 <div> 11 <input 12 type="text" 13 ref="todoItemValue" 14 placeholder="add or search something..." 15 /> 16 <button type="submit" onClick={this.addTodoItem}>添加 </button> 17 </div> 18 ) 19 } 20 } (2)修改./src/App.js的逻辑和代码如下: 01 import React from 'react'; 02 import TodoForm from './TodoForm'; 03 04 export default class App extends React.Component { 05 // 省略了未修改的代码 06 07 addTodoItem = (todoItemValue) => { 08 const newTodoItem = { 09 id: this.state.todoItems.length, 10 value: todoItemValue, 11 done: false, 12 delete: false 13 }; 14 this.setState({ 15 todoItems: [...this.state.todoItems, newTodoItem] 16 }) 17 } 18 19 // 省略了未修改的代码 20 21 render() { 22 return ( 23 <div> 24 <h1>TodoList</h1> 25 <TodoForm 26 addTodoItem={this.addTodoItem} 27 /> 28 // 省略了未修改的代码 29 </div> 30 ); 31 } 32 } %提示:上述例子中用到了数据流等相关知识,如State和Props等,会在第2章中详细介绍。 (3)按照TodoForm组件的思路实现TodoListItem组件,即新建./src/TodoListItem.js文件,代码如下: 01 import React from 'react'; 02 03 export default class TodoListItem extends React.Component { 04 deleteTodoItem = () => { 05 this.props.deleteTodoItem(this.props.item); 06 } 07 08 render() { 09 return ( 10 <li> 11 <label>{this.props.item.value}</label> 12 <button 13 onClick={this.deleteTodoItem} 14 > 15 删除 16 </button> 17 </li> 18 ) 19 } 20 } (4)实现TodoList组件,即新建./src/TodoList.js文件,代码如下: 01 import React from 'react'; 02 import TodoListItem from './TodoListItem'; 03 04 export default class TodoList extends React.Component { 05 deleteTodoItem = (item) => { 06 this.props.deleteTodoItem(item); 07 } 08 09 render() { 10 return ( 11 <ul> 12 { 13 this.props.todoItems.map((item) => { 14 if (item.delete) return; 15 return ( 16 <TodoListItem 17 key={item.id} 18 item={item} 19 deleteTodoItem={this.deleteTodoItem} 20 /> 21 ) 22 }) 23 } 24 </ul> 25 ) 26 } 27 } (5)修改./src/App.js的逻辑和代码如下: 01 import React from 'react'; 02 import TodoForm from './TodoForm'; 03 import TodoList from './TodoList'; 04 05 export default class App extends React.Component { 06 // 省略了未修改的代码 07 08 render() { 09 return ( 10 <div> 11 <h1>TodoList</h1> 12 <TodoForm 13 addTodoItem={this.addTodoItem} 14 /> 15 <TodoList 16 todoItems={this.state.todoItems} 17 deleteTodoItem={this.deleteTodoItem} 18 /> 19 </div> 20 ); 21 } 22 } 此时,组件化封装优化后的TodoList App基本完成,效果如图1.9所示。 图1.9 TodoList App 2.0版本界面 至此,相信读者不仅了解了React开发的相关知识,也对React的组件化思想有了初步的认识。 1.3.3 **个React Node.js组合示例 1.3.2节中实现的TodoList App,数据来源是由前端定义的,通常Web应用的数据都是存储在服务端的数据库中。前端通过基于HTTP的接口来完成数据的增、删、改、查等操作。 1.服务端(Node端) 下面基于前面学习的Node开发知识来构建TodoList App的服务端程序。 (1)新建一个Node项目,命令如下: mkdir todo-list-server cd todo-list-server (2)使用npm init命令初始化Node项目生成package.json文件。项目初始化时会提示输入若干项,可以按Enter键接受默认值。如果想跳过提示直接生成package.json文件,还可以使用如下命令: npm init -y (3)为了简化服务端的实现代码,还需要安装Express依赖包,命令如下: npm install --save express %提示:Express是一个保持*简化规模且灵活的Node Web应用程序框架,它为Web和移动应用程序提供了强大的功能。关于Express框架的使用,第5章会详细介绍。 (4)新建Node项目主文件app.js,并添加代码如下: 01 var express = require('express'); 02 var app = express(); 03 04 app.get('/', function (req, res) { 05 res.send('Hello World!'); 06 }); 07 08 app.listen(8000, function () { 09 console.log('Server running at http://127.0.0.1:8000/') 10 }); (5)启动Node服务,命令如下: node app.js Server running at http://127.0.0.1:8000/ 此时,打开浏览器访问http://localhost:8000,页面上出现“Hello World!”,项目初始化完成。 2.服务端接口 下面在服务端程序的基础上开发待办事项的增、删、改、查接口。 为了简化接口和实现步骤,这里将服务端的数据直接编写在代码中,而不使用数据库存储。修改todo-list-server中的app.js代码如下: 01 var express = require('express'); 02 var app = express(); 03 04 var todoItems = [ 05 { id: 0, value: 'React', done: false, delete: false } 06 ] 07 08 app.get('/items', function (req, res) { 09 res.send(todoItems); 10 }); 11 12 app.listen(8000, function () { 13 console.log('Server running at http://127.0.0.1:8000/') 14 }); 此时,在浏览器中打开http://localhost:8000/items,会返回如图1.10所示的结果。 图1.10 获取待办事项接口 3.前端 完成接口之后,还需要修改前端(React端)逻辑,调用该接口获取待办事项的数据。 (1)基于上一节React前端,创建前端项目如下: cp -R todo-list todo-list-client cd todo-list-client npm install (2)使用包管理器引入一个基于Promise的HTTP库——axios(https://github.com/axios/ axios),它可以运行在浏览器和Node环境中,使***可以很容易地发送HTTP请求。具体引入命令如下: npm install --save axios (3)修改todo-list-client项目中的./src/App.js文件代码如下: 01 import React from 'react'; 02 import axios from 'axios'; 03 import TodoForm from './TodoForm'; 04 import TodoList from './TodoList'; 05 06 export default class App extends React.Component { 07 constructor(props) { 08 super(props); 09 this.state = { 10 todoItems: [] 11 } 12 } 13 14 componentDidMount() { 15 const that = this; 16 axios.get('http://localhost:8000/items') 17 .then(function (response) { 18 that.setState({ 19 todoItems: [...response.data] 20 }) 21 }) 22 } 23 24 // 省略了未修改的代码 25 } 此时,运行todo-list-client项目会发现浏览器报错,报错信息如图1.11所示。 图1.11 浏览器报错信息 这是因为浏览器跨域限制,解决方法是在todo-list-server项目中的app.js文件中添加以下代码: 01 // 省略了未修改的代码 02 03 app.all('*', function (req, res, next) { 04 // 允许跨域的域名,*代表允许任意域名跨域 05 res.header('Access-Control-Allow-Origin', '*'); 06 // 允许跨域的请求头 07 res.header('Access-Control-Allow-Headers', 'content-type'); 08 // 允许跨域的请求方法 09 res.header('Access-Control-Allow-Methods', 'DELETE,PUT,POST,GET, OPTIONS'); 10 next(); 11 }) 12 13 app.get('/items', function (req, res) { 14 res.send(todoItems); 15 }); 16 17 // 省略了未修改的代码 (4)重新运行Node服务后刷新浏览器,发现错误已解决并且成功获取到待办事项的数据。 %提示:关于跨域限制问题,将在第7章中详细介绍。 (5)完成了查询接口后,新增、删除和修改待办事项就很容易理解了。需要注意的是: l 新增接口为POST请求; l 删除接口为DELETE请求; l 修改接口为PATCH请求。 %提示:上述接口规范属于RESTful架构风格,将在第3章中详细介绍。 修改todo-list-server项目中的app.js文件的代码如下: 01 // 省略了未修改的代码 02 03 app.get('/items', function (req, res) { 04 res.send(todoItems); 05 }); 06 07 app.post('/items', function (req, res) { 08 if (req.body.todoItem) { 09 todoItems = [...todoItems, req.body.todoItem] 10 } 11 res.send(todoItems); 12 }) 13 14 app.delete('/items', function (req, res) { 15 if (req.body.id) { 16 todoItems.forEach(todoItem => { 17 if (todoItem.id === req.body.id) { 18 todoItem.delete = true; 19 } 20 }) 21 } 22 res.send(todoItems); 23 }) 24 25 // 省略了未修改的代码 其中,POST和DELETE请求的数据都在请求体中,所以需要通过req.body获取请求传递的内容。 %提示:关于HTTP的请求头和请求体,将在第3章中详细介绍。 修改todo-list-client项目中的./src/App.js文件以调用上述接口,代码如下: 01 addTodoItem = (todoItemValue) => { 02 const newTodoItem = { 03 id: this.state.todoItems.length, 04 value: todoItemValue, 05 done: false, 06 delete: false 07 }; 08 const that = this; 09 axios.post('http://localhost:3000/item-add', { 10 todoItem: newTodoItem 11 }) 12 .then(function (response) { 13 that.setState({ 14 todoItems: [...response.data] 15 }) 16 }) 17 } 18 19 deleteTodoItem = (item) => { 20 const that = this; 21 axios.delete('http://localhost:3000/item-delete', { 22 data: { 23 id: item.id 24 } 25 }) 26 .then(function (response) { 27 that.setState({ 28 todoItems: [...response.data] 29 }) 30 }) 31 } (6)重新运行Node服务,但是接口并没有如预期那样生效。通过在todo-list-server项目的app.js文件中做调试打印发现,req.body.*为未定义,那么,该如何解决呢? 这是因为需要通过依赖包body-parser来完成请求体的解析。解决方法是首先在todo- list-server项目中运行以下命令: npm install --save body-parser 然后在todo-list-server项目的app.js文件中添加以下代码: 01 var express = require('express'); 02 var bodyParser = require('body-parser'); 03 var app = express(); 04 05 app.use(bodyParser.json()); 06 07 var todoItems = [ 08 { id: 0, value: 'React', done: false, delete: false } 09 ] 10 11 // 省略了未修改的代码 依赖包body-parser的作用就是对POST和DELETE请求的请求体进行解析。 (7)再次运行Node服务,然后刷新浏览器,即可进行新增和删除操作。 另外,修改待办事项与新增、删除待办事项的写法类似,可参考前面的React示例,这里不再赘述,留给读者自行练习。