React Native填坑之旅--GraphQL

小紅星閃啊閃發表於2021-11-10

GraphQL還是通過Http的GET和POST的方式返回資料,只是GET的長度限制導致可能的查詢會出問題。所以一般都可以用POST來獲取、修改資料。這就是說GraphQL在客戶端App來說可以和平時請求API的方式完全一樣。在基本使用上,有沒有第三方graphql client的庫的幫助都沒什麼區別。

GraphQL是啥

在正式開始之前,稍微介紹一下GraphQL。如果你的專案稍有規模,那麼你一定經受過一種折磨。一個很久之前的API返回了巨多務必的資料,是可以完全服務現在的需求。但是明顯資料過多在要求效能的時候,在後端資料是查出來的,有快取也得訪問了快取才能返回並不是沒有代價。在前端佔了頻寬返回就慢。然後從一大堆資料裡拿出你想要的也要代價。後面的維護對於前後端都是可能產生棘手的問題。之所以FB要提出GraphQL的標準也是因為FB本身支援的產品太多遇到了這樣的問題。

如果客戶端這邊說有了什麼需求,就獲取這個需求的必要資料,那麼基本就要新開發API。GraphQL就是一個你要啥就返回啥的巨大API。可以查詢你指定的資料,也可以修改後端的資料。查詢就是Query,修改操作叫做Mutation

查詢:

query {
    todos {
      id
      title
    }
  }

這是一個查詢。要查詢的是todos(可以暫時理解為一個表),要查詢的是idtitle兩個欄位。這個查詢只會返回idtitle兩個欄位對應的資料。

也可以是帶條件的查詢:

  query ($from: Int!, $limit: Int!) {
    todos (from: $from, limit: $limit) {
      id
      title
    }
  }

新增、修改

mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
  createReview(episode: $ep, review: $review) {
    stars
    commentary
  }
}

返回

{
  "data": {
    "createReview": {
      "stars": 5,
      "commentary": "This is a great movie!"
    }
  }
}

基本介紹就到這裡。詳細可以參考官方文件

支援的語言

為啥不是Relay

官方的庫專職負責勸退有沒有體會過。GraphQL是Facebook提出來的一個標準,注意這是一個標準而不是實現。服務端的情況不熟不多做介紹,但是在客戶端FB或者現在叫Meta了,給出了一個實現並且已經發展了很多年。這個庫叫做Relay

它憑藉強大的功能和Meta(當年FB)背書,很快發展了起來。不過這個工具顯然已經有點後勁不足了。從現在TypeScript專案發展的情況來看,它顯然缺乏對TypeScript的支援。比如它的一個配套babel外掛沒有對TypeScript的支援。當然也是一個小問題,只需要在自己的專案裡新增一個index.d.ts檔案並新增型別就可以。

然後是它的模式。在你按照官方文件的Step by step一步一步走完的話,你還是不能做開發。因為你在新增另外一個檔案的和查詢的時候就會發現,這個需要的查詢並不會自動生成。要麼是悄沒聲的沒有報錯也沒有生成對應的檔案,要麼是報一些莫名其妙的錯。因為你需要根據你的檔名來命名查詢(或者任何的操作)。也就是它的模式可以認為是強侵入的,雖然會比其他的方式少寫一些固定程式碼,雖然也不一定。筆者水平有限,只好先棄了。

URQL怎麼樣

首先,urql在github有6.5K的star。並且設計也足夠活躍。最後被後還有個公司支援。不能說不是KPI專案,但是KPI專案也有個好處,至少有為了KPI的人在維護程式碼。

另外,這個庫是用TypeScript開發的。也就是說它肯定是TypeScript友好的,你的專案如果用了TypeScript,在型別上不用擔心過時、不完整等問題。

image.png

並不是其他的庫不合適,更多可以選擇的庫在GraphQL官網裡有列出來。

用一下現成的GraphQL服務:Github

Github很久以前就提供了GraphQL API。我們就在APP裡呼叫github的GraphQL API。用來查詢某個owner(比如facebook)下面的公開程式碼庫。

