编写基础请求代码
# 编写基础请求代码
我们这节课开始编写 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
文件中指定了用来编译这个项目的根文件和编译选项,关于它的具体学习,我希望同学们去官网系统学习一下
我们在之前讲 TypeScript 的基础时,会运行 tsc
命令去编译 TypeScript 文件,编译器会从当前目录开始去查找 tsconfig.json
文件,作为编译时的一些编译选项。
我们来看一下 tsconfig.json 文件,它包含了很多编译时的配置,其中我们把 strict
设置为 true
,它相当于启用所有严格类型的检查选项。启用 --strict
相当于启用 --noImplicitAny
,--noImplicitThis
,--alwaysStrict
,--strictNullChecks
和 --strictFunctionTypes
和 --strictPropertyInitialization
。
我们讲 TypeScript 的基础时提到了 --strictNullChecks
,其它的编译配置我建议同学们都去查看它的官网文档,把它当做手册去查阅即可。
# 定义 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 上系统地学习一下它的一些属性和方法,当做参考资料,因为在后续的开发中我们可能会反复查阅这些文档资料。
# 引入 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
库去运行我们的 demo,利用 webpack
来作为 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 也没有做处理;另外我们虽然从网络层面收到了响应的数据,但是我们代码层面也并没有对响应的数据做处理。那么下面一章,我们就来解决这些问题。