原文連接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
方法的類型推導,有興趣能夠試用下。