要使用github的GraphQL服務需要用到token,所以需要一個輸入token的介面。在使用者輸入token後可以持久化儲存這個token。還需要有一個介面可以刪掉這個token,讓使用者有機會可以輸入新的token。尤其使用者修改了許可權之後,那麼就必須要有一個更新token的地方。

導航

simulator_screenshot_B30AF36D-0A2F-4257-AB60-2541F100FD62.png

simulator_screenshot_F318440D-1F2F-49BB-9DCC-51FF7FF2A215.png

使用者在進入APP之後,在點選Repo選項之後,!如果不存在這麼一個Token,則會進入token頁面。在使用者輸入token之後才能繼續後面的功能。

使用者在輸入Token頁輸入token後跳轉到列表頁。在Settings頁可以刪掉Token,然後自動跳轉到Token頁。

在使用者成功輸入token,進入repo列表頁可以看到repo列表。現在只顯示facebook下面的公開repo。後面加入search bar可以輸入owner,這樣就可以控制要搜尋的是哪些repo了。

URQL基本配置

urql的配置分兩部分。第一是provider的配置。使用provider可以讓所有呼叫graphql api的地方都很方便的拿到請求的endpoint和需要的auth token。當然不是明文的讀取而是可以直接呼叫查詢。

配置Provider

App.tsx可以看到:

    <Provider value={client}>
      <NavigationContainer>
        <Stack.Navigator initialRouteName="Token">
          <Stack.Screen
            name="Tabs"
            component={Tabs}
            options={{ headerShown: false }}
          />
        </Stack.Navigator>
      </NavigationContainer>
    </Provider>

這個Provider和react-redux的provider的作用一樣。這裡urql的provider提供的是一個client。

Exchange

Exchange是urql的一箇中介軟體機制。這個機制也和Redux的中介軟體機制類似。

這裡我們需要給官網提供的authExchange填空,把獲取和使用token的邏輯加進去。

首先需要安裝authExchange

yarn add @urql/exchange-auth

然後在路徑:src/data/graphqlClient.ts下可以看到給authExchange填空的程式碼。在這裡需要新增的除了上文說的獲取token,使用token之外還有錯誤處理的內容。之類為了簡單,錯誤處理的部分先忽略。有需要的同學可以研究官網例項。

獲取token的方法是getAuth。我們的token是在Github配置生成,然後使用者完整新增並儲存在APP的裡。所以不需要額外的API呼叫獲取token。

  getAuth: async ({ authState }): Promise<AuthState | null> => {
    try {
      if (!authState) {
        const token = await AsyncStorage.getItem(GITHUB_TOKEN_KEY);
        return { token } as AuthState; // *
      }
      return null;
    } catch (e) {
      return null;
    }
  },

在加星這一步可以看到,token是作為authState物件的一個屬性返回了。

使用token是通過方法addAuthToOperation實現的。在這裡最後會返回一個新建的operation。裡面就存放了從getAuth拿到的token:

  addAuthToOperation: ({ authState, operation }) => {
    // ...略
    return makeOperation(operation.kind, operation, {
      ...operation.context,
      fetchOptions: {
        ...fetchOptions,
        headers: {
          ...fetchOptions.headers,
          Authorization: `Bearer ${authState.token}`,  // *
        },
      },
    });
  },

在加*這一步使用了token,從authState裡讀出了token放在header的認證裡隨著api請求傳送到了後端。

填充urql的client

通過Exchange配置好了token之後,關鍵的一步就完成了。接下來就需要把配置號的exchange和graphql的endpoint都新增到client裡供graphql的查詢使用。

const getGraphqlClient = () => {
  const client = createClient({
    url: 'https://api.github.com/graphql', // 1
    exchanges: [
      dedupExchange,
      cacheExchange,
      authExchange({    // 2
        ...authConfig,
      }),
      fetchExchange,
    ],
  });

  return client;
};

export { getGraphqlClient }; // 3
  1. 在url屬性新增github的graphql的endpoint:https://api.github.com/graphql
  2. 把auth exchange新增到exchange陣列裡。在這裡配置的時候需要注意同步操作的exchange要放在非同步操作的exchange前面。所以,authExchange要放在第三位。

實現一個查詢

完成了上面的配置之後,我們可以開始實現一個簡單的查詢了。

