前言:
在小程序中使用 graphql 相对来讲是一个小众的需求,并且在 Taro 中就更少一些,但对我们来讲却是一个必需要解决的问题。由于今年基础服务端的技术全面升级,已经都切换到基于 graphql api 实现上面,所以新的小程序端就需要完全支持 grapqhl api的实现。
选型
小程序选型
首先是小程序端选型的问题,我们今年以前的所有小程序都是原生+uni来实现的,再早一点也用到过 wepy,但主要还是 uni。但今年由于 vue3 的到来和对于 typescript的应用,我们需要一个能对 typescript + vue3支持较好的小程序方案。现在市面对于这个需求支持最好的就是 taro3 了。
Graphql client 库选型
Taro 做为小程序的实现是比较满足需求的,但是 taro3 官方并没有针对 graphql 支持,而社区中主要还是基于 @apollo 的库方案比较多一些,还有一些直接是基于 post 请求来实现的,但是整体来讲方案都比较简陋,或者有一定兼容问题。graphql client实现是有一套规范标准,并且针对使用复合API编写响应式查询/变量、缓存还是要有一定支持才能体现 graphql 的强大。
经过反复选型和试验,市面能支持我们需求(Vue3+typescript+完善的 graphql 实现)的最终有两个库可选:
-
URQL
用于React、Svelte、Vue或JavaScript的高度可定制和通用的GraphQL客户端,这个 graphql 最初实现是基于 react 端的,后期已经对各流行的库有了完善支持 https://formidable.com/open-source/urql/
-
Villus
这是一个小而快速的GraphQL客户端,对 vue3 有完善的支持。https://villus.logaretm.com/
实现
以上两个库的网络都是基于 fetch 来实现的,所以直接导入进 taro3 工程是没有办法实现小程序端网络请求的。小程序会有自己的 wx-request,taro 也是封装了请求而已。所以我们要做一项工作就是实现一个 "fetch" 接口来适配。
URQL这个库经过适配编译会出现异常,并且包较大一些不太适配,最终选用的是 villus 直接将源码引入到 taro 工程中,结构如下:
├── 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
我们只需要对 fetch.ts 进行改造,加入一个 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
);
}
...
重点是把原来的 await fetch(...)
改造为 Taro.request(...)
这样一个适配就使我们引入了一个完善的 grapqhl 客户端。
应用
1. 实现 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 式请求
3. auth.ts API请求文件
/**
* 开始登录
*/
static async doLogin() {
// 取得微信 code
const {code} = await this.wxLogin()
return client.executeMutation({
query: Login,
variables: {
code
}
})
}
响应式请求
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
}
},
...
客户端测试
总结
此次文章中记录了 taro3 + vue3 + graphql 的整合方案,评估了 URQL和Villus两套方案,最终选用 Villus 的改造方案,完成了整套技术的结合,并最终在商业应用中完美的使用。希望对有在小程序中使用 grahql 的朋友有所帮助。