原文连接javascript
接触过前端的应该都有听过GraphQLhtml
简单来讲就是前端自行定义接口所须要返回的数据, 想要尝试的能够试着调用GithubAPI V4.前端
而对于咱们经常使用的xhr
请求可否也作到跟GraphQL
同样能自定义接口返回的数据?vue
答案是能够, 可是提早必须是后端必须提供足够的数据让前端自行选择.java
假设目先后端定义了一个User
模型, 包含了十几项数据ios
// Example
class User {
id,
username,
created,
updated,
// .. 省略好几我的
}
复制代码
任何接口若是有涉及到拿User
数据的, 都会把该User
的数据全量返回, 也就是说前端能从接口中拿到User
相关的十多项数据.git
但实际上并非每一个接口都须要这么多数据, 可能部分接口咱们只须要用到username
和id
. 但对于后端来讲, 他们只管写经过逻辑, 而不去管UI
上须要哪些数据.github
这样一来, 每一个接口都有可能返回大量无用的数据, 若是数据嵌套过深, 极端状况可能有上兆的数据.vuex
所以前端须要作到像GraphQL
同样可以自行定义所需的数据. (前提仍是须要后端支持)typescript
若是后端是用JAVA
开发, 那么可使用squiggly来支持前端数据自定义
根据这个库的介绍, 能够经过自定义filter
形式来过滤掉JAVA
类中数据的输出
用javascript
以及上面的User
做为例子的话, 假设咱们的filter
是username,id
, 那么当咱们log(User)
时候只会输出username
和id
两个数据, 其余都被过滤掉
固然还支持其余过滤方式, 但下面都是以精确匹配方式来完成数据定义
直接在请求中带上自定义请求头, 值设为所须要返回的字段
const fileds = 'name,user.username,user.id'
axios.request({
url: '/example',
headers: {
fields
}
})
复制代码
这样后端返回的字段只有
{
"name": "",
"user": {
"username": "",
"id": ""
}
}
复制代码
这种方法存在弊端
fileds
会很麻烦fields
不利于复用fields
中定义的字段没法反应到response
中基于上面的问题, 我所期待的效果应该以下:
fileds
fields
易于继承和扩展fileds
同时能定义其类型, 而且反应到response
上解决上面上个问题能够从两个方法入手
fileds
typescript
完成类型定义彷佛只用typescript
+ interface
就能很好的解决上述功能
定义类型
interface ResData {
name: string,
user: {
username: string
id: number
}
}
复制代码
借助ts
能够很容易定义一个类型, 只要把它赋值给axios
就能很容易定义response
接下来只须要想办法把interface
转成字符串
但其实类型和字符串是两个层面的东西, 类型属于ts
, 而字符串是实实在在的js
变量, 将两个层面链接一块儿的通道其实就是AST
, 咱们能够经过解析ts
语法, 经过transform
转成js
代码
因而乎发现了一个ttypescript, 能够自行实现transformer
来完成编译, 同时发现了一个很合适的transformer
而这篇文章总体思路跟我都是很类似, 这里就不在展开
可是说下这个方法的一些弊端
最终要达到的目的其实就是: 定义字段同时定义返回类型, 而上面的方法是从ts
层面出发, 咱们能够试着从js
层面出发, 利用ts
的类型推到功能完成
举个例子
const a = {
name: '',
user: {
username: '',
id: 1
}
}
type A = typeof a
复制代码
借助ts
的类型推到能够很容易得出
type A = {
name: string;
user: {
username: string;
id: number;
};
}
复制代码
有了这个例子, 咱们就能够很容易完成咱们的目标
const NumberType = 1 // type: number
const StringType = '' // type: string
const BooleanType = true // type: boolean
const AnyType = '' as any // type: any
const a = {
name: StringType,
user: {
username: StringType,
id: NumberType
}
}
const b = {
key1: BooleanType,
key2: {
key3: {
key4: {
key5: NumberType
}
}
}
}
复制代码
经过定义变量+类型推导就能很轻松完成fileds
的定义
render
方法做用其实就是将上面定义好的变量转成字符串形式的fields
function render(arg) {
// 实现方法其实很简单, 就是遍历object输出key
// 遇到nested或者array就递归
}
复制代码
这时候咱们能够这样
const fileds = render(a)
axios.request<typeof a>({
url: '/example',
headers: {
fields
}
})
复制代码
到这里其实就达到了最终的目标定义fileds同时定义返回类型
可是目前这样维护起来不太容易, 咱们须要继承以及更多的类型支持
继承的目标就是在已有的fileds
上继续扩展, Object.assign
就能知足
但assign
自己是不带类型的, 所以须要给他加入类型以便ts
进行类型推导
// 最简单的继承
function extend(t0, ...args) {
return Object.assign({}, t0, ...args)
}
复制代码
剩下要作的只须要对它进行重载以知足类型推导
// 举个例子
// 咱们只须要使用泛型来重载它的输入和输入类型
export function extend<T0 extends Record<string, any>, T1>( t: T0, u: T1, ): {
[P in keyof (T0 & T1)]: (T0 & T1)[P]
}
function extend(t0, ...args) {
return Object.assign({}, t0, ...args)
}
const a = extend({a: 1}, {c: ''})
type A = typeof a
// A = { a: number, c: string }
复制代码
在typescript
还有高级类型好比pick
, omit
, union
等
要实现他们, 原理跟继承同样, 都通泛型以及重载实现
// 再举个例子
function constant<T extends string | number>(arg: T): T {
return arg
}
const a = constant(1)
type A = typeof a
// A = 1, 而不是number
复制代码
const A = {
name: StringType
}
const B = {
user: {
username: StringType,
id: NumberType
}
}
const C = extend({
c: BooleanType
}, A, B)
type TypeC = typeof c
// { name: string, user: { username: string, id: number }, c: boolean}
const D = pick(C, ['user'])
type TypeD = typeof D
// { user: { username: string, id: number } }
const E = omit(C, ['user'])
type TypeE = typeof E
// { name: string, c: boolean }
复制代码
经过一系列的辅助方法, 就能够很好的达到咱们的目的: 定义fileds
同时定义类型
const A = {
name: StringType
}
const fileds = render(A)
axios.request<typeof A>({
url: '/example',
headers: {
fields
}
})
复制代码
仍是借用了泛型+类型推导
function render(arg: any) {}
function request<T>(fieldsDeclare: T, url) {
const fields = render(fieldsDeclare)
// 在这里借用了类型推导
return Axios.request<T>({
url,
headers: {
fields
}
})
}
const A = {
name: ''
}
request(A, '').then(r => {
r.data // typeof A { name: string }
r.data.name // string
})
复制代码
有了以上基础,其实要实现真正的GraphQL
也是能够的,只须要实现render
方法便可。
基于ts
的泛型+类型推导其实能实现不少强大的功能,好比vuex-ts-enhance,就是借助泛型+类型推导,完成了vuex
中mapXXX
方法的类型推导,有兴趣能够试用下。