在小程序框架Taro中使用 vue3+graphql

Frame 35.png

前言:

在小程序中使用 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 实现)的最终有两个库可选:

  1. URQL


    urql

用于React、Svelte、Vue或JavaScript的高度可定制和通用的GraphQL客户端,这个 graphql 最初实现是基于 react 端的,后期已经对各流行的库有了完善支持 https://formidable.com/open-source/urql/

  1. Villus


    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
      }
    },
...

客户端测试

image.png

总结

此次文章中记录了 taro3 + vue3 + graphql 的整合方案,评估了 URQL和Villus两套方案,最终选用 Villus 的改造方案,完成了整套技术的结合,并最终在商业应用中完美的使用。希望对有在小程序中使用 grahql 的朋友有所帮助。

你可能感兴趣的:(在小程序框架Taro中使用 vue3+graphql)