编写基础请求代码
# 编写基础请求代码
我们这节课开始编写 ts-axios 库,我们的目标是实现简单的发送请求功能,即客户端通过 XMLHttpRequest 对象把请求发送到 server 端,server 端能收到请求并响应即可。
我们实现 axios 最基本的操作,通过传入一个对象发送请求,如下:
axios({
method: 'get',
url: '/simple/get',
params: {
a: 1,
b: 2
}
})
2
3
4
5
6
7
8
# 创建入口文件
我们删除 src 目录下的文件,先创建一个 index.ts 文件,作为整个库的入口文件,然后我们先定义一个 axios 方法,并把它导出,如下:
function axios(config) {
}
export default axios
2
3
4
5
6
7
这里 TypeScript 编译器会检查到错误,分别是 config 的声明上有隐含的 any 报错,以及代码块为空。代码块为空我们比较好理解,第一个错误的原因是因为我们给 TypeScript 编译配置的 strict 设置为 true 导致。
# 编译配置文件 tsconfig.json
tsconfig.json 文件中指定了用来编译这个项目的根文件和编译选项,关于它的具体学习,我希望同学们去官网 (opens new window)系统学习一下
我们在之前讲 TypeScript 的基础时,会运行 tsc 命令去编译 TypeScript 文件,编译器会从当前目录开始去查找 tsconfig.json 文件,作为编译时的一些编译选项。
我们来看一下 tsconfig.json 文件,它包含了很多编译时的配置,其中我们把 strict 设置为 true,它相当于启用所有严格类型的检查选项。启用 --strict 相当于启用 --noImplicitAny,--noImplicitThis,--alwaysStrict,--strictNullChecks 和 --strictFunctionTypes 和 --strictPropertyInitialization。
我们讲 TypeScript 的基础时提到了 --strictNullChecks,其它的编译配置我建议同学们都去查看它的官网文档 (opens new window),把它当做手册去查阅即可。
# 定义 AxiosRequestConfig 接口类型
接下来,我们需要给 config 参数定义一种接口类型。我们创建一个 types 目录,在下面创建一个 index.ts 文件,作为我们项目中公用的类型定义文件。
接下来我们来定义 AxiosRequestConfig 接口类型:
export interface AxiosRequestConfig {
url: string
method?: string
data?: any
params?: any
}
2
3
4
5
6
其中,url 为请求的地址,必选属性;而其余属性都是可选属性。method 是请求的 HTTP 方法;data 是 post、patch 等类型请求的数据,放到 request body 中的;params 是 get、head 等类型请求的数据,拼接到 url 的 query string 中的。
为了让 method 只能传入合法的字符串,我们定义一种字符串字面量类型 Method:
export type Method = 'get' | 'GET'
| 'delete' | 'Delete'
| 'head' | 'HEAD'
| 'options' | 'OPTIONS'
| 'post' | 'POST'
| 'put' | 'PUT'
| 'patch' | 'PATCH'
2
3
4
5
6
7
接着我们把 AxiosRequestConfig 中的 method 属性类型改成这种字符串字面量类型:
export interface AxiosRequestConfig {
url: string
method?: Method
data?: any
params?: any
}
2
3
4
5
6
然后回到 index.ts,我们引入 AxiosRequestConfig 类型,作为 config 的参数类型,如下:
import { AxiosRequestConfig } from './types'
function axios(config: AxiosRequestConfig) {
}
export default axios
2
3
4
5
6
那么接下来,我们就来实现这个函数体内部的逻辑——发送请求。
# 利用 XMLHttpRequest 发送请求
我们并不想在 index.ts 中去实现发送请求的逻辑,我们利用模块化的编程思想,把这个功能拆分到一个单独的模块中。
于是我们在 src 目录下创建一个 xhr.ts 文件,我们导出一个 xhr 方法,它接受一个 config 参数,类型也是 AxiosRequestConfig 类型。
import { AxiosRequestConfig } from './types'
export default function xhr(config: AxiosRequestConfig) {
}
2
3
4
接下来,我们来实现这个函数体逻辑,如下:
export default function xhr(config: AxiosRequestConfig): void {
const { data = null, url, method = 'get' } = config
const request = new XMLHttpRequest()
request.open(method.toUpperCase(), url, true)
request.send(data)
}
2
3
4
5
6
7
8
9
我们首先通过解构赋值的语法从 config 中拿到对应的属性值赋值给我的变量,并且还定义了一些默认值,因为在 AxiosRequestConfig 接口的定义中,有些属性是可选的。
接着我们实例化了一个 XMLHttpRequest 对象,然后调用了它的 open 方法,传入了对应的一些参数,最后调用 send 方法发送请求。
对于 XMLHttpRequest 的学习,我希望同学们去 mdn (opens new window) 上系统地学习一下它的一些属性和方法,当做参考资料,因为在后续的开发中我们可能会反复查阅这些文档资料。
# 引入 xhr 模块
编写好了 xhr 模块,我们就需要在 index.ts 中去引入这个模块,如下:
import { AxiosRequestConfig } from './types'
import xhr from './xhr'
function axios(config: AxiosRequestConfig): void {
xhr(config)
}
export default axios
2
3
4
5
6
7
8
那么至此,我们基本的发送请求代码就编写完毕了,接下来我们来写一个小 demo,来使用我们编写的 axios 库去发送请求。
# demo 编写
我们会利用 Node.js 的 express (opens new window) 库去运行我们的 demo,利用 webpack (opens new window) 来作为 demo 的构建工具。
# 依赖安装
我们先来安装一些编写 demo 需要的依赖包,如下:
"webpack": "^4.28.4",
"webpack-dev-middleware": "^3.5.0",
"webpack-hot-middleware": "^2.24.3",
"ts-loader": "^5.3.3",
"tslint-loader": "^3.5.4",
"express": "^4.16.4",
"body-parser": "^1.18.3"
2
3
4
5
6
7
其中,webpack 是打包构建工具,webpack-dev-middleware 和 webpack-hot-middleware 是 2 个 express 的 webpack 中间件,ts-loader 和 tslint-loader 是 webpack 需要的 TypeScript 相关 loader,express 是 Node.js 的服务端框架,body-parser 是 express 的一个中间件,解析 body 数据用的。
# 编写 webpack 配置文件
在 examples 目录下创建 webpack 配置文件 webpack.config.js:
const fs = require('fs')
const path = require('path')
const webpack = require('webpack')
module.exports = {
mode: 'development',
/**
* 我们会在 examples 目录下建多个子目录
* 我们会把不同章节的 demo 放到不同的子目录中
* 每个子目录的下会创建一个 app.ts
* app.ts 作为 webpack 构建的入口文件
* entries 收集了多目录个入口文件,并且每个入口还引入了一个用于热更新的文件
* entries 是一个对象,key 为目录名
*/
entry: fs.readdirSync(__dirname).reduce((entries, dir) => {
const fullDir = path.join(__dirname, dir)
const entry = path.join(fullDir, 'app.ts')
if (fs.statSync(fullDir).isDirectory() && fs.existsSync(entry)) {
entries[dir] = ['webpack-hot-middleware/client', entry]
}
return entries
}, {}),
/**
* 根据不同的目录名称,打包生成目标 js,名称和目录名一致
*/
output: {
path: path.join(__dirname, '__build__'),
filename: '[name].js',
publicPath: '/__build__/'
},
module: {
rules: [
{
test: /\.ts$/,
enforce: 'pre',
use: [
{
loader: 'tslint-loader'
}
]
},
{
test: /\.tsx?$/,
use: [
{
loader: 'ts-loader',
options: {
transpileOnly: true
}
}
]
}
]
},
resolve: {
extensions: ['.ts', '.tsx', '.js']
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin()
]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# 编写 server 文件
在 examples 目录下创建 server.js 文件:
const express = require('express')
const bodyParser = require('body-parser')
const webpack = require('webpack')
const webpackDevMiddleware = require('webpack-dev-middleware')
const webpackHotMiddleware = require('webpack-hot-middleware')
const WebpackConfig = require('./webpack.config')
const app = express()
const compiler = webpack(WebpackConfig)
app.use(webpackDevMiddleware(compiler, {
publicPath: '/__build__/',
stats: {
colors: true,
chunks: false
}
}))
app.use(webpackHotMiddleware(compiler))
app.use(express.static(__dirname))
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))
const port = process.env.PORT || 8080
module.exports = app.listen(port, () => {
console.log(`Server listening on http://localhost:${port}, Ctrl+C to stop`)
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 编写 demo 代码
首先在 examples 目录下创建 index.html 和 global.css,作为所有 demo 的入口文件已全局样式文件。
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>ts-axios examples</title>
<link rel="stylesheet" href="/global.css">
</head>
<body style="padding: 0 20px">
<h1>ts-axios examples</h1>
<ul>
<li><a href="simple">Simple</a></li>
</ul>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
global.css:
html, body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
color: #2c3e50;
}
ul {
line-height: 1.5em;
padding-left: 1.5em;
}
a {
color: #7f8c8d;
text-decoration: none;
}
a:hover {
color: #4fc08d;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
然后在 examples 目录下创建 simple 目录,作为本章节的 demo 目录,在该目录下再创建 index.html 和 app.ts 文件
index.html 文件如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Simple example</title>
</head>
<body>
<script src="/__build__/simple.js"></script>
</body>
</html>
2
3
4
5
6
7
8
9
10
app.ts 文件如下:
import axios from '../../src/index'
axios({
method: 'get',
url: '/simple/get',
params: {
a: 1,
b: 2
}
})
2
3
4
5
6
7
8
9
10
因为我们这里通过 axios 发送了请求,那么我们的 server 端要实现对应的路由接口,我们来修改 server.js,添加如下代码:
const router = express.Router()
router.get('/simple/get', function(req, res) {
res.json({
msg: `hello world`
})
})
app.use(router)
2
3
4
5
6
7
8
9
# 运行 demo
接着我们在 package.json 中去新增一个 npm script:
"dev": "node examples/server.js"
然后我们去控制台执行命令
npm run dev
相当于执行了 node examples/server.js,会开启我们的 server。
接着我们打开 chrome 浏览器,访问 http://localhost:8080/ 即可访问我们的 demo 了,我们点到 Simple 目录下,通过开发者工具的 network 部分我们可以看到成功发送到了一条请求,并在 response 中看到了服务端返回的数据。
至此,我们就实现了一个简单的请求发送,并编写了相关的 demo。但是现在存在一些问题:我们传入的 params 数据并没有用,也没有拼接到 url 上;我们对 request body 的数据格式、请求头 headers 也没有做处理;另外我们虽然从网络层面收到了响应的数据,但是我们代码层面也并没有对响应的数据做处理。那么下面一章,我们就来解决这些问题。