[译] 使用 Redux Offline 和 Apollo 进行离线 GraphQL 查询

使用 Redux Offline 和 Apollo 进行离线 GraphQL 查询

具备讽刺意味的是,在咱们日益链接的世界中,对 web 应用程序离线功能的需求正在不断增加。咱们的用户(和客户端)但愿在联机、脱机和在链接不稳定的区域使用富互联网应用。javascript

这实际上是一件很困难的事情。html

让咱们深刻探讨如何经过 ReactApollo Client 提供的 GraphQL data layer 构建一个功能强大的脱机解决方案。这篇文章将会分为两部分,本周咱们将会讨论脱机查询。下周咱们将会讨论脱机修改。前端

Redux Persist 和 Redux Offline

在底层,Apollo ClientRedux 提供支持。这意味着咱们能够在 Apollo 应用程序中使用整个 Redux 生态系统中的工具和库。java

在 Redux 离线支持的世界中,有两个主要参与者:Redux Persist 和 Redux Offline。react

Redux Persist 是一个很是棒但很简单的工具。它被设计用来从 localStorage (或者从这些支持的存储引擎)中存储和检索(或者说“rehydrate”) redux store。android

Redux Offline 扩展自 Redux Persist 并添加功能和实用层。Redux Offline 自动检测网络的断开和从新链接,容许您在脱机时将操做排入队列,并在从新链接后自动重试这些操做。ios

Redux Offline 是离线支持的标配选项。🔋git

离线查询

开箱即用地,Apollo Client 在部分链接的网络状况下工做得至关好。客户一旦进行查询,该查询的结果就会保存到 Apollo store。github

若是使用 network only 之外的任何 fetchPolicy 再次执行同一查询,则该查询的结果将当即从客户端的 store 中提取出来并返回到查询组件。这意味着,即便咱们的客户端与服务器断开链接,重复的查询仍将返回最新的可用结果。web

不幸的是,一旦用户关闭咱们的应用,他们的 store 就会丢失。那如何在应用重启的状况下来持久化客户机的 Apollo store 呢?

Redux Offline 正是解决问题的良药!

Apollo store 实际上存在于咱们的应用的 Redux store(在 apollo key 中)中。经过将整个 Redux store 持久化到 localStorage 中,并在每次加载应用程序时从新获取。经过这种方法,即使在断开网络链接时,咱们也能够经过应用程序从新启动来传递过去查询的结果!

在 Apollo Client 应用程序中使用 Redux Offline 并不是不存在问题。让咱们看看如何让这两个库协同工做。

手动构建一个 Store

一般状况下,创建一个 Apollo client 十分简单:

export const client = new ApolloClient({
    networkInterface
});
复制代码

ApolloClient 的构造函数将自动为咱们建立 Apollo store(并间接建立咱们的 Redux store)。咱们只需将这个新的 client 放入咱们的 ApolloProvider 组件中:

ReactDOM.render(
    <ApolloProvider client={client}> <App /> </ApolloProvider>,
    document.getElementById('root')
);
复制代码

当使用 Redux Offline 时,咱们须要手动构造 Redux store,以传入 Redux Offline 的中间件。首先,让咱们来重现 Apollo 为咱们所作的一切:

export const store = createStore(
    combineReducers({ apollo: client.reducer() }),
    undefined,
    applyMiddleware(client.middleware())
);
复制代码

新的 store 使用了 Apollo client 为咱们提供的 reducer 和 middleware,并使用了一个值为 undefined 的初始 store 来进行初始化。

咱们如今能够把这个 store 传入咱们的 ApolloProvider 中:

<ApolloProvider client={client} store={store}>
    <App /> </ApolloProvider>
复制代码

完美。既然咱们已经手动建立了 Redux store,咱们就可使用 Redux Offline 来开发支持离线的应用。

基础查询持久化

以最简单的形式引入 Redux Offline,包括为咱们的 store 添加一个中间件:

import { offline } from 'redux-offline';
import config from 'redux-offline/lib/defaults';
复制代码
export const store = createStore(
    ...
    compose(
        applyMiddleware(client.middleware()),
        offline(config)
    )
);
复制代码

这个 offline 中间件将会自动地把咱们的 Redux store 持久化到 localStorage 中。

不相信我吗?

启动你的控制台并查看此 localStorage

localStorage.getItem("reduxPersist:apollo");
复制代码

你将会看到一个巨大的 JSON blob,它表明着你 Apollo 应用程序的整个当前状态。

redux_persist_apollo.webm

太棒啦!

Redux Offline 如今将自动地把 Redux store 的快照保存到 localStorage 中。任什么时候候从新加载应用程序,此状态都将自动从 localStorage 中提取并 rehydrate 到你的 Redux store 中。

