iT邦幫忙

2017 iT 邦幫忙鐵人賽
DAY 24
0

前言

前一篇拼死拼活的才把 Token 拿回來,這篇要來用 Token 拿資料回來顯示 User 的頁面。

準備 GraphQL Client

經過一段考慮後,這邊決定使用 Apollo Client 來發送 GraphQL Request,首先必須先安裝 apollo-clientreact-apollographql-tag 這三個套件:

npm install --save apollo-client react-apollo graphql-tag

或是:

yarn add apollo-client react-apollo graphql-tag

接著用 Github 的 GraphQL API 網址 https://api.github.com/graphql 來建立 Network Interface:

import { createNetworkInterface } from 'apollo-client';

const networkInterface = createNetworkInterface({
  uri: 'https://api.github.com/graphql',
});

這邊需要加一個 Middleware 來把 Access Token 加到 Request Header,才能利用使用者授權的權限:

Authorization: Bearer <Access Token>

這 API applyMiddleware(req, next) 很類似 Express 的 Middleware,使用 next 參數來繼續執行下一個 Middleware。要在 Apollo Client 背後的 fetch 上加一些參數,這是絕妙的方式,這邊使用 AsyncStorage.getItem 來拿出 Access Token 以便能塞進 Header:

networkInterface.use([
  {
    async applyMiddleware(req, next) {
      // Create the header object if needed.
      if (!req.options.headers) {
        req.options.headers = {};
      }

      const token = await AsyncStorage.getItem('@IronGithub:access_token');
      if (token) {
        req.options.headers.authorization = `Bearer ${JSON.parse(token).access_token}`;
      }
      next();
    },
  },
]);

把前面的 networkInterface 丟進 ApolloClient 當參數就能建構出我們要的 Client:

import ApolloClient from 'apollo-client';

const client = new ApolloClient({
  networkInterface,
});

最後在用 ApolloProvider 來把 client 放進 React Context,就先告一段落囉:

import { ApolloProvider } from 'react-apollo';

export default class LoggedIn extends Component {
  render() {
    return (
      <ApolloProvider client={client}>
        <UserScreen />
      </ApolloProvider>
    );
  }
}

思考 GraphQL Query

這次要做的 Component 必須拿登入的使用者資料 (viewer),其中必須有:

  • avatarURL:大頭貼網址
  • login:帳戶 Unique ID
  • name:使用者名稱
  • followers.totalCount:followers 的人數
  • following.totalCount:following 的人數

followersfollowing 都是一種叫做 Connection 的 Type,有興趣的讀者可以看官方網站的這裡,在這邊只要先知道可以從 totalCount 拿到總數就好

所以我們會奏出以下的 Query:

query {
  viewer {
    avatarURL
    login
    name
    followers {
      totalCount
    }
    following {
      totalCount
    }
  }
}

先用 GraphiQL 試試看:

成功!API 沒問題的話就可以開始實作了。

實務上可能是先決定 Feature,然後有 UI 上的需求才會回來 Query 加欄位

用 GraphQL 描述 Component 資料需求

ApolloProvider 裡面像下面範例這樣利用 graphql(query) Higher-Order Component 來發出 GraphQL 的 Request 取回資料:

// UserScreen.js
import { graphql } from 'react-apollo';
import gql from 'graphql-tag';

class UserScreen extends Component {
  render() {
    // ...
  }
}

const query = gql`query {
  viewer {
    avatarURL
    login
    name
    followers {
      totalCount
    }
    following {
      totalCount
    }
  }
}`;

export default graphql(query)(UserScreen);

如此一來這些資料就會自己查完丟進來 Component,所以我們可以看到 propTypes 跟 GraphQL Query 有九成像:

class UserScreen extends Component {
  static propTypes = {
    data: PropTypes.shape({
      loading: PropTypes.bool.isRequired,
      error: PropTypes.object,
      viewer: PropTypes.shape({
        avatarURL: PropTypes.string.isRequired,
        login: PropTypes.string.isRequired,
        name: PropTypes.string.isRequired,
        followers: PropTypes.shape({
          totalCount: PropTypes.number.isRequired,
        }).isRequired,
        following: PropTypes.shape({
          totalCount: PropTypes.number.isRequired,
        }).isRequired,
      }),
    }).isRequired,
  };
  // ...
}

data 裡面會有 loadingerror 的欄位所以可以很方便地顯示載入畫面跟錯誤畫面:

class UserScreen extends Component {
  // ...
  render() {
    const { loading, error, viewer } = this.props.data;
    if (loading || error) return null;
    return (
      <Something />
    )
  }
}

資料都搞定之後,前端工程師的挑戰才要真正開始。

處理 UI

Grid Layout

這邊想特別介紹的是 - react-native-easy-grid,這真的是個簡單快速不囉唆的排版法。

只有三個簡單的 Component Col, Row, Grid

import { Col, Row, Grid } from 'react-native-easy-grid';

左右切:

<Grid>
  <Col></Col>
  <Col></Col>
</Grid>

上下切:

<Grid>
  <Row></Row>
  <Row></Row>
</Grid>

要巢狀或是其他的比例也都很簡單,花個兩三分鐘看個 Readme 就能學會。

放置

this.props.data.viewer 裡面已經有所有需要的資料,所以只要把前面學過的東西一次用出來,用 JSX 加 style 慢慢的

class UserScreen extends Component {
  // ...

  render() {
    const { loading, error, viewer } = this.props.data;
    if (loading || error) return null;
    return (
      <Grid>
        <Row size={65} style={styles.profile}>
          <Image
            style={styles.avatar}
            source={{ uri: viewer.avatarURL }}
          />
          <Text style={[styles.textCenter, styles.login]}>
            {viewer.login}
          </Text>
          <Text style={[styles.textCenter, styles.name]}>
            {viewer.name}
          </Text>
        </Row>
        <Row size={35}>
          <Col>
            <Text style={[styles.textCenter, styles.followText]}>
              <Text style={styles.followNumber}>{viewer.followers.totalCount}</Text> Followers
            </Text>
          </Col>
          <Col>
            <Text style={[styles.textCenter, styles.followText]}>
              <Text style={styles.followNumber}>{viewer.following.totalCount}</Text> Following
            </Text>
          </Col>
        </Row>
      </Grid>
    );
  }
}

const styles = StyleSheet.create({
  textCenter: {
    textAlign: 'center',
  },
  profile: {
    flexDirection: 'column',
    justifyContent: 'center',
  },
  avatar: {
    marginTop: 90,
    marginBottom: 50,
    width: 220,
    height: 220,
    borderRadius: 20,
  },
  login: {
    flex: 3,
    fontSize: 28,
    lineHeight: 28,
  },
  name: {
    flex: 2,
    fontSize: 22,
    lineHeight: 22,
  },
  followText: {
    fontSize: 20,
  },
  followNumber: {
    fontSize: 30,
    color: '#EA7A4C',
  },
});

成果如下:

結語

各位讀者看到者裡應該感受得出來,GraphQL API 是對前端非常友善的 API,很多複雜的事情都給後端去處理掉。前端工程師這幾年總是被各種新技術跟新語法攪在一起,又要常常加入框架之間的戰爭,還享有六個月就能用新技術重寫一次專案的福利,終於有個機會能輕鬆一點...。


上一篇
Day 23:實作 OAuth 來使用 Github GraphQL API
下一篇
Day 25:實作 Navigation 與 Drawer
系列文
使用 Modern Web 技術來打造 Native App30

尚未有邦友留言

立即登入留言