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

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

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

写入 store 的失败缘由分析和解决方案前端

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

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

因而,咱们就须要经过 Apollo 提供的一些接口,结合本身的业务需求,定制本身的 Apollo。git

网络层

GraphQL 入门: Apollo Client - 网络层github

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

fetch

Apollo 使用的是 fetch api
从源码来看,apollo 代码中并无包含 polyfill,也就是说,在低版本的浏览器上可能会由于缺乏 fetch 而失败。因此你须要安装 isomorphic-fetch
另外,若是你碰到跨域等问题,也是 fetch 引发的,和 apollo 没什么关系。redux

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

network middlewares

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

因此你能够在这里

  • 设置 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;
}
相关文章
相关标签/搜索