[譯] 使用 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前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索