前言:
在小程序中使用 graphql 相對(duì)來(lái)講是一個(gè)小眾的需求沛励,并且在 Taro 中就更少一些墩崩,但對(duì)我們來(lái)講卻是一個(gè)必需要解決的問(wèn)題。由于今年基礎(chǔ)服務(wù)端的技術(shù)全面升級(jí)侯勉,已經(jīng)都切換到基于 graphql api 實(shí)現(xiàn)上面鹦筹,所以新的小程序端就需要完全支持 grapqhl api的實(shí)現(xiàn)。
選型
小程序選型
首先是小程序端選型的問(wèn)題址貌,我們今年以前的所有小程序都是原生+uni來(lái)實(shí)現(xiàn)的铐拐,再早一點(diǎn)也用到過(guò) wepy,但主要還是 uni练对。但今年由于 vue3 的到來(lái)和對(duì)于 typescript的應(yīng)用遍蟋,我們需要一個(gè)能對(duì) typescript + vue3支持較好的小程序方案。現(xiàn)在市面對(duì)于這個(gè)需求支持最好的就是 taro3 了螟凭。
Graphql client 庫(kù)選型
Taro 做為小程序的實(shí)現(xiàn)是比較滿足需求的虚青,但是 taro3 官方并沒(méi)有針對(duì) graphql 支持,而社區(qū)中主要還是基于 @apollo 的庫(kù)方案比較多一些螺男,還有一些直接是基于 post 請(qǐng)求來(lái)實(shí)現(xiàn)的棒厘,但是整體來(lái)講方案都比較簡(jiǎn)陋,或者有一定兼容問(wèn)題下隧。graphql client實(shí)現(xiàn)是有一套規(guī)范標(biāo)準(zhǔn)奢人,并且針對(duì)使用復(fù)合API編寫響應(yīng)式查詢/變量、緩存還是要有一定支持才能體現(xiàn) graphql 的強(qiáng)大淆院。
經(jīng)過(guò)反復(fù)選型和試驗(yàn)何乎,市面能支持我們需求(Vue3+typescript+完善的 graphql 實(shí)現(xiàn))的最終有兩個(gè)庫(kù)可選:
-
URQL
urql
用于React、Svelte、Vue或JavaScript的高度可定制和通用的GraphQL客戶端支救,這個(gè) graphql 最初實(shí)現(xiàn)是基于 react 端的抢野,后期已經(jīng)對(duì)各流行的庫(kù)有了完善支持 https://formidable.com/open-source/urql/
-
Villus
villus
這是一個(gè)小而快速的GraphQL客戶端,對(duì) vue3 有完善的支持各墨。https://villus.logaretm.com/
實(shí)現(xiàn)
以上兩個(gè)庫(kù)的網(wǎng)絡(luò)都是基于 fetch 來(lái)實(shí)現(xiàn)的指孤,所以直接導(dǎo)入進(jìn) taro3 工程是沒(méi)有辦法實(shí)現(xiàn)小程序端網(wǎng)絡(luò)請(qǐng)求的。小程序會(huì)有自己的 wx-request欲主,taro 也是封裝了請(qǐng)求而已邓厕。所以我們要做一項(xiàng)工作就是實(shí)現(xiàn)一個(gè) "fetch" 接口來(lái)適配。
URQL這個(gè)庫(kù)經(jīng)過(guò)適配編譯會(huì)出現(xiàn)異常扁瓢,并且包較大一些不太適配详恼,最終選用的是 villus 直接將源碼引入到 taro 工程中,結(jié)構(gòu)如下:
├── villus
│ ├── Mutation.ts
│ ├── Provider.ts
│ ├── Query.ts
│ ├── Subscription.ts
│ ├── cache.ts
│ ├── client.ts
│ ├── dedup.ts
│ ├── fetch-taro.ts
│ ├── fetch.ts
│ ├── handleSubscriptions.ts
│ ├── helpers.ts
│ ├── index.ts
│ ├── shared
│ ├── symbols.ts
│ ├── types.ts
│ ├── useClient.ts
│ ├── useMutation.ts
│ ├── useQuery.ts
│ ├── useSubscription.ts
│ └── utils
└── wxcomponents
我們只需要對(duì) fetch.ts 進(jìn)行改造引几,加入一個(gè) fetch-taro.ts 的適配昧互,改造如下:
** fetch.ts 原始文件**
import { GraphQLError } from 'graphql';
import { ClientPlugin } from './types';
import { makeFetchOptions, resolveGlobalFetch, parseResponse } from './shared';
import { CombinedError } from './utils';
interface FetchPluginOpts {
fetch?: typeof window['fetch'];
}
export function fetch(opts?: FetchPluginOpts): ClientPlugin {
const fetch = opts?.fetch || resolveGlobalFetch();
if (!fetch) {
throw new Error('Could not resolve a fetch() method, you should provide one.');
}
return async function fetchPlugin(ctx) {
const { useResult, opContext, operation } = ctx;
const fetchOpts = makeFetchOptions(operation, opContext);
let response;
try {
response = await fetch(opContext.url as string, fetchOpts).then(parseResponse);
} catch (err) {
return useResult(
{
data: null,
error: new CombinedError({ response, networkError: err }),
},
true
);
}
...
fetch-taro.ts改造后文件
import {GraphQLError} from 'graphql';
import {ClientPlugin, CombinedError} from "./index";
import Taro from '@tarojs/taro';
import {makeFetchOptions} from "./shared";
export function fetch(): ClientPlugin {
return async function fetchPlugin(ctx) {
const {useResult, opContext, operation} = ctx;
const fetchOpts = makeFetchOptions(operation, opContext);
let response;
try {
const requestTask = Taro.request({
header: opContext.headers,
url: opContext.url as string,
method: 'POST',
data: fetchOpts.body,
dataType: "json",
})
response = await requestTask
} catch (err) {
return useResult(
{
data: null,
error: new CombinedError({response, networkError: err}),
},
true
);
}
...
重點(diǎn)是把原來(lái)的 await fetch(...)
改造為 Taro.request(...)
這樣一個(gè)適配就使我們引入了一個(gè)完善的 grapqhl 客戶端。
應(yīng)用
1. 實(shí)現(xiàn) graphql client 全局定義
import {createClient} from '../villus';
import global from "../utils/global";
import {fetch} from '../villus/fetch-taro'
import config from '../config'
function authPlugin({opContext}) {
opContext.headers.Authorization = 'Bearer ' + global.getToken();
}
export const client = createClient({
url: config.baseUrl + 'weapp-api',
use: [ authPlugin, fetch() ],
cachePolicy: 'network-only',
});
2. 定義 graphql 文件
import gql from 'graphql-tag';
export const Login = gql`
mutation WxLogin($code: String!){
wxLogin(code: $code){
user{
id
identifier
token
permissions
}
}
}
`
### API 式請(qǐng)求
3. auth.ts API請(qǐng)求文件
/**
* 開始登錄
*/
static async doLogin() {
// 取得微信 code
const {code} = await this.wxLogin()
return client.executeMutation({
query: Login,
variables: {
code
}
})
}
響應(yīng)式請(qǐng)求
import { useQuery } from "villus";
const FetchFood = `
query QueryFoods($operator: StringOperators!){
foods(options:{filter:{
food: $operator
}}){
items{
id
food
meta{
transform{
key
unit
}
}
}
totalItems
}
}
`;
import SearchView from '../../components/common-search/index'
export default {
components: {
SearchView
},
setup() {
const { execute, data, isFetching } = useQuery({
query: FetchFood,
variables: {
"operator": {
"contains": "豬肉"
}
}
})
return {
data
}
},
...
客戶端測(cè)試
總結(jié)
此次文章中記錄了 taro3 + vue3 + graphql 的整合方案伟桅,評(píng)估了 URQL和Villus兩套方案敞掘,最終選用 Villus 的改造方案,完成了整套技術(shù)的結(jié)合楣铁,并最終在商業(yè)應(yīng)用中完美的使用玖雁。希望對(duì)有在小程序中使用 grahql 的朋友有所幫助。