src/GithubScreen.tsx檔案裡可以看到具體的查詢和執行後的效果。

首先來準備我們的查詢語句。

import { gql, useQuery } from 'urql'; // 1

const REPO_QUERY = gql`    // 2
  query searchRepos($query: String!) {
    search(query: $query, type: REPOSITORY, first: 50) {
      repositoryCount
      pageInfo {
        endCursor
        startCursor
      }
      edges {
        node {
          ... on Repository {
            name
          }
        }
      }
    }
  }
`;
  1. 引入urql到工具方法gql和useQuery。useQuery後面會用到
  2. 編寫查詢語句。

這個查詢語句看起來會讓初學者不知所措。上面的例子我們也只是提到了query,metation之類少數幾個關鍵字。那麼這麼長的(還不算長)查詢語句如何能寫出來呢。github專門提供了一個graphql api的explorer。點選這裡到explorer。事實上,在很多語言對GraphQL的實現裡都有這樣一個explorer,至少是在開發階段可以享受到這個服務。

image.png

  • 首先在這個頁面登入你的github賬號。
  • 在GraphiQL裡就可以測試各種各樣的查詢語句了。
  • 如果你有schema不清楚的可以看最右面的Doc文件
  • 左下角的Query Variable可以輸入查詢的變數。這裡就是query,對應的是repo的owner和repo的license型別。
  • 中間一列就是查詢的結果。

在中間看到查詢結果之後,就可以判斷你的查詢語句是否合適。在本例中就是我們需要的查詢語句了,直接複製到我們的程式碼裡使用。

或者,如果你對於schema的定義略有了解的話,比如我們這次要查詢的是repository。也可以使用查詢語句編輯器裡面的智慧提示。整個來說,編寫查詢、修改語句是非常方便的。

一個簡單的查詢

上面說到如何編寫一個查詢語句,現在來使用這個語句查詢repo列表。

urql也提供了這樣的一個hook給react使用。

import { gql, useQuery } from 'urql';  // 1

const REPO_QUERY = gql `query searchRepos(...) { ... }`;

const GithubScreen = () => {
  const [result] = useQuery({  // 2
    query: REPO_QUERY,
    variables: { query: 'user:facebook' },
  });

  const { data, fetching, error } = result; // 3

  // ...略...

}

很簡單一個簡單的查詢就可以搞定了。

  1. 只需要請useQuery出場。
  2. 使用useQuery。這個hook還會返回一個重新執行查詢的方法,主要使給重新整理時使用。
  3. 網路請求三狀態,data是資料,fetching表示請求中,error是查詢出現錯誤。

後面的程式碼可以根據出現的作出不同處理。

最後在FlatList中顯示user是facebook的所有公開repo。

image.png

最後

一個簡單的查詢在這個app裡就已經完成了。但是顯然還有一些工作需要做。比如,loading和error處理都顯得比較簡陋。我們在前面的系列裡提到了redux-toolkit。是否可以有一個slice來讓這些邏輯的處理和redux結合在一起。

我們在依賴裡也已經新增了react-native-paper元件庫。這個庫可以在native和web上通用。我還沒有把web斷的截圖放上來,主要因為有點慘不忍賭。UI也可以在後面稍作美化。

最主要的工作是如何在實際的開發中使用graphql。它的潛力絕不只是看起來很新穎這麼簡單看,而是可以實實在在的解決問題的。前端的同學會遇到一個最大的阻力就是來自於後端同學是否接收這一不太新的新事物。

在youtube上有一個30分鐘搞定graphql的視屏,點這裡可以看。實際後端要整合graphql肯定不會像視屏裡的那麼容易,而且他本身也僅僅演示了查詢操作的處理。不過也不會像想象的那麼難。GraphQL是一個標準,在實現上也是由從外部查詢語句到內部獲取資料之間的轉換,也就是視訊裡的resolver,和schema定義。它依然依賴於底層的“DAO”層,或者是rest api的http請求。在GraphQL實現之後的收益就非常的顯而易見,資料消費端(各種App)對於後端修改的需求會大幅度減少。擺弄query語句就可以何必後端新增API呢?

相關文章