React Express
仅仅有了 API ,功能还不完全。需要有前端来调用 API ,完成整个小功能。
注意:前端和后端是完全独立的两个项目,通过 API 来作为桥梁
所以
$ cd Desktop
$ mkdir express-react-demo
$ cd express-react-demo
$ mkdir express-backend
$ mkdir react-frontend
前端项目准备
先定个小目标
- 页面最终显示出来自己的用户名,例如 fightingljm
- 要用到 react 的 state ,constructor ,生命周期
- 用 ES6/Babel/Webpack
啦啦啦:创建 React 应用的脚手架项目可以推荐
点我看脚手架
不过这里我们自己走一遍
$ cd react-frontend
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
class App extends Component {
constructor(){
super();
this.state = {
username: ''
};
}
componentWillMount() {
this.setState({username: 'fightingljm'});
}
//用生命周期函数 componentWillMount 对 username 进行了覆盖,此乃妙计
//后面结合 axios 向后台请求数据的代码,就会比较容易看出作用了
render(){
return(
<div>
{this.state.username}
</div>
)
}
}
ReactDOM.render(<App/>,document.getElementById('app'));
package.json
{
"name": "react-frontend",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "./node_modules/.bin/webpack && http-server ."
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"babel-core": "^6.23.1",
"babel-loader": "^6.4.0",
"babel-preset-env": "^1.2.1",
"babel-preset-react": "^6.23.0",
"react": "^15.4.2",
"react-dom": "^15.4.2",
"webpack": "^2.2.1"
}
}
webpack.config.js
var path = require('path');
module.exports = {
entry: path.resolve(__dirname, 'src/index.js'),
output: {
path: path.resolve(__dirname, 'build'),
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.js$/,
use: 'babel-loader'
}
]
}
};
.babelrc
{
"presets": ["env", "react"]
}
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app"></div>
<script src="./build/bundle.js" charset="utf-8"></script>
</body>
</html>
目标完成 ^_^
。下一步:
安装 http-server
前端代码最好也跑在一个 http 服务器之上,最简单的方法是:
$ npm install -g http-server
这样我们就拥有一个简单的系统工具叫 http-server
$ cd react-frontend
$ http-server .
注意:要生成 /build/bundel.js 才可以执行代码,这里我把两条命令绑到了一块儿,如下所示
"scripts": {
"build": "./node_modules/.bin/webpack && http-server ."
}
这里我们只需要执行
$ npm run build
下面来看如何发请求到服务器端
开发一个功能好的做法是, 先修改后端代码,也就是实现 API => 然后用 curl 这样的命令来测试以下 API ,发现妥了再进行下一步 => write yourself front-end code
来来来,一起见证奇迹
实现 API
$ cd express-backend
$ atom .
index.js
const express = require('express');
const app = express();
app.get('/username',function (req,res) {
res.send({username:'fightingljm'})
})
app.listen(3000,function () {
console.log('running on port 3000...');
})
用 curl 来测试 API
curl 是一个安装在系统上的命令,可以用来发 http 请求,最适合用来测试 API
当然测试之前要保证后端代码是跑起来的,否则会报以下错误
Failed to connect to localhost port 3000: Connection refused
即 连接 localhost:3000 失败,连接被拒绝 . 错误原因是 : 后端没有启动
所以我们要先执行 npm run build
,这样才能保证在任何位置都可以请求到
$ curl -X GET 'http://localhost:3000/username'
如果后端没有问题,应该可以看到下面的输出
{username: 'fightingljm'}
安装 axios ,发送 http 请求
现在我们来到前端代码中引入 axios . 按照 axios 官网的说法,它是一个 http client ( http 的客户端),换句话说,它是专门用来发 http 请求的。
axios 是常用的发 http 请求的工具(现在一般不提发 ajax 请求这个说法了)。
首先来进行装包:
$ npm install --save axios
把 axios 安装到 react-frontend 这个项目中。
装包之后,就可以到 src/index.js 中去使用了,代码如下
import axios from 'axios';
我们当前的请求不希望是通过按钮来触发,而是希望,页面加载的时候,自动发出 http 请求,向服务要数据,所以,代码非常适合写到生命周期函数中:
componentWillMount() {
axios.get('http://localhost:3000/username').then(function(response){
return console.log(response);
}
)
}
问题随之而来,当我们在浏览器中打开时,会发现如下错误
XMLHttpRequest cannot load http://localhost:3000/. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access.
XMLHttpRequest 是发 HTTP 请求的底层机制,是浏览器自带功能。上面的意思是
无法加载后台 http://localhost:3000 . 被请求的资源中没有设置 Access-Control-Allow-Origin 头部。源头设置为 Null ,所以不允许 访问。
Access-Control-Allow-Origin
字面意思:允许来源访问控制。服务器上的默认是不允许其他网址(或者网址相同,但是端口号不同)的网站请求资源的,如果需要开通权限,就需要设置这个选项。
那么,如何开通服务器上的这个资源访问权限呢?就是要在 服务器 上做下面的设置
Access-Control-Allow-Origin: *
有了这个设置,所有的第三方网站都可以访问服务器上的资源了。
跨域请求的解决方案
解决方案采用: https://github.com/expressjs/cors
cors (Cross Origin Resource Share –跨域请求资源分享) ,安装了这个包就可以完成 Access-Control-Allow-Origin: *
这个设置了。
首先我们来瞅瞅在没有装包之前, Access-Control-Allow-Origin 的设置
$ curl -I http://localhost:3000/
curl 的 -I 选项用来专门拿到服务器返回的 header 。命令返回的信息,就是服务器端被请求资源的的 header 。
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 11
ETag: W/"b-sQqNsWTgdUEFt6mb5y4/5Q"
Date: Thu, 08 Dec 2016 01:51:44 GMT
Connection: keep-alive
上面是返回的结果,很明显是没有 Access-Control-Allow-Origin 这一项的。即为 null ,所以不能实现跨域资源共享.
下面我们就来安装 cors 包解决这个问题,点我 可以看到装包命令如下:
$ npm install --save cors
注意:这个包要安装到后台代码中。
然后按照文档,在后台代码 index.js 添加下面两行代码:
const express = require('express');
const app = express();
+ const cors = require('cors')
+ app.use(cors());
app.get('/username',function (req,res) {
res.send({username:'fightingljm'})
})
app.listen(3000,function () {
console.log('running on port 3000...');
})
保证代码是运行状态,再用 curl -I http://localhost:3000/username
看看输出:
HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: *
Content-Type: text/html; charset=utf-8
Content-Length: 11
ETag: W/"b-sQqNsWTgdUEFt6mb5y4/5Q"
Date: Thu, 08 Dec 2016 02:17:23 GMT
Connection: keep-alive
快看,是 Access-Control-Allow-Origin: *
。
浏览器中,刷新一下,可以看到后台返回的 response 数据了。错误没有了。同时我们发现在后台输出的 object 下的 data 下的 username 正是存储了我们的用户名.
那下一步就让我们在浏览器把他整出来…
调整接口数据格式
修改前台代码,调整 componentWillMount ,如下:
componentWillMount() {
axios.get('http://localhost:3000/username').then(function(response){
return console.log(response.data.username);
})
}
这样,前台浏览器的 console 中,就可以看到返回的数据 fightingljm
进一步靠近我们的目标,在浏览器显示,下面进一步调整 componentWillMount 如下:
componentWillMount() {
axios.get('http://localhost:3000/username').then((response) => {
this.setState({username: response.data.username});
})
}
注意:以上一定要写成箭头函数,否则 this 找不到
至此,前台页面上成功显示出了,后台的数据。这样,一个前后端分离架构,通过 API 通信的应用,我们就完成了。