即便当前应用程序已与服务器断开链接,任何在 store 中使用此方案的查询都将返回该数据。

Rehydration 竞争的状况

不幸地是,store 的 rehydration 不是即刻完成的。若是咱们的应用程序试图在 Redux Offline 取得 store 时进行查询,奇怪的事情就会发生啦。

若是咱们打开了 Redux Offline 的 autoRehydrate 日志记录(这自己就是一种折磨),咱们会在首次加载应用程序时会看到相似的错误:

21 actions were fired before rehydration completed. This can be a symptom of a race condition where the rehydrate action may overwrite the previously affected state. Consider running these actions after rehydration: …

Redux Persist 的做者认可了这一点,并已经编写了一种延迟应用程序的渲染直到完成 rehydration 的方法。不幸的是,他的解决方案依赖于手动调用 persistStore,而 Redux Offline 已经默默为咱们作了这项工做。

让咱们看看其它的解决方法。

咱们将会建立一个新的 Redux action,并将其命名为 REHYDRATE_STORE,同时咱们建立一个对应的 reducer,并在咱们的 Redux store 中设置一个值为 truerehydrated 标志位:

export const REHYDRATE_STORE = 'REHYDRATE_STORE';
复制代码
export default (state = false, action) => {
    switch (action.type) {
        case REHYDRATE_STORE:
            return true;
        default:
            return state;
    }
};
复制代码

如今让咱们把这个新的 reducer 添加到咱们的 store 中,而且告诉 Redux Offline 在获取到 store 的时候触发咱们的 action:

export const store = createStore(
    combineReducers({
        rehydrate: RehydrateReducer,
        apollo: client.reducer()
    }),
    ...,
    compose(
        ...
        offline({
            ...config,
            persistCallback: () => {
                store.dispatch({ type: REHYDRATE_STORE });
            },
            persistOptions: {
                blacklist: ['rehydrate']
            }
        })
    )
);
复制代码

完美。当 Redux Offline 恢复完咱们的 store 后,会触发 persistCallback 回调函数,这个函数会 dispatch 咱们的 REHYDRATE_STORE action,并最终更新咱们 store 中的 rehydrate

rehydrate 添加到 Redux Offline 的黑名单能够确保咱们的 store 永远不会存储到 localStorage 或从 localStorage 取得咱们的 store。

既然咱们的 store 能准确地反映是否发生了 rehydration 操做,那么让咱们编写一个组件来监听 rehydrate 字段,而且只在 rehydratetrue 时对它的 children 进行渲染。

class Rehydrated extends Component {
    render() {
        return (
            <div className="rehydrated"> {this.props.rehydrated ? this.props.children : <Loader />} </div>
        );
    }
}

export default connect(state => {
    return {
        rehydrate: state.rehydrate
    };
})(Rehydrate);
复制代码

最后,咱们能够用新的 <Rehydrate> 组件把 <App/> 组件包裹起来,以防止应用程序在 rehydration 以前进行渲染:

<ApolloProvider client={client} store={store}>
    <Rehydrated>
        <App />
    </Rehydrated>
</ApolloProvider>
复制代码

哇哦。

如今,咱们的应用程序能够愉快地等待 Redux Offline 从 localStorage 中彻底取得咱们的 store,而后继续渲染并进行任何后续的 GraphQL 查询或修改了。

注意事项

在配合 Apollo client 使用 Redux Offline 时,须要注意如下这些事项。

首先,须要注意的是本文的示例使用的是 1.9.0-0 版本的 apollo-client 包。Apollo Client 在 1.9 版本中引入了修复程序,来解决与 Redux Offline 同时使用时的一些怪异表现

与此文相关的另外一个须要关注的点是,Apollo Clinent Devtools 对 Redux Offline 的支持不太友好。在安装了 Devtools 的状况下使用 Redux Offline 有时会致使意外的错误。

在建立 Apollo client 实例时,不链接 Devtools 便可很容易避免这些错误:

export const client = new ApolloClient({
    networkInterface,
    connectToDevTools: false
});
复制代码

敬请期待

Redux Offline 应该为您的 Apollo 支持的 React 应用程序的查询解析提供基本支持,即便您的应用程序是在与服务器断开链接时从新加载的。

下周咱们会进一步探讨如何使用 Redux Offline 处理脱机修改的问题。

敬请期待!

若是发现译文存在错误或其余须要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可得到相应奖励积分。文章开头的 本文永久连接 即为本文在 GitHub 上的 MarkDown 连接。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏

相关文章
相关标签/搜索