资讯专栏INFORMATION COLUMN

21 分钟学 apollo-client 系列:请求拦截和 FragmentMatcher

Eastboat / 2639人阅读

摘要:分钟学是一个系列,简单暴力,包学包会。接管了请求和状态管理。一般在生产环境中,我们通常还希望做权限验证请求拦截等事务处理。

21 分钟学 apollo-client 是一个系列,简单暴力,包学包会。

搭建 Apollo client 端,集成 redux
使用 apollo-client 来获取数据
修改本地的 apollo store 数据
提供定制方案

请求拦截

封装修改 client 的 api
apollo store 存储细节

写入 store 的失败原因分析和解决方案

我们已经搭建了最小化的 ApolloClient。

Apollo 接管了 api 请求和状态管理。一般在生产环境中,我们通常还希望做权限验证、请求拦截等事务处理。

于是,我们就需要通过 Apollo 提供的一些接口,结合自己的业务需求,定制自己的 Apollo。

网络层

GraphQL 入门: Apollo Client - 网络层

上面这篇文章中,介绍了 Apollo 网络层的基础知识,推荐阅读。我只是再做一些补充。

fetch

Apollo 使用的是 fetch api
从源码来看,apollo 代码中并没有包含 polyfill,也就是说,在低版本的浏览器上可能会因为缺少 fetch 而失败。所以你需要安装 isomorphic-fetch
另外,如果你碰到跨域等问题,也是 fetch 引起的,和 apollo 没什么关系。

const networkInterface = (createNetworkInterface({
    uri: "...",
    opts: {
        // fetch 相关的设置在这里配置
        credentials: "same-origin",
    },
}));
network middlewares

如果你使用过 Express 的话,就能很容易理解这个概念。否则你也可以理解为 middleware 就是请求的拦截器,可以在每个请求发送前或发送后,拦截请求,对其做一些修改,再决定是否传递,也可以直接取消。

所以你可以在这里

设置 headers

权限验证

取消请求

缓存请求

实践中,建议把所有的 middlewares 写到一个多带带的文件。
我个人比较喜欢写简化的代码,你也可以参考我进行配置。

middlewares.js

const wrapMiddlewares = (key) => (mws) => mws.map(mw => ({ [key]: mw }));
const wrapBeforewares = wrapMiddlewares("applyMiddleware");
const wrapAfterwares = wrapMiddlewares("applyAfterware");

// 可以看成一个没有冗余代码的配置表
export default function applyApolloMiddlewares(networkInterface) {
    networkInterface.use(wrapBeforewares([
        setHeaders,
    ]))
    .useAfter(wrapAfterwares([
        checkAuth,
    ]));

    return networkInterface;
}

// ---- 请求前 ----

function setHeaders(req, next) {
    try {
        if (!req.options.headers) {
            req.options.headers = {
                "Content-Type": "application/json;charset=utf-8",
            };
        }
    } catch (e) {
        console.error(e);
    }

    // 必须返回 next
    next();
}

// ---- 请求后 -----

function checkAuth({ response: res }, next) {
    try {
        // 自行处理 401 等各种 statusCode
    } catch (e) {
        console.error(e);
    }

    next();
}

建议对所有的 middlewares 包裹一层 try catch,因为我发现 apollo 这个库不能很好地报错,经常静默失败。

于是你的 createApolloClient 函数可以改写为

createApolloClient.js

import { ApolloClient, createNetworkInterface } from "react-apollo";
import applyApolloMiddlewares from "./middlewares";

const networkInterface = (createNetworkInterface({
    uri: "...",
    opts: {
        // fetch 相关的设置在这里配置
        credentials: "same-origin",
    },
}));

const client = new ApolloClient({
    networkInterface: applyApolloMiddlewares(networkInterface),
});

return client;
FragmentMatcher

正式请求中,如果你的 schema 包含了 union type,形如

content { # 类型为 Content
    ... on Article {
        id
        title
    }
    ... on Timeline {
        id
        content
    }
}

即根据返回数据的类型不同,抓取不同的数据,这在 GraphQL 中,使用的是 inline fragment 语法来解决。
而上面代码中的 Content 这个类,本身没有具体字段,但有许多子类,被称为 union type

可以看看 GraphQL 关于这一细节的描述:inline-fragments。

