Open zWingz opened 4 years ago
接触过前端的应该都有听过GraphQL
简单来说就是前端自行定义接口所需要返回的数据, 想要尝试的可以试着调用GithubAPI V4.
而对于我们常用的xhr请求能否也做到跟GraphQL一样能自定义接口返回的数据?
xhr
GraphQL
答案是可以, 但是提前必须是后端必须提供足够的数据让前端自行选择.
假设目前后端定义了一个User模型, 包含了十几项数据
User
// Example class User { id, username, created, updated, // .. 省略好几个人 }
任何接口如果有涉及到拿User数据的, 都会把该User的数据全量返回, 也就是说前端能从接口中拿到User相关的十多项数据.
但实际上并不是每个接口都需要这么多数据, 可能部分接口我们只需要用到username和id. 但对于后端来说, 他们只管写通过逻辑, 而不去管UI上需要哪些数据.
username
id
UI
这样一来, 每个接口都有可能返回大量无用的数据, 如果数据嵌套过深, 极端情况可能有上兆的数据.
因此前端需要做到像GraphQL一样能够自行定义所需的数据. (前提还是需要后端支持)
如果后端是用JAVA开发, 那么可以使用squiggly来支持前端数据自定义
JAVA
根据这个库的介绍, 可以通过自定义filter形式来过滤掉JAVA类中数据的输出
filter
用javascript以及上面的User作为例子的话, 假设我们的filter是username,id, 那么当我们log(User)时候只会输出username和id两个数据, 其他都被过滤掉
javascript
username,id
log(User)
当然还支持其他过滤方式, 但下面都是以精确匹配方式来完成数据定义
直接在请求中带上自定义请求头, 值设为所需要返回的字段
const fileds = 'name,user.username,user.id' axios.request({ url: '/example', headers: { fields } })
这样后端返回的字段只有
{ "name": "", "user": { "username": "", "id": "" } }
这种方法存在弊端
fileds
fields
response
基于上面的问题, 我所期待的效果应该如下:
解决上面上个问题可以从两个方法入手
typescript
似乎只用typescript + interface就能很好的解决上述功能
interface
定义类型
interface ResData { name: string, user: { username: string id: number } }
借助ts可以很容易定义一个类型, 只要把它赋值给axios就能很容易定义response
ts
axios
接下来只需要想办法把interface转成字符串
但其实类型和字符串是两个层面的东西, 类型属于ts, 而字符串是实实在在的js变量, 将两个层面连接一起的通道其实就是AST, 我们可以通过解析ts语法, 通过transform转成js代码
js
AST
transform
于是乎发现了一个ttypescript, 可以自行实现transformer来完成编译, 同时发现了一个很合适的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
render
function render(arg) { // 实现方法其实很简单, 就是遍历object输出key // 遇到nested或者array就递归 }
这时候我们可以这样
const fileds = render(a) axios.request<typeof a>({ url: '/example', headers: { fields } })
到这里其实就达到了最终的目标定义fileds同时定义返回类型
但是目前这样维护起来不太容易, 我们需要继承以及更多的类型支持
继承的目标就是在已有的fileds上继续扩展, Object.assign就能满足
Object.assign
但assign本身是不带类型的, 因此需要给他加入类型以便ts进行类型推导
assign
// 最简单的继承 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等
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方法的类型推导,有兴趣可以试用下。
vuex
mapXXX
接触过前端的应该都有听过GraphQL
简单来说就是前端自行定义接口所需要返回的数据, 想要尝试的可以试着调用GithubAPI V4.
而对于我们常用的
xhr
请求能否也做到跟GraphQL
一样能自定义接口返回的数据?答案是可以, 但是提前必须是后端必须提供足够的数据让前端自行选择.
例子
假设目前后端定义了一个
User
模型, 包含了十几项数据任何接口如果有涉及到拿
User
数据的, 都会把该User
的数据全量返回, 也就是说前端能从接口中拿到User
相关的十多项数据.但实际上并不是每个接口都需要这么多数据, 可能部分接口我们只需要用到
username
和id
. 但对于后端来说, 他们只管写通过逻辑, 而不去管UI
上需要哪些数据.这样一来, 每个接口都有可能返回大量无用的数据, 如果数据嵌套过深, 极端情况可能有上兆的数据.
因此前端需要做到像
GraphQL
一样能够自行定义所需的数据. (前提还是需要后端支持)Squiggly
如果后端是用
JAVA
开发, 那么可以使用squiggly来支持前端数据自定义根据这个库的介绍, 可以通过自定义
filter
形式来过滤掉JAVA
类中数据的输出用
javascript
以及上面的User
作为例子的话, 假设我们的filter
是username,id
, 那么当我们log(User)
时候只会输出username
和id
两个数据, 其他都被过滤掉当然还支持其他过滤方式, 但下面都是以精确匹配方式来完成数据定义
最简单粗暴的方式
直接在请求中带上自定义请求头, 值设为所需要返回的字段
这样后端返回的字段只有
这种方法存在弊端
fileds
会很麻烦fields
不利于复用fields
中定义的字段无法反应到response
中进一步改进
基于上面的问题, 我所期待的效果应该如下:
fileds
fields
易于继承和扩展fileds
同时能定义其类型, 并且反应到response
上解决上面上个问题可以从两个方法入手
fileds
typescript
完成类型定义似乎只用
typescript
+interface
就能很好的解决上述功能定义类型
借助
ts
可以很容易定义一个类型, 只要把它赋值给axios
就能很容易定义response
接下来只需要想办法把
interface
转成字符串但其实类型和字符串是两个层面的东西, 类型属于
ts
, 而字符串是实实在在的js
变量, 将两个层面连接一起的通道其实就是AST
, 我们可以通过解析ts
语法, 通过transform
转成js
代码于是乎发现了一个ttypescript, 可以自行实现
transformer
来完成编译, 同时发现了一个很合适的transformer而这篇文章整体思路跟我都是很相似, 这里就不在展开
但是说下这个方法的一些弊端
最终实现
定义fields以及类型
最终要达到的目的其实就是: 定义字段同时定义返回类型, 而上面的方法是从
ts
层面出发, 我们可以试着从js
层面出发, 利用ts
的类型推到功能完成举个例子
借助
ts
的类型推到可以很容易得出有了这个例子, 我们就可以很容易完成我们的目标
通过定义变量+类型推导就能很轻松完成
fileds
的定义实现render方法
render
方法作用其实就是将上面定义好的变量转成字符串形式的fields
这时候我们可以这样
到这里其实就达到了最终的目标定义fileds同时定义返回类型
但是目前这样维护起来不太容易, 我们需要继承以及更多的类型支持
继承
继承的目标就是在已有的
fileds
上继续扩展,Object.assign
就能满足但
assign
本身是不带类型的, 因此需要给他加入类型以便ts
进行类型推导剩下要做的只需要对它进行重载以满足类型推导
更多类型支持
在
typescript
还有高级类型比如pick
,omit
,union
等要实现他们, 原理跟继承一样, 都通泛型以及重载实现
组合使用
通过一系列的辅助方法, 就可以很好的达到我们的目的: 定义
fileds
同时定义类型配合axios使用
最粗暴的方式
更方便的方式
还是借用了泛型+类型推导
结语
有了以上基础,其实要实现真正的
GraphQL
也是可以的,只需要实现render
方法即可。基于
ts
的泛型+类型推导其实能实现很多强大的功能,比如vuex-ts-enhance,就是借助泛型+类型推导,完成了vuex
中mapXXX
方法的类型推导,有兴趣可以试用下。