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;
}
相關文章
相關標籤/搜索