apollo 中使用一种启发式的类型搜索方式,不像后端已经存在所有的类型说明,前端为了类型安全,必须由你来告诉 apollo,该 union type 下到底有哪些子类

否则你可能会看到这样的报错

You are using the simple (heuristic) fragment matcher, but your queries contain union or interface types.
     Apollo Client will not be able to able to accurately map fragments

报错信息会指引你阅读这个文档,说的就是我上面描述的情况:Using Fragments on unions and interfaces

为了少写垃圾代码,我写了一些 helper,希望对你有用。

const createIntrospectionFactory = (kind) => (name, possibleTypes) => ({
    name,
    kind,
    possibleTypes: possibleTypes.map(typename => ({ name: typename })),
});

const createInterface = createIntrospectionFactory("INTERFACE");
const createUnion = createIntrospectionFactory("UNION");

export default {
    introspectionQueryResultData: {
        __schema: {
            types: [
                createInterface("Content", [
                    "Article",
                    "Timeline",
                ]),
            ]
        }
    }
};

所以你的 client 可以写成

import { ApolloClient, createNetworkInterface, IntrospectionFragmentMatcher } from "react-apollo";
import applyApolloMiddlewares from "./middlewares";
import fragmentMatcher from "./fragmentMatcher";

export default function createApolloClient() {
    const networkInterface = (createNetworkInterface({
        uri: "...",
        opts: {
            credentials: "same-origin",
        },
    }));

    const client = new ApolloClient({
        networkInterface: applyApolloMiddlewares(networkInterface),
        fragmentMatcher: new IntrospectionFragmentMatcher(fragmentMatcher),
    });

    return client;
}








文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/91923.html

相关文章

  • 21 分钟 apollo-client 系列:扩展 ApolloClient 的 api

    摘要:分钟学是一个系列,简单暴力,包学包会。那怎么办呢本章就教你非常简单地实现扩展的。我们可以借鉴的的写法,为的实例添加一些自己的方法。更重要的是,也会有的效果,上一个的输出会成为下一个的输入,便于组合。 21 分钟学 apollo-client 是一个系列,简单暴力,包学包会。 搭建 Apollo client 端,集成 redux使用 apollo-client 来获取数据修改本地的 ...

    levy9527 评论0 收藏0
  • 21 分钟 apollo-client 系列:apollo store 存储细节

    摘要:分钟学是一个系列,简单暴力,包学包会。内部通过自己的私有没有暴露给开发者来更新这个。相当于这个就是自己维护的,它将所有通过得到的数据保存在这里。的生成规则根据官方文档的说法,在创建时,可选设置。如果不存在,则可能出现。 21 分钟学 apollo-client 是一个系列,简单暴力,包学包会。 搭建 Apollo client 端,集成 redux使用 apollo-client 来...

    lavor 评论0 收藏0
  • 21 分钟 apollo-client 系列:简单搭建

    摘要:分钟学是一个系列,简单暴力,包学包会。其中提到了等需要后端配合的东西,徒增了配置的复杂性。如果不行,再跟随我的简单步骤试试。环境要求请确保你已经搭建了自己的环境下文在行号前添加表示删除的原代码,表示新增的代码。 21 分钟学 apollo-client 是一个系列,简单暴力,包学包会。 搭建 Apollo client 端,集成 redux使用 apollo-client 来获取数据...

    ranwu 评论0 收藏0
  • 21 分钟 apollo-client 系列:写入失败的原因解决方案

    摘要:分钟学是一个系列,简单暴力,包学包会。一旦你丢失了,可能会导致写入失败,或者尽管写入了,但本该携带的那一层的数据没有写入。 21 分钟学 apollo-client 是一个系列,简单暴力,包学包会。 搭建 Apollo client 端,集成 redux使用 apollo-client 来获取数据修改本地的 apollo store 数据提供定制方案 请求拦截 封装修改 clie...

    Baoyuan 评论0 收藏0
  • 21 分钟 apollo-client 系列:获取数据

    摘要:分钟学是一个系列,简单暴力,包学包会。一旦组件挂载后,会自动进行数据请求,前提是客户端提供的和后端的相符。如果回调返回直接不作请求。在组件内进行分页请求之前提到了,这个装饰器为添加了对象,其中有个函数为。 21 分钟学 apollo-client 是一个系列,简单暴力,包学包会。 搭建 Apollo client 端,集成 redux使用 apollo-client 来获取数据修改本...

    robin 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<