React Native入門備忘録

# 目的
ニュースアプリを作成しながらReact Nativeを勉強したので備忘録
ニュースアプリはAPIでニュースを取得し表示するシンプルなもの

## Agenda
- React Nativeの使い所
- コンポーネントを用いたUI開発
- API通信を用いた実装
- 画面遷移
- Reduxを用いた状態管理
- ストアへの公開

# 環境構築

- Expo CLIのインストール
- プロジェクト作成
- シミュレーターで動かす
- コードを変更してみる
- Prettierのインストール

### ベース環境

- OS: Mac OSX
- Editor: VSCode
- Node.js: v12.10.0
- react-native-cli: 2.0.1
- react-native: 0.61.4
- Expo: v35.0.0
- Expo CLI: v3.20.3



### Expoのインストール

ExpoはReact Nativeの開発を簡単にするツール

1. Expoのページにアクセス
https://expo.io/

**Get Started**をクリックし、メニューに進む

2. node.jsのインストール

expo-cliのインストール

javascriptのビルドや、公開時のpublishをしてくれるツール

```sh
$ npm install expo-cli --global

$ expo --version
3.20.3
```

3. Expoのアカウントを作成し、ログイン

メニューの**Sign up**からExpoアカウントを作成
ターミナルからログイン

```sh
$ expo login
? Username/Email Address: xxx
? Password: [hidden]

Success. You are now logged in as kawk.
```

4. プロジェクトを作成

```sh

$ expo init myNewProject --npm
? Choose a template: (Use arrow keys)
  ----- Managed workflow -----
❯ blank                 a minimal app as clean as an empty canvas 
  blank (TypeScript)    same as blank but with TypeScript configuration 
  tabs                  several example screens and tabs using react-navigation 
  ----- Bare workflow -----
  minimal               bare and minimal, just the essentials to get you started
 
  minimal (TypeScript)  same as minimal but with TypeScript configuration 
・・・

✔ Downloaded and extracted project files.
✔ Installed JavaScript dependencies.

✅ Your project is ready!
```

`expo init <プロジェクト名> --npm`
`--npm`オプションはパッケージ管理にnpmを利用する指定。デフォルトではyarnが適用される
**Managed workflow**はExpoのサポート下で簡単に開発できる。
**blank**と**tabs**の違いはどういったUIで始めるかの違い。
balnkは真っ白な画面からスタート
tabsは画面下に3つのタブがある状態からスタート

**「Bare workflow」**
Expoなしに近い方法でカスタマイズ性は高いが開発難易度が上がる。

今回はManaged workflowの**blank**を利用。


作成したプロジェクトを起動する

```sh
$ cd myNewProject
$ expo start
```

ブラウザが立ち上がる。このページからシミュレータや実機アプリ立ち上げができる。

### シミュレータを動かす

事前準備として予めiOSとAndroidのシミュレータが必要となる。
それぞれxcode、Android studioに付随するものを使用する。

Android Studioのインストール
https://docs.expo.io/workflow/android-studio-emulator/?redirected


シミュレータの立ち上げ方はそれぞれ、メニューから
**Run on iOS simulator**
**Run on Android device/emulator**
をクリック。
※Androidの場合は予めエミュレータを立ち上げておく必要がある

うまく立ち上がらない場合は`expo start`したターミナルに戻りCtrl + Cでプロセスを停止する。
再度、`expo start -c`でキャッシュをクリアして起動。
なにかトラブルがあればキャッシュクリアで解決することが多い。


### スマホの実機で動かす

始める前に、App Storeから**Expo Client**をスマホにインストールする。
開発中のソースコードはExpo Clientで動作する。

インストールしたら立ち上げたExpoのページからQRコードをスマホで読み取り、Expo Clientを起動する。

ネットワーク経由でJavaScriptをダウンロードして実機で動かすことができる。
注意はMacとiPhoneが同じLANに接続されている必要がある。

### コードを変更して確認する

作成したプロジェクト内にある`App.js`を編集する。
アプリが起動するとはじめにApp.jsが実行される。

text部分などを変更して保存すると、リアルタイムで変更が反映される。（ホットリロード）

App.js内にはSDKのバージョンが書かれているが、バージョンアップなどをした際には書き換えが必要になる。

### Prettierのインストール

Prettier = ソースコードの整形ツール

VSCodeのEXTENSION`Prettier`で検索しプラグインをインストール。

Save時に自動で整形するように設定する。
1. VScodeの設定を開き、「format」で検索
2. 「Editor: Format On Save」にチェック

整形のルールを細かく指定する場合、ワークスペースのルートに`.prettierrc`というファイルを追加。

Prettier公式ページを参考にコンフィグファイルを設定する。
https://prettier.io/docs/en/configuration.html

例: 
 
```json
{
  "trailingComma": "es5",
# css等で値を指定する際など、パラメータの後ろにカンマを自動追加

  "tabWidth": 4,
# タブの幅: スペース4個分(2に変更するなど)

  "semi": false,
# JavaScriptのimport行の行末になどセミコロンを自動追加する

  "singleQuote": true
# ダブルクォーテーションかダブルクォーテーションで統一
}
```

# TL;DR

React NativeとExpoについて

## React Native

- Facebookの開発したクロスプラットフォームフレームワーク
- JavaScriptで記述可能
- Reactのお作法に則っている

iOS、Androidの差分を気にせずアプリ開発ができる。

### React Nativeで作られたアプリ

- Facebook
- Instagram
- Pintarest
- Skype
- Bloomberg
etc...

### 開発方法

開発の検討時には以下の2パターンで選択肢となることが多い。

- Expoを使う
- Expoを使わない

判断基準は下記のメリット・デメリットから判断


## Expo
- React Nativeをさらに手軽にするツール
- OTAアップデート、クラウドビルドなどメリット多数
- 一方、作れるものに制限もある

**OTAアップデート** = iOSのアプリをリリース時など通常Appleの審査が必要になる部分も、審査に通すことなくネット経由でアプリのアップデートが可能。
アプリ起動に最新のJSバンドルに差し替える仕組みになっている。
**クラウドビルド** = アプリのビルド時にExpoのクラウドでビルド可能



## メリット
- 変更がリアルタイムに反映
- Web感覚で開発可能
- Appleの審査無しでアップデートできる(最初のリリース時は要審査)
- 本番のビルドがコマンド一つ
- expo build ios  => ipaファイルが生成
- expo build android  => apkファイルが生成
- でExpoのクラウド上にビルドが実行できる。


## デメリット
- 基本的にExpoが提供しているAPIのみ

利用できる機能(カメラ機能など)は以下のAPI referenceから確認する。
https://docs.expo.io/versions/latest/

利用したい機能を選び、**Installation**のセクションを確認し、`expo install expo-xxx`などのコマンドでインストールして利用する。

- サードパーティ製 npmモジュールには使えないものもある

モジュールの導入方法に`pod install..`などXCodeのコマンドなどが必要になっている場合はExpoで使用できない。

- ネイティブSDKが使えない
アプリ内課金、Bluetooth、サードパーティSDK(広告計測など)
設計フェーズからこのような要件があるときにはExpo利用は適さない

Expoの利用をやめる場合、ejectという機能を利用すると普通のReact Nativeプロジェクトに戻すことができる。
ただ、若干クセがあるらしい。
逆に、ejectしたプロジェクトをExpoに戻すことはできない。

## 開発方法での位置づけ

- ネイティブアプリではなく、Webアプリでいい場合はPWAなどを利用してWebサイトを作成

- ネイティブアプリエンジニアスキルがある場合は、SwiftやKotlinを使用して開発する

- ネイティブの特定のSDKや機能を利用したい場合はReact Native、そうでない場合はReact Native + Expoで開発

SNSのような投稿、コミュニケーションアプリ等の標準機能であればExpoで開発可能。


# UIを作成する
- Componentについて

ReactではプログラムはComponentという単位を組み合わせて作る仕組みになっている

- JSXでUIを書く

ReactでUIをデザインするときはJSXという記号でコードを書く

- JSXのStyleを整える

フォントや余白のマージンなどの設定


## Componentについて

複雑なUIをComponentという小さな部品を組み合わせて実現している。
Component = 画像やテキストなど

適切な粒度のComponentに分けることで可読性や再利用性が担保できる。

コンポーネント単位でUIを作成する**アトミックデザイン**というUIデザイン手法の概念がある。

### Componentの登場人物
- JSX
- Props
- State

### JSX

**render**関数が返すJSXによって画面描画をする。見た目を定義する記法。
<View>タグの中に<Text>タグを定義してテキストを表示するなど、HTMLっぽい感覚で書ける。
Componentには2種類、Classコンポーネント、Functionコンポーネントがあり、Functionコンポーネントの場合はJSXは登場しない。

## PropsとState

### Props
Props = 不変
componentの"性質"を定義
親 -> 子に情報を渡す

### State
State = 可変
componentの"状態"を表現
stateを変更することで画面が更新される

人に例えると
`prpos.gender: male`
性別が男性なのは不変
`state.hungry: true`
お腹が空いている状態なのは可変

## JSXを編集

まずは箱を書くところを実装。
初期段階では以下のようになっている。

```js
export default function App() {
  return (
    <View style={styles.container}>
      <Text>Hello World</Text>
    </View>
  )
```

上のcomponentはreturnでJSXのテキストを返しているだけ。
下の様に箱を書くように書き変えると、画面に一つの枠線ができる。

```js
export default function App() {
  return (
    <View style={styles.container}>
      <View
        style={{
          height: 100,
          width: '100%',
          borderColor: 'gray',
          borderWidth: 1,
        }}
      ></View>
    </View>
  )
}
```

### Styleを整える

上の例ではviewタグの中に直接書いたが、JSX表記が長くなり読みづらくなるので、**Style**は別書きするほうがいい。
Styleは、ファイル内の下部に書くか上部に書くかで流派が分かれる。
デフォルトで以下のStyleが下部に存在する。

```js
const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
})
```

上のStyleは、上記JSXの以下行にて読み出されている。
`<View style={styles.container}>`

上で書いたJSXのStyleを以下のように"itemContainer"として定義する。

```js
const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
  itemCotainer: {
    height: 100,
    width: '100%',
    borderColor: 'gray',
    borderWidth: 1,
  },
})
```

JSX部分には"imageContainer"を読み出すように編集する。

```js
export default function App() {
  return (
    <View style={styles.container}>
      <View style={styles.itemCotainer}></View>
    </View>
  )
}
```

### 枠の中に枠をつくる

```js
import React from 'react'
import { StyleSheet, Text, View } from 'react-native'

export default function App() {
  return (
    <View style={styles.container}>
      <View style={styles.itemCotainer}>
        <View style={styles.leftContainer} />
        <View style={styles.rightContainer} />
      </View>
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
  itemCotainer: {
    height: 100,
    width: '100%',
    borderColor: 'gray',
    borderWidth: 1,
    flexDirection: 'row',
  },
  leftContainer: {
    backgroundColor: 'red',
    width: 100,
  },
  rightContainer: {
    backgroundColor: 'blue',
    flex: 1,
  },
})
```

### 画像を表示する例

React Nativeの標準機能である**Image**を使用して画像を表示する。
書き方は公式のリファレンスを見る。
https://reactnative.dev/docs/image


開発中にURLを指定して画像を表示したい場合など、Lorem Picsumというサービスが便利
https://picsum.photos/

```js
export default function App() {
  return (
    <View style={styles.container}>
      <View style={styles.itemCotainer}>
        <View style={styles.leftContainer}>
          <Image
            style={{ width: 100, height: 100 }}
            source={{ uri: 'https://picsum.photos/id/10/200/200' }}
          />
        </View>
        <View style={styles.rightContainer} />
      </View>
    </View>
  )
}
```

### テキストを表示

https://reactnative.dev/docs/text
**numberOfLine**を指定すると指定した行数以上は"..."で省略表示される

```js
<View style={styles.rightContainer}>
          <Text numberOfLines={3}>test</Text>
          <Text>Rect News</Text>
        </View>
```

### レイアウトの調整

Styleのパラメータ各種についてはCSSリファレンスを参考にすると良い。


```js
import React from 'react'
import { StyleSheet, Text, View, Image } from 'react-native'

export default function App() {
  return (
    <View style={styles.container}>
      <View style={styles.itemCotainer}>
        <View style={styles.leftContainer}>
          <Image
            style={{ width: 100, height: 100 }}
            source={{ uri: 'https://picsum.photos/id/10/200/200' }}
          />
        </View>
        <View style={styles.rightContainer}>
          <Text numberOfLines={3} style={styles.text}>
            test
          </Text>
          <Text style={styles.subText}>Rect News</Text>
        </View>
      </View>
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
  itemCotainer: {
    height: 100,
    width: '100%',
    borderColor: 'gray',
    borderWidth: 1,
    flexDirection: 'row',
  },
  leftContainer: {
    width: 100,
  },
  rightContainer: {
    flex: 1,
    flexDirection: 'column',
    padding: 10,
    justifyContent: 'space-between',
  },
  text: {
    fontSize: 16,
  },
  subText: {
    fontSize: 12,
    color: 'gray',
  },
})

```


ここまでが一通りのページ編集の動きとなる。
これまではコンポーネントは特に意識していない為、次からコンポーネントに切り出し実装していく。

# コンポーネントを作成する

- ニュース項目のComponentを作成
- propsで親から子にデータを渡す
- FlatListでニュース一覧を表示

## コンポーネントを切り出す

まず、VSCodeから本アプリのプロジェクトのルートディレクトリにコンポーネントのフォルダ`conponents`を作成する。
次にコンポーネントとなるファイルをcomponentsフォルダの中に作成する。今回は"ListItem.js"とする。

App.jsからListItem.jsコンポーネントにおいても使用する部分をコピペする。
今回は以下のImport行とStyle部分。

```js
import React from 'react'
import { StyleSheet, Text, View, Image } from 'react-native'

const styles = StyleSheet.create({
    container: {
      flex: 1,
      backgroundColor: '#fff',
      alignItems: 'center',
      justifyContent: 'center',
    },
    itemCotainer: {
      height: 100,
      width: '100%',
      borderColor: 'gray',
      borderWidth: 1,
      flexDirection: 'row',
    },
    leftContainer: {
      width: 100,
    },
    rightContainer: {
      flex: 1,
      flexDirection: 'column',
      padding: 10,
      justifyContent: 'space-between',
    },
    text: {
      fontSize: 16,
    },
    subText: {
      fontSize: 12,
      color: 'gray',
    },
  })
```

ListItemコンポーネントを作成するにあたって、今回は**Function Component**を使用する。

ListItemコンポーネントの作成にあたって、ListItem.jsに以下を追加する。

```js
const ListItem = () => {
    return (
    )
}

export default ListItem;
```

JSXの中にはApp.jsのJSXから引っ越しさせる。

```js
const ListItem = () => {
    return (
        <View style={styles.itemCotainer}>
        <View style={styles.leftContainer}>
          <Image
            style={{ width: 100, height: 100 }}
            source={{ uri: 'https://picsum.photos/id/10/200/200' }}
          />
        </View>
        <View style={styles.rightContainer}>
          <Text numberOfLines={3} style={styles.text}>
            test
          </Text>
          <Text style={styles.subText}>Rect News</Text>
        </View>
      </View>
    )
}

export default ListItem;
```

作成したコンポーネントを呼び出すには、App.jsにImportで追記する。

```js
import ListItem from './components/ListItem';
```
実際にListItemのJSXを表示するには以下のようにApp.jsのJSX部分をListItemコンポーネントを呼び出すように変更する。

```js
export default function App() {
  return (
    <View style={styles.container}>
      <ListItem />
    </View>
  )
}
```

## Props経由でデータを渡す

テキストなどの値を親から子に情報を渡す様に実装する。
テキストの場合親から子のテキストを変更、再利用が可能になる。

親=App.js
子=ListItem.js

親側では"author"というPropsを利用し、テキストを渡す。

```App.js
<ListItem author="SampleNews" />
```

子側では引数としてpropsを受け取る
JSXの中にJavaScriptのコードを書くときは**{}**で囲む
変更行は以下

```ListItem.js
const ListItem = (props) => {
・・・
<Text style={styles.subText}>{props.author}</Text>
```

```ListItem.js
const ListItem = (props) => {
  return (
    <View style={styles.itemCotainer}>
      <View style={styles.leftContainer}>
        <Image
          style={{ width: 100, height: 100 }}
          source={{ uri: 'https://picsum.photos/id/10/200/200' }}
        />
      </View>
      <View style={styles.rightContainer}>
        <Text numberOfLines={3} style={styles.text}>
          test
        </Text>
        <Text style={styles.subText}>{props.author}</Text>
      </View>
    </View>
  )
}
```

さらに画像のURLについても"ImageUrl"という名前のpropsで定義し、継承できるように追加実装する


```App.js
export default function App() {
  return (
    <View style={styles.container}>
      <ListItem
        imageUrl="https://picsum.photos/id/10/200/200"
        author="SampleNews"
      />
    </View>
  )
}
```

```ListItem.js
<Image
          style={{ width: 100, height: 100 }}
          source={{ uri: props.imageUrl }}
        />
```

propsの展開方法として、以下のように書くこともできる。
(展開箇所の"props."をいちいち書かなくて済む。)

```ListItem.js
const ListItem = ({imageUrl, author, title}) => {
  return (
    <View style={styles.itemCotainer}>
      <View style={styles.leftContainer}>
        <Image
          style={{ width: 100, height: 100 }}
          source={{ uri: imageUrl }}
        />
      </View>
      <View style={styles.rightContainer}>
        <Text numberOfLines={3} style={styles.text}>
          {title}
        </Text>
        <Text style={styles.subText}>{author}</Text>
      </View>
    </View>
  )
}
}
```

コンポーネント化したので、以下のようにApp.jsのJSX部分のコピペで再利用ができる

```App.js
export default function App() {
  return (
    <View style={styles.container}>
      <ListItem
        title="吾輩は猫である。にゃー"
        imageUrl="https://picsum.photos/id/10/200/200"
        author="SampleNews1"
      />
      <ListItem
        title="ほげほげ"
        imageUrl="https://picsum.photos/id/10/200/200"
        author="SampleNews2"
      />
      <ListItem
        title="ふがふが"
        imageUrl="https://picsum.photos/id/10/200/200"
        author="SampleNews3"
      />
    </View>
  )
}
```


## ニュース記事一覧の表示

APIから情報を取得して表示する想定なので、まず以下の様なJSON形式のダミーデータをプロジェクトルートに適当なフォルダで"dummies"を用意し、"articles.json"として以下のようなファイルを作成。
jsonの中身は今まで作成した内容に合わせて一定のフォーマットのデータが提供される想定。
("title", "ImageUrl", "author"など)

```json
[
  {
    "author": "World Nature News",
    "title": "世界自然保護団体、森林の二酸化炭素削減効果について研究結果を発表",
    "urlToImage": "https://picsum.photos/id/10/200/200",
    "publishedAt": "2019-05-22T06:14:23Z"
  },
  {
    "author": "Fun Fun Travel",
    "title": "今年の連休、人気の海外旅行先は？20代にアンケート調査。1位は意外なあの国!?",
    "urlToImage": "https://picsum.photos/id/13/200/200",
    "publishedAt": "2019-05-22T05:42:23Z"
  },
  {
    "author": "Biz News",
    "title": "世界の有名IT企業も実践するマインドフルネス。日本の企業でも続々と導入事例。",
    "urlToImage": "https://picsum.photos/id/17/200/200",
    "publishedAt": "2019-05-21T15:33:23Z"
  }
]
```

App.jsのJSXにて、上のサンプルデータをAPIに見立てて表示するようにする。
まず、Importにて"articles"という名前で"./dummies/articles"をインポートし、
JSXでは、以下のように配列を用意し、**item**として出力する。

```App.js
import articles from './dummies/articles'

export default function App() {
  const items = articles.map((article) => {
    return (
      <ListItem
        imageUrl={article.urlToImage}
        title={article.title}
        author={article.author}
      />
    )
  })
  return <View style={styles.container}>{items}</View>
}
```
この状態はListItemを配列でマップしているが、
ListItemが配列で複数あると、例えば画面が更新されたときにどのListItemを更新したらいいのかReactがわからなくなるので、**Key**を指定する必要がある。

今回の場合、まずmapメソッドに2つ目の引数として、**index**を追加する
挙動は順番に1,2,3,4...とされる
ListItemの中に`key={index}1を追加する。

```App.js
export default function App() {
  const items = articles.map((article, index) => {
    return (
      <ListItem
        imageUrl={article.urlToImage}
        title={article.title}
        author={article.author}
        key={index}
      />
    )
  })
  return <View style={styles.container}>{items}</View>
}
```

この状態で一覧画面ができるが、まだスクロールができないので次から実装していく

## FlatListで一覧を表示

通常スワイプしても上下に動かず、スクロールができない。
実装するには以下の様なコンポーネントを利用する方法がある。

- ScrollView
- FlatList

今回は**FlatList**で実装する。メリットとしては表示がはやい。
https://reactnative.dev/docs/flatlist

まず、FlatListコンポーネントをImportする。

```App.js
import { StyleSheet, View, FlatList } from 'react-native'
```
以下ののようにJSXを追加する

```App.js
  return (
    <View style={styles.container}>
      <FlatList
        data={articles}
        renderItem={({ item }) => (
          <ListItem
            imageUrl={item.urlToImage}
            title={item.title}
            author={item.author}
          />
        )}
        keyExtractor={(item, index) => index.toString()}
      />
    </View>
  )
```

containerスタイルのレイアウトは変更

```App,js
container: {
    flex: 1,
    backgroundColor: '#fff',
  },
```

### SafeAreaView

上の状態で画面上下部分と表示がかぶってしまうので、**SafeAreaView**というコンポーネントを使用して適切な余白を与える。
https://reactnative.dev/docs/safeareaview

今回の場合、JSX内の<View>タグ部分を単純に<SafeAreaView>に置き換える

```App.js
  return (
    <SafeAreaView style={styles.container}>
      <FlatList
        data={articles}
        renderItem={({ item }) => (
          <ListItem
            imageUrl={item.urlToImage}
            title={item.title}
            author={item.author}
          />
        )}
        keyExtractor={(item, index) => index.toString()}
      />
    </SafeAreaView>
  )
```

これまででコンポーネント化と表示内容の一覧表示までが完成。

# API経由でデータを取得

- useStateでstateを利用する
- useEffectでcomponentの初期設定をする
- API経由でニュース情報を取得する

## useStateでstateを利用する
state = Componentの状態を表す可変の数値

functionコンポーネントでstateを利用する場合は**フック**という仕組みを利用する。
従来はClassコンポーネントでないとstateを利用できなかったが、フックを利用することでfunctionコンポーネントでも利用可能となる。
https://ja.reactjs.org/docs/hooks-intro.html

functionコンポーネントで書くことほうが簡潔にかけたり推奨されている為、今回もfunctionを使用していく

フックに公式のExampleでは、以下の**"count"**部分が可変であり、stateの値となる。
**"useState(0)"**の部分では"count"の初期値として"0"を代入している。

```js
const [count, setCount] = useState(0);
```

以下では**"setCount"**というメソッドをつかって"count"の値を更新する。
公式のExampleでは、以下の行で"count"の値に+1している。

```js
<button onClick={() => setCount(count + 1)}>
```

### 使用方法

App.jsにて**useState**をImportする。

```js
// before
import React from 'react'
// After
import React, {useState} from 'react'
```

次に今までダミーを使用していたニュース記事のAPIにあたるJSONの"Article"をstateを使って読み込むように変更する。

```js
export default function App() {
  const [articles, setArticles] = useState(dummyArticles)
  return (
    <SafeAreaView style={styles.container}>
      <FlatList
        data={articles}
        renderItem={({ item }) => (
          <ListItem
            imageUrl={item.urlToImage}
            title={item.title}
            author={item.author}
          />
        )}
        keyExtractor={(item, index) => index.toString()}
      />
    </SafeAreaView>
  )
}
```
以上で表示内容は変わらないが、さっきは直接articleを表示させていたのが、stateを使用してダミーデータを読み込み表示することができた。

## useEffect
Componentの初期ロード時にAPIを叩き情報をGETする。
時間差を用いて表示することでstateが更新されたタイミングで表示が変えることができる。

https://ja.reactjs.org/docs/hooks-reference.html

### 使用方法

App.jsにて**useEffect**をImportする。

```js
import React, { useState, useEffect } from 'react'
```

以下、第一引数がメソッドとなる。
今回はalertを使用して呼び出されていることを確認できるように記述する。
第二引数に空配列を渡すことで、comoponentがマウントしたときに一度だけ実行される様になる。
以下ではアプリ立ち上げ時にポップアップで"called!"が表示される。

```App.js
export default function App() {
  const [articles, setArticles] = useState(dummyArticles);
  useEffect(() => {alert("called!")}, [])
```

これを利用すると、アプリ起動時(renderされたタイミングで)APIからデータをGETして表示する、というような処理が実現できる。

次は、useEffectの中でdummyArticleをセットする。
わかりやすいように2秒の時間差で表示する。時間差には**setTimeoout**を使用する。

```App.js
export default function App() {
  const [articles, setArticles] = useState([])
  useEffect(() => {
    alert('called!')
    const timer = setTimeout(() => {
      setArticles(dummyArticles)
    }, 2000)
  }, [])
```

上記を実装すると、アプリ立ち上げ > ポップアップ表示 > 2秒後にニュース一覧が表示、の動きとなる。

useEffectの返り値には**Cleanup**関数というものがあり、コンポーネントがアンマウントされるときにクリーンアップが必要な場合に利用する。
今回は以下のように"***return...***"行を追加し、タイマーをクリーンアップする。

```App.js
export default function App() {
  const [articles, setArticles] = useState([])
  useEffect(() => {
    alert('called!')
    const timer = setTimeout(() => {
      setArticles(dummyArticles)
    }, 2000)
    return () => clearTimeout(timer)
  }, [])
```

## NewsAPIを利用する準備

https://newsapi.org/

「Get API key」からアカウントを作成し、自分のアカウントページを確認。
自分のAPI keyをコピーする。

API keyはExpoの**app.json**に定義する。
https://docs.expo.io/workflow/configuration/

app.jsonの設定項目の中に**extra**という設定項目があり、ここに好きなパラメータを指定しアプリの中で使用することができる。
アプリの中からパラメータを呼び出すには、`Constants.manifeest.extra`で呼び出すことができる。

**Constants**の使い方は、まずInstallが必要となる。https://docs.expo.io/versions/latest/sdk/constants/#expoconstantsmanifest

```sh
$ expo install expo-constants

Installing 1 SDK 37.0.0 compatible native module using npm.
> npm install
audited 800 packages in 5.38s
found 9 low severity vulnerabilities
  run `npm audit fix` to fix them, or `npm audit` for details
```

インストールができたか確認するには、プロジェクト内の`package.json`を参照すると、`"dependencies"`配下に`"expo-constants": "~x.x.x"`の様に追記されているのが確認できる。

続いて、**app.json**の中に**extra**を用いてAPI keyを定義する。

```json
...
    "ios": {
      "supportsTablet": true
    },
    "extra": {
      "newsApiKey": "< API Key >"
    }
  }
}

```

app.jsonを変更した際には、**Expoの再起動**が必要になるので、ターミナルからCtrl + Cで停止し、`$ expo start`で起動する。


### API Keyをアプリ側で使用できることを確認

App.jsにConstantsをImportする。

```js
import Constants from 'expo-constants';
```

以前定義した`useEffect`の中に`alert`を利用して、API keyを出力

```App.js
export default function App() {
  const [articles, setArticles] = useState([])
  useEffect(() => {
    //追記
alert(Constants.manifest.extra.newsApiKey)

    const timer = setTimeout(() => {
      setArticles(dummyArticles)
    }, 2000)
    return () => clearTimeout(timer)
  }, [])
```

アプリ立ち上げ時にAPI Keyが表示されることを確認できる。

## モジュールのインストールとAPI実行

[Axios](https://github.com/axios/axios)というHTTP Clientを使ってAPIを実行する。
Axiosはnpmインストールして使用する。

```sh
$ npm install axios
```
Axiosを利用してAPIを叩くため、App.jsにてaxiosをImportし、NewsAPIのURLを指定する。
URLは[NewsAPI](https://newsapi.org/)のトップページにあるサンプルを利用する。

※文字列の中で変数を展開する際は**'**(シングルクォート)ではなく、**`**(バッククォート)で囲む事に注意

```App.js
Import axios from 'axios';

const URL =`http://newsapi.org/v2/top-headlines?country=jp&apiKey=${Constants.manifest.exstra.newsApiKey}`
```

続いて、NewsAPIを叩きニュースを取得してくるメソッドを作成する。
非同期的にニュースを取得したい為、**async**使用する。
[AxiosのGitHub](https://github.com/axios/axios)にある使い方のサンプルを参考に、NewsAPIからデータをGETする処理を書く。


```App.js
  const fetchArticles = async () => {
    try {
      const response = await axios.get(URL)
      console.log(response)
    } catch (error) {
      console.error(error)
    }
  }
```

新しくメソッドを定義したら、アプリ起動時のマウントされたタイミングで実行されるように`useEffect`に処理を追加する。

```App.js
const [articles, setArticles] = useState([])
  useEffect(() => {
    fetchArticles()
  }, [])
```
この状態ではまだ画面には表示されないが、Expoを実行しているコンソールを見るとNewsAPIからデータをGETできていることがわかる。

続いて、GETしたデータを画面に表示できるように、responseの中身から"articles"を抜き出す。
コンソールのレスポンスデータであるJSONを確認すると、"data"の中の"articles"というKeyに記事に該当するValueが含まれていることがわかる。

このデータを利用し、setArticlesを使ってresponseのデータをarticlesというstateに格納する。

先程作成したメソッドの"fetchArticles"の中に`setArticles(response.data.articles)`
を追加する。

```App.js
  const fetchArticles = async () => {
    try {
      const response = await axios.get(URL)
      setArticles(response.data.articles)
      console.log(response)
    } catch (error) {
      console.error(error)
    }
  }
```

この状態で実行するとNews一覧が表示されることを確認できる。

## Function ComponentとClass Componentについて

コンポーネント作成方法2つの違いについて補足。
Function Componentのほうが現在では推奨されており、公式のDocmentでもFunction Componentで書かれるようになっている。

### Function Component
- 記述がシンプル
- 動作が早い
- Hooks
    - UseState
    - UseEffect

ClassComponentの特徴であった**State**は**Hooks**の実装によりFunction Componentでも利用可能になった。


### ClassComponent
- State
- ライフサイクルが利用可能(ComponentDidMountメソッドなど)
    - ComponentDidMount = UseEffectに相当するメソッド

# 画面遷移の実装

ニュースの記事一覧から記事をタップするとニュース詳細(Web View)を表示するように画面遷移を実装する。

画面遷移の実装についてはライブラリは複数あるが、メジャーどころの**React Navigation**を使用する。

## ライブラリのインストール

Expo公式の[Routing & Navigation](https://docs.expo.io/guides/routing-and-navigation/)についてのページを確認すると、[React Navigation](https://github.com/react-navigation/react-navigation)を使用するように推奨されていることがわかる。

[React Navigationの公式ページ](https://reactnavigation.org/)から、[Getting started](https://reactnavigation.org/docs/getting-started)ページを参照し、インストールする。

```sh
$ npm install @react-navigation/native
```

続いて、Expo managed projectの場合、追加で必要なライブラリをインストールする。

```sh
$ expo install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-view
```

さらに、この後で[StackNavigator](https://reactnavigation.org/docs/stack-navigator/)というものを使用するため、それもインストールする。

```sh
npm install @react-navigation/stack
```

## StackNavigatorの利用

これまでApp.jsでは、ニュース一覧のページのみを表示していたが、ニュース一覧ページは**StackNavigator**を利用し、`HomeScreen`というものを作ってその中に収めていく。
ニュース記事の詳細ページは、`ArticleScreen`というものを作って収めることとする。

StackNavigatorは、**ReactNavigator**の機能の一つであり、アプリの画面遷移を実装するための機能である。

構成する画面としては、以下のような入れ子構造にする。

- App.js
    - AppNavigator
        - StackNavigator
            - Home Screen
            - AricleScreen


### Home Screenの実装

プロジェクト内のルートで`screens`というフォルダを作成する。
※React Navigationでは、画面単位のことをScreenと呼ぶ。

作成した"screens"フォルダの中に、`HomeScreen.js`ファイルを作成する。

HomeScreen.jsの中身は、App.jsからコードを全てコピーし、必要のないライブラリの削除やPATHの修正を行う。
例えば"ListItem"は、相対PATHが'../components/ListItem'の様に変更となるため注意。



```HomeScreen.js
import React, { useState, useEffect } from 'react'
import { StyleSheet, View, FlatList, SafeAreaView } from 'react-native'
import ListItem from '../components/ListItem'
import Constants from 'expo-constants'
import axios from 'axios'
```

functionコンポーネントの"App"を、"HomeScreen"に改名しアロー関数の記載方法に変更。

```HomeScreen.js
export default HomeScreen = () => {
  const [articles, setArticles] = useState([])
  useEffect(() => {
    fetchArticles()
  }, [])
```
[React Navigation公式](https://reactnavigation.org/docs/hello-react-navigation/)のサンプルコードを参考に画面遷移の実装をはじめる。

まず、App.jsに必要なライブラリのインストール。

```App.js
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
```

App.jsに`createStackNavigator`の定義

```App.js
const Stack = createStackNavigator();
```

App.jsのRenderの中身にStack Navigatorのルーティング設定を追加。
既存の内容はHomeScreen.jsに移行し、必要ないので消去する。

```App.js
export default function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen name="Home" component={HomeScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}
```
先程作成した`HomeScreen.js`をインポートする。

```App.js
import HomeScreen from './screens/HomeScreen'
```

### ヘッダーを非表示にする

このページではヘッダーを非表示にしたい、という場合など、Stack Navigatorのオプションで定義することができる。

```App.js
<Stack.Screen
  name="Home"
  component={HomeScreen}
  options={{ headerShown: false }}
/>
```

その他、App.jsからいらないコードを削除し、以下のようになる。

```App.js
import React from 'react'
import { NavigationContainer } from '@react-navigation/native'
import { createStackNavigator } from '@react-navigation/stack'
import HomeScreen from './screens/HomeScreen'

const Stack = createStackNavigator()

export default function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen name="Home" component={HomeScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  )
}
```

### Navigationを作成する

現状、App.jsにはStack Navigatorのルーティングのみ定義されているが、今後ページ数が増えていくことによってApp.jsのコード量が肥大していくことが予想されるので、ルーティングに関しては別ファイルに移管する、
上記の`AppNavigator`にあたるファイルを作成する。

はじめに、ルートに`navigation`フォルダを作成し、`AppNavigator.js`ファイルを作成する。

App.jsからNavigation関連のコードをコピーし、インポートライブラリのPATH修正とメソッド名を"AppNavigator"に修正。

```AppNavigator.js
import React from "react";
import { NavigationContainer } from "@react-navigation/native";
import { createStackNavigator } from "@react-navigation/stack";
import HomeScreen from "../screens/HomeScreen";

const Stack = createStackNavigator();

export default AppNavigator = () => {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen
          name="Home"
          component={HomeScreen}
          options={{ headerShown: false }}
        />
      </Stack.Navigator>
    </NavigationContainer>
  );
};
```

App.js側は、作成した`AppNavigator`を呼び出すだけの機能に修正する。

```App.js
import React from 'react'
import AppNavigator from './navigation/AppNavigator'

export default function App() {
  return <AppNavigator />
}
```

上書き保存で画面が表示されることを確認する。

よく`Can't find viriable: AppNavigator`のようなエラーが発生するが、Expoをリロードすると直る。

## 画面遷移を実装

最終的にはニュース記事をタップすると、記事詳細が出るような画面を作成していく。

### 新規ページの作成

`ArticleScreen`という名前で、ニュース記事詳細を表示するページの枠を作成する。

"screen"フォルダの中に`ArticleScreen.js`を作成する。

```ArticleScreen.js
import React from 'react'
import { StyleSheet, SafeAreaView, Text } from 'react-native'

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
  },
})

export default ArticleScreen = () => {
  return (
    <SafeAreaView style={styles.container}>
      <Text>This is Article Screen</Text>
    </SafeAreaView>
  )
}
```

次に、AppNavigatorからArticleScreenを呼び出すように追加する。
まず、ArticleScreenをインポートする。

```AppNavigator.js
import ArticleScreen from "../screens/ArticleScreen";
```

StackNavigatorの中に"ArticleScreen"を追加する。

```Appnavigator.js
export default AppNavigator = () => {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen
          name="Home"
          component={HomeScreen}
          options={{ headerShown: false }}
        />
        <Stack.Screen name="Article" component={ArticleScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  )
}
```


### タップした時の動作を実装

タップした時のアクションを指定するには、React Nativeの[TouchableOpacity](https://reactnative.dev/docs/touchableopacity)というライブラリを使用する。

今回はニュース記事のリストの任意の記事をタップされた時にページを遷移するようにする。

まず、**TouchableOpacity**をインポートする。

```ListItem.js
import { StyleSheet, Text, View, Image, TouchableOpacity } from 'react-native'
```

次に、既存で最上位の`<View>`で囲まれている部分を`<TouchableOpacity>で囲むように置き換える。

**onPress**というオプションで実行するメソッドを指定する。
タップされたときの処理は親側（HomeScreen）で実装する。
ListItem内では、タップされた時に親側に"onPress"を返すように定義する。

```ListItem.js
const ListItem = ({ imageUrl, title, author, onPress }) => {
  return (
    <TouchableOpacity style={styles.itemCotainer} onPress={onPress}>
      <View style={styles.leftContainer}>
        <Image style={{ width: 100, height: 100 }} source={{ uri: imageUrl }} />
      </View>
      <View style={styles.rightContainer}>
        <Text numberOfLines={3} style={styles.text}>
          {title}
        </Text>
        <Text style={styles.subText}>{author}</Text>
      </View>
    </TouchableOpacity>
  )
}
```

親側である、`HomeScreen`では、ListItemを呼び出している部分に、`onPress`を追加し、テストとしてタップ時にアラートを出力するようにする。

```HomeScreen.js
  return (
    <SafeAreaView style={styles.container}>
      <FlatList
        data={articles}
        renderItem={({ item }) => (
          <ListItem
            imageUrl={item.urlToImage}
            title={item.title}
            author={item.author}
            onPress={() => alert("tap")}
          />
        )}
        keyExtractor={(item, index) => index.toString()}
      />
    </SafeAreaView>
  )
}
```

これでニュース記事一覧から任意の記事をタップすると「tap」というポップアップが表示される。


### タップ時の画面遷移を実装

React Navigationで画面遷移を実装する際は、公式の[Moving between screens](https://reactnavigation.org/docs/navigating/)ページを参考にする。

HomeScreenのpropsによって`navigation`を展開する。
これにより、**navigation.navigate**を利用可能となる。

```HomeScreen.js
export default HomeScreen = (props) => {
  const {navigation} = props;
  const [articles, setArticles] = useState([])
  useEffect(() => {
    fetchArticles()
  }, [])
```

テストでタップ時にalertをするよう定義していた`onPress`の部分を`navigation.navigate`に書き換える。
`navigation.navigate`で指定するKeyは、`AppNavigator.js`で定義したScreenの"Name"を指定する様にする(e.g. "Home", "Article")

```HomeScreen.js
onPress={() => navigation.navigate("Article")}
```
この状態でニュース記事一覧から任意の記事をタップすると、上で作成したArticleページの内容「This is Article Screen」のテキストページに遷移する。

HomeScreenのpropsの指定についての書き方は以下のように省略して書くことができる。

```HomeScreen.js
export default HomeScreen = ({ navigation }) => {
  const [articles, setArticles] = useState([])
  useEffect(() => {
    fetchArticles()
  }, [])
```

以上で画面遷移の実装が完了となる。

### WebViewを表示する

Artibleページではニュース記事詳細のWebViewを表示するよう実装していく。

Expoの[react-native-webview](https://docs.expo.io/versions/latest/sdk/webview/)を使用する。

必要なライブラリをインストール

```sh
$ expo install react-native-webview
```

ArticleScreenに**react-native-webview**をインポートする。

```ArticleScreen.js
import {WebView} from "react-native-webview"
```

続いて、テキストで「This is Article Screen」と表示している部分をWebViewに置き換える。
指定するパラメータは表示したいWebページのURLを指定する。
今回はテストでExpoの公式ページを表示するように設定してみる。

```ArticleScreen.js
<SafeAreaView style={styles.container}>
      <WebView source={{ uri: 'https://expo.io' }} />
    </SafeAreaView>
```


### Screen間でパラメータを受け渡す

現状はArticleScreenに遷移すると、すべてExpoのページが表示されるようになっている為、ニュース記事毎のページを表示できる様に変更する。
そのために、HomeScreenからニュース毎のURLをArticleScreenに渡すようにする。

React NavigationでのScreen間のパラメータ受け渡し方法についてはReact Navigation公式ページの[Passing parameters to routes](https://reactnavigation.org/docs/params/)を参照。

実際のパラメータ指定方法は`navigation.navigate`に第二引数として渡したいパラメータを変数で指定する。

ニュース記事を表示するには"item"に格納されたAPIレスポンスのJSON内"article"の中に"url"という項目があるため、まずはHomeScreenで"article"をパラメータとして渡すように指定

```HomeScreen.js
onPress={() => navigation.navigate('Article', {article: item})}
```

パラメータの受け取り側はpropsに**route**というものがあり、`route.params`の様に展開することができる。
WebViewのURL指定部分には、"article.url"を指定する事でタップされたニュースのURLがWebViewで表示される。

```ArticleScreen.js
export default ArticleScreen = ({ route }) => {
  const { article } = route.params
  return (
    <SafeAreaView style={styles.container}>
      <WebView source={{ uri: article.url }} />
    </SafeAreaView>
  )
}
```

# クリップ機能の実装

気に入ったニュースの記事をクリップしておける機能を実装する。
ArticleScreenの記事詳細ページにはクリップボタンを用意し、クリップタブを設けクリップした記事一覧が閲覧できる様にする。
この実装には状態を管理するライブラリである**Redux**を使用する。
この機能はアプリの機能が多くなってきた時に必要となる。

画面構成は以下の様に、Homeと同階層のClipページを追加する構成となる。

- Home
    - Article
- Clip

## Reduxについて

Reduxとは、状態管理ツールである。

例えば、Articleのスクリーンで記事のクリップボタンがタップされた際、クリップボタンがタップされたという状態をClipのスクリーンに伝えなくてはならない。
その場合は、`Article -> Home -> Clip`とバケツリレーの様に状態を伝える方法が考えられるが実装が複雑で面倒。
また、ArticelとClipとで状態が二重に管理されることからバグも生まれやすい。
そういった問題をReduxで解決できる。

Reduxでは、状態を集中管理する**Store**というものが提供される。
Storeを利用すると、ArticleでクリップボタンがタップされるとReduxのStoreに状態が保存され、ClipからReduxのStoreを参照することで状態がわかる、という設計にできる。

### ComponentがStoreを更新するまでの流れ

1. **Component**が**Action**を**Dispatch**する
2. **Reducer**が、Storeの**Old State**、Actionから渡された**Payload**を元に新たなStateである**New State**を作成する。
3. New StateがStoreに反映されると、Componentにも状態が反映される

- **Store**
状態の集中管理機能

- **Reducer**
Storeを更新する為の機能

* **Dispatch**
日本語で"送信する"という意味だが、Reduxの世界では、Actionを**Dispatch**する。という様に言う。

* **Action**
Actionは以下２つの機能をもっている
    * **Type**
    Actionの種類を識別するための予め定義された文字列

    * **Payload**
    Actionを実行する時に使用される任意のデータ

## Reduxのインストール

Reduxを使用するにあたっては、以下２つのパッケージが必要になる。

- [redux](https://redux.js.org/introduction/getting-started)
- [react-redux](https://react-redux.js.org/introduction/quick-start)

それぞれターミナルからnpmインストールする。

```sh
$ npm install redux react-redux
```
package.jsonからインストールされた事が確認できる。

## Actionの実装

追加するAction

- **ADD_CLIP**
クリップを追加する

- **DELETE_CLIP**
クリップを削除する

まず、Redux関連のファイルを入れる`store`というフォルダを用意する。（プロジェクト内のルートにstoreフォルダを作成する）
storeフォルダの中に`actions`フォルダを作成する。
今回作成するClip機能は各ユーザーが持つ情報の為、actionsフォルダの中に`user.js`を作成する。

user.jsにはアクションを定義する。
`addClip`アクションと`deleteClip`アクションを作成する。
引数には`clip`の情報が渡ってくると仮定する。このclipは後で実装する。
リターンする内容には**Type**と**Payload**を指定する。

```user.js
export const addClip = ({clip}) => {
    return {
        type: 'ADD_CLIP',
        clip,
// Payload部分はclip: clipの省略形。同名の場合は省略可能
    }
}

export const deleteClip = ({clip}) => {
    return {
        type: 'DELETE_CLIP',
        clip,
    }
}
```

作成したアクションは後ほど**Reducer**で使用される。

## Reducerの実装

Reducerは前回のState(**Old State)と、**Action**を受け取って、新しいState(**New State**)をリターンする関数。

まず、`store`フォルダの中に`reducers`フォルダを作成し、その中に`user.js`を作成する。

user.jsには、単純な関数である**reducer**を定義し、使用されるOld StateとActionにあたる引数を指定する。

`state`には初期値が必要のため、`initialState`として`clips`という空の配列を定義し、stateに代入する。

`action`はtype毎(ADD_CLIP / DELETE_CLIP)に処理を分けるよう、switch文を使用して書く。
想定しないtypeが渡って来た場合は何もしないのでdefaultは前回と同じOld Stateをreturnするようにする。

`ADD_CLIP`の処理内容には、前回の`state.clips`にactionで渡ってきたclipを追加するようにしたいので、前回のstateをスプレッド関数(**...state,**)で展開し、前回のstate + actionで渡ってきた新しいstate、といった内容に`clips`の中身を更新する。

`DELETE_CLIP`の処理内容には、前回のstateから**filter**を使用し、条件に当てはまる要素を除いた配列を返す。
条件にはactionのclipのURLと、配列clipsの中のURLが一致した場合、一致したclipを削除し、新たなclipsを作成する、という処理にする

```user.js
const initialState = {
  clips: [],
};

const reducer = (state = initialState, action) => {
  switch (action.type) {
    case "ADD_CLIP":
      return {
        ...state,
        clips: [...state.clips, action.clip],
      };
    case "DELETE_CLIP":
      return {
        ...state,
        clips: state.clips.filter(clip => clip.url != action.clip.url),
      };
    default:
      return state;
  }
};

export default reducer;

```

## Storeの実装

StoreはJSONのようなツリー構造になっている。
今回の場合、`store`の中に`user`があって、`clips[]`を持っている。

Storeの実装にはまず、作成した`store`フォルダの中に`index.js`を作成する。

index.jsには、上記のようなツリー構造を作るために、reduxの機能の**combineReducers**というライブラリを使用するのでインポートする。

続いて、ツリー構造を定義する。
ツリーの中には上で作成したReducerである**userReducer**を定義する。（インポートしておく）

```index.js
import {combineReducers} from 'redux';
import userReducer from './reducers/user';

const rootReducer = combineReducers ({
    user: userReducer
})
```

次にrootのReducer自体を定義する。
そのために**createStore**というライブラリをインポートしておく。

```index.js
import { createStore, combineReducers } from "redux";
import userReducer from "./reducers/user";

const rootReducer = combineReducers({
  user: userReducer,
});

const store = createStore(rootReducer);

export default store;
```

これでStoreの定義が完了。
このStoreを使用するためにApp.jsに変更を加える。

react-reduxの**Provider**というライブラリと、作成したstoreをインポートする。

既存の`<AppNavigator>`は`<Provider`>で囲み、パラメータとしてstoreを渡すように変更する。

```App.js
import React from 'react'
import AppNavigator from './navigation/AppNavigator'
import { Provider } from 'react-redux'
import store from './store'

export default function App() {
  return (
    <Provider store={store}>
    <AppNavigator />
    </Provider>
  );
}
```

## デバッガーのインストール


### react-native-debugger

Reduxを用いる開発で、状態がどのように変更されているかの確認に、[react-native-debugger](https://github.com/jhen0409/react-native-debugger)を使用するため、Macにインストールする。

```sh
$ brew update && brew cask install react-native-debugger
```

### redux-devtools-extension

つぎに[redux-devtools-extension](https://github.com/zalmoxisus/redux-devtools-extension)をnpmインストールする。

```sh
$ npm install --save redux-devtools-extension
```

### デバッガを使うためのコード修正

`composeWithDevTools`をStoreの`index.js`にインポートし、`createStore`の第2引数に付与する。

```index.js
import { createStore, combineReducers } from "redux";
import userReducer from "./reducers/user";
import { composeWithDevTools } from "redux-devtools-extension";

const rootReducer = combineReducers({
  user: userReducer,
});

const store = createStore(rootReducer, composeWithDevTools());

export default store;
```

### デバッガとの接続

はじめに、Mac上でインストールした**React Native Debugger.app**デスクトップアプリを起動する。
デフォルトで`Port8081`が使用されるが、Expoでは`19001`を使用するので起動し直す。
メニューから`"Debugger" -> "New Window"`を開き、ポートが`19001`になっていることを確認し、"Confirm"をクリック。

Expoと接続するには、シミュレータから`Command + D`を開き"Debug Remote JS"をクリック。

これでシミュレータとデバッガツールが接続される。
作成したStoreのclips[]などツリー上で確認できる

## ActionのDispatch

ReduxのStoreにクリップした記事の情報を保存したり、削除したりできる機能を実装する。

### 仮のボタンを作成

Articleスクリーンの上側にボタンを表示する。
ボタンを表示するためにまず、ArticleScreen.jsに`TouchableOpacity`をインポートする。

"SafeAreaView"の中に"ADD_CLIP"、"DELETE_CLIP"それぞれのボタンを作成する。

```ArticleScreen.js
import React from 'react'
import { StyleSheet, SafeAreaView, Text, TouchableOpacity } from 'react-native'
import { WebView } from 'react-native-webview'

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
  },
})

export default ArticleScreen = ({ route }) => {
  const { article } = route.params
  return (
    <SafeAreaView style={styles.container}>
      <TouchableOpacity onPress={() => {}}>
        <Text style={{margin: 10, fontSize:30}}>ADD_CLIP</Text>
      </TouchableOpacity>
      <TouchableOpacity onPress={() => {}}>
        <Text style={{margin: 10, fontSize:30}}>DELETE_CLIP</Text>
      </TouchableOpacity>
      <WebView source={{ uri: article.url }} />
    </SafeAreaView>
  )
}
```

次に、"ADD_CLIP"ボタンがタップされた時にクリップに保存する処理を実装する。
そのためにはArtibleScreenからStoreに接続する必要がある。
まずはComponentであるArticleScreenがDispatchを使用できるようにする。

React Reduxの[Hooks](https://react-redux.js.org/api/hooks)ページから、**useDispatch()**を参照する。

まず、useDispatchをインポートする。
次に、実行するAction（作成したaddClipアクション）をインポートする。

サンプルと同じ様に、useDispatchの初期化を行い、onPress時のActionを指定する。
ADD_CLIPの引数である"clip"にはAPIレスポンス値の"article"を代入する。

```ArticleScreen.js
import React from 'react'
import { StyleSheet, SafeAreaView, Text, TouchableOpacity } from 'react-native'
import { WebView } from 'react-native-webview'
import { useDispatch } from 'react-redux'
import { addClip } from '../store/actions/user'

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
  },
})

export default ArticleScreen = ({ route }) => {
  const { article } = route.params

  const dispatch = useDispatch()
  return (
    <SafeAreaView style={styles.container}>
      <TouchableOpacity
        onPress={() => {
          dispatch(addClip({ clip: article }))
        }}
      >
        <Text style={{ margin: 10, fontSize: 30 }}>ADD_CLIP</Text>
      </TouchableOpacity>
      <TouchableOpacity onPress={() => {}}>
        <Text style={{ margin: 10, fontSize: 30 }}>DELETE_CLIP</Text>
      </TouchableOpacity>
      <WebView source={{ uri: article.url }} />
    </SafeAreaView>
  )
}
```

### 動作確認

ADD_CLIPが正しく動作しているかデバッガを使って確認する。
シミュレータからADD_CLIPをタップすると、デバッガのStateタブからclips配列にNewsAPIのレスポンス値(articleの内容)が格納されていることが確認できる。

同じようにDELETE_CLIPについても実装する。
まず、ArticleScreen.jsに`deleteClip`をインポートする。

```ArticleScreen.js
import React from 'react'
import { StyleSheet, SafeAreaView, Text, TouchableOpacity } from 'react-native'
import { WebView } from 'react-native-webview'
import { useDispatch } from 'react-redux'
import { addClip, deleteClip } from '../store/actions/user'

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
  },
})

export default ArticleScreen = ({ route }) => {
  const { article } = route.params

  const dispatch = useDispatch()
  return (
    <SafeAreaView style={styles.container}>
      <TouchableOpacity
        onPress={() => {
          dispatch(addClip({ clip: article }))
        }}
      >
        <Text style={{ margin: 10, fontSize: 30 }}>ADD_CLIP</Text>
      </TouchableOpacity>
      <TouchableOpacity
        onPress={() => {
          dispatch(deleteClip({ clip: articles }))
        }}
      >
        <Text style={{ margin: 10, fontSize: 30 }}>DELETE_CLIP</Text>
      </TouchableOpacity>
      <WebView source={{ uri: article.url }} />
    </SafeAreaView>
  )
}
```

これで同じ様にニュース記事からDELETE_CLIPをたっぷするとデバッガツールにて配列に格納された値が削除される事を確認できる。


## 画面の下にタブを表示

Storeの情報を実際にComponentに表示する処理を実装する。
具体的にはクリップ一覧ページを新たに作成するため、`Home`タブと`Clip`タブを用意する。

### createBottomTabNavigator

React Navigationで画面下のタブを実装するには[createBottomTabNavigator](https://reactnavigation.org/docs/bottom-tab-navigator/)を使用する。

インストール

```sh
$ npm install @react-navigation/bottom-tabs
```

### Navigationの構成について

**TabNavigator**の導入により、いままで`AppNavigator`ひとつだったものが、`HomeStack`と`ClipStack`というように分けて管理される。

それぞれ以下のような構造とする。

- **AppNavigator**
    - **TabNavigator**
        - ~~AppNavigator~~
        - **HomeStack**
            - StackNavigator
                - HomeScreen
                - ArticleScreen

        - **ClipStack**
            - **StackNavigator**
                - **ClipScreen**


### Homeをタブで表示する

まず、AppNavigator.jsにて**createBottomTabNavigator**をインポートし、タブを定義する。

既存の"Stack.Navigator"を`HomeStack`として新たに定義し、"NavigationContainer"に**Tab.Navigator**を入れ、**Tab.Screen**にてHomeタブを定義する。

```AppNavigator.js
import React from 'react'
import { NavigationContainer } from '@react-navigation/native'
import { createStackNavigator } from '@react-navigation/stack'
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'
import HomeScreen from '../screens/HomeScreen'
import ArticleScreen from '../screens/ArticleScreen'

const Stack = createStackNavigator()
const Tab = createBottomTabNavigator()

const HomeStack = () => {
  return (
    <Stack.Navigator>
      <Stack.Screen
        name="Home"
        component={HomeScreen}
        options={{ headerShown: false }}
      />
      <Stack.Screen name="Article" component={ArticleScreen} />
    </Stack.Navigator>
  )
}

export default AppNavigator = () => {
  return (
    <NavigationContainer>
      <Tab.Navigator>
        <Tab.Screen name="Home" component={HomeStack} />
      </Tab.Navigator>
    </NavigationContainer>
  )
}
```

これで画面下に"Home"タブのみ表示される。

### ClipScreenの作成

プロジェクト内の"`screens`フォルダに`ClipScreen.js`ファイルを作成する。

```CrepScreen.js
import React from 'react'
import { StyleSheet, SafeAreaView, Text } from 'react-native'

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
  },
})

export default ClipScreen = () => {
  return (
    <SafeAreaView style={styles.container}>
      <Text>Clip Screen</Text>
    </SafeAreaView>
  )
}
```

`AppNavigator.js`にて作成した`ClipScreen`をインポートし、Homeタブと同様にタブを定義し、タブの要素に追加する。

```AppNavigator.js
import React from 'react'
import { NavigationContainer } from '@react-navigation/native'
import { createStackNavigator } from '@react-navigation/stack'
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'
import HomeScreen from '../screens/HomeScreen'
import ArticleScreen from '../screens/ArticleScreen'
import ClipScreen from '../screens/ClipScreen'

const Stack = createStackNavigator()
const Tab = createBottomTabNavigator()

const HomeStack = () => {
  return (
    <Stack.Navigator>
      <Stack.Screen
        name="Home"
        component={HomeScreen}
        options={{ headerShown: false }}
      />
      <Stack.Screen name="Article" component={ArticleScreen} />
    </Stack.Navigator>
  )
}

const ClipStack = () => {
  return (
    <Stack.Navigator>
      <Stack.Screen name="Clip" component={ClipScreen} />
    </Stack.Navigator>
  )
}

export default AppNavigator = () => {
  return (
    <NavigationContainer>
      <Tab.Navigator>
        <Tab.Screen name="Home" component={HomeStack} />
        <Tab.Screen name="Clip" component={ClipStack} />
      </Tab.Navigator>
    </NavigationContainer>
  )
}
```
これでHomeタブにつづきClipタブが機能するようになった。

## タブにアイコンを表示する

今までの状態ではタブにテキストしか表示されないため、アイコンを設定する。

タブのカスタマイズはReact Navigation/Tab Navigationの[Customizing the appearance](https://reactnavigation.org/docs/tab-based-navigation/)を参考に実装していく。

### Expoでアイコンを使用する

Expoでアイコンを使用するときには、[expo/vector-icons](https://docs.expo.io/guides/icons/)を使用する。
実際に使用できるアイコンは[ギャラリー](https://icons.expo.fyi/)から確認できる。

### 実装

AppNavigator.jsにてアイコンをインポートする。
今回は**FontAwesome**を使用する。

次に、`screenOption`を定義し、中にタブバーアイコンを定義する。定義内容は[サンプル](https://reactnavigation.org/docs/tab-based-navigation/)を参照。
内容はタブの名前毎にタブのアイコンを区分けしJSXをリターンしている。

アイコンを定義したら**Tab.Navigator**のOptionに反映する。

```AppNavigator.js
import React from 'react'
import { NavigationContainer } from '@react-navigation/native'
import { createStackNavigator } from '@react-navigation/stack'
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'
import { FontAwesome } from '@expo/vector-icons'
import HomeScreen from '../screens/HomeScreen'
import ArticleScreen from '../screens/ArticleScreen'
import ClipScreen from '../screens/ClipScreen'

const Stack = createStackNavigator()
const Tab = createBottomTabNavigator()

const HomeStack = () => {
  return (
    <Stack.Navigator>
      <Stack.Screen
        name="Home"
        component={HomeScreen}
        options={{ headerShown: false }}
      />
      <Stack.Screen name="Article" component={ArticleScreen} />
    </Stack.Navigator>
  )
}

const ClipStack = () => {
  return (
    <Stack.Navigator>
      <Stack.Screen name="Clip" component={ClipScreen} />
    </Stack.Navigator>
  )
}

const screenOption = ({ route }) => ({
  tabBarIcon: ({ color, size }) => {
    let iconName

    if (route.name === 'Home') {
      iconName = 'home'
    } else if (route.name === 'Clip') {
      iconName = 'bookmark'
    }
    return <FontAwesome name={iconName} size={size} color={color} />
  },
})

export default AppNavigator = () => {
  return (
    <NavigationContainer>
      <Tab.Navigator screenOptions={screenOption}>
        <Tab.Screen name="Home" component={HomeStack} />
        <Tab.Screen name="Clip" component={ClipStack} />
      </Tab.Navigator>
    </NavigationContainer>
  )
}
```

これでタブの実装が完了となる。

## Reduxから状態を取得して画面に表示する

現状ClipScreenは空になっているので、Clipした記事が一覧表示されるようにする。
Storeの情報をComponentに送るためには、React reduxの[useSelector](https://react-redux.js.org/api/hooks)を使用する。

### useSelector実装

Storeから情報を取得したいComponentであるClipScreen.jsにて、**useSelector**をインポートする。
また、一覧表示するために**FlatList**もインポートする。

次にstateの件数をカウントするデータ`user.counter`を読み込むため`user`としてを定義する。
定義した"user"はclips配列に格納。

続いてFlatListでclipsのデータを表示する。
ListItemはHomeScreenからコピー。インポートもする。

ListItemメソッド内で`navigation`を使用する為、ClipScreenにおいてもnavigationを引数に設定する。

最後に、`keyExtractor`を設定する

```ClipScreen.js
import React from 'react'
import { useSelector } from 'react-redux'
import { StyleSheet, SafeAreaView, Text, FlatList } from 'react-native'
import ListItem from '../components/ListItem'

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
  },
})

export default ClipScreen = ({ navigation }) => {
  const user = useSelector((state) => state.user)
  const { clips } = user
  return (
    <SafeAreaView style={styles.container}>
      <FlatList
        data={clips}
        renderItem={({ item }) => (
          <ListItem
            imageUrl={item.urlToImage}
            title={item.title}
            author={item.author}
            onPress={() => navigation.navigate('Article', { article: item })}
          />
        )}
        keyExtractor={(item, index) => index.toString()}
      />
    </SafeAreaView>
  )
}
```

これで記事からADD_CLIPをタップすると、Clipタブに記事が表示されるようになった。


## クリップ一覧から記事詳細への画面遷移

現状ではクリップ一覧から記事詳細をタップすると、Homeタブの記事詳細に画面が遷移する。
これはArticleScreenがHomeStackの下にしか存在しないため、ClipStackの下にもArtibleScreenを追加する必要がある。

その為には、AppNavigator.jsの`ClipStack`にArticleページを追加で定義する。(HomeStackの定義からコピーでOK)

```AppNavigator.js
const ClipStack = () => {
  return (
    <Stack.Navigator>
      <Stack.Screen name="Clip" component={ClipScreen} />
      <Stack.Screen name="Article" component={ArticleScreen} />
    </Stack.Navigator>
  )
}
```

## クリップボタンにアイコンを表示

現状クリップボタンは"ADD_CLIP"と"DELETE_CLIP"というテキストになっているので、アイコンを設定し、アイコンがタップされるとON/OFFが切り替わるような機能を追加する。

### ClipのON/OFF判定

Store上にあるClipの情報と比較して、対象の記事がClipされているかどうかを判断する。
この為には"ArticleScreen.js"にてClipScreenで実装したのと同じように**useSelectore**を使用する。


次にクリップされているか判定する関数`isClipped`を定義する。
この関数は"clips"をリターンする。
また、配列(clips)の中に、ある情報が入っているかを判定するには**some**を使用する(clips.some)

今回はclipのURLがArticleのURLと等しいかをチェックする。

```ArticleScreen.js
import { useDispatch, useSelector } from 'react-redux'

...

export default ArticleScreen = ({ route }) => {
  const { article } = route.params

  const dispatch = useDispatch()

  const user = useSelector(state => state.user);
  const {clips} = user;

  const isClipped = () => {
    return clips.some(clip => crip.url === article.url)
  }
```

### Clipボタンの作成

Clipボタンは別のComponentに分けて作成する。
`components`フォルダに`ClipButton.js`を作成する。

`FontAwesome`と`TouchableOpacity`を利用する。
**enabled**による判定でFontAwesomeのアイコンを切り替えるように指定する。

```ClipButton.js
import React from 'react'
import { StyleSheet, TouchableOpacity } from 'react-native'
import { FontAwesome } from '@expo/vector-icons'

const styles = StyleSheet.create({
  container: {
    padding: 5,
  },
})

const ClipButton = ({ onPress, enabled }) => {
  const name = enabled ? 'bookmark' : 'bookmark-o'
  return (
    <TouchableOpacity onPress={onPress} style={styles.container}>
      <FontAwesome name={name} size={40} color="gray" />
    </TouchableOpacity>
  )
}

export default ClipButton
```


### Clipボタンの配置

ArticleScreenに作成したComponentの`ClipButton`をインポートする。
SafeAreaView内に**ClipButton**を表示する。

onPress時には`toggleClip`というメソッドを呼び出すようにし、"toggleClip"というメソッドは、
「もし、この記事がクリップされていた場合は（isClipped）クリップを削除する」、「もしこの記事がクリップされていなかったらクリップに追加」とするように`if.else`を用いて新規作成する。

enableには`isClipped`を渡し、不要になった他のボタン（ADD_CLIP、DELETE_CLIP）は削除する。


```ArticleScreen.js
import React from 'react'
import { StyleSheet, SafeAreaView, Text, TouchableOpacity } from 'react-native'
import { WebView } from 'react-native-webview'
import { useDispatch, useSelector } from 'react-redux'
import { addClip, deleteClip } from '../store/actions/user'
import ClipButton from '../components/ClipButton'

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
  },
})

export default ArticleScreen = ({ route }) => {
  const { article } = route.params

  const dispatch = useDispatch()

  const user = useSelector((state) => state.user)
  const { clips } = user

  const isClipped = () => {
    return clips.some((clip) => clip.url === article.url)
  }

  const toggleClip = () => {
    if (isClipped()) {
      dispatch(deleteClip({ clip: article }))
    } else {
      dispatch(addClip({ clip: article }))
    }
  }

  return (
    <SafeAreaView style={styles.container}>
      <ClipButton onPress={toggleClip} enabled={isClipped()} />
      <WebView source={{ uri: article.url }} />
    </SafeAreaView>
  )
}
```

これで記事詳細からクリップボタンによる追加/削除が可能となる。

## Reduxを永続化して端末に保存する

現状、保存したクリップはアプリを再起動すると消える。
これは今Reduxがメモリにアプリのデータを保存しているからアプリを再起動すると消えるものである。
これを永続化させるためにメモリではなく、スマホのストレージに保存するようにする。

### redux-persist

ReduxのStateをスマホのストレージに保存するには、[redux-persist](https://github.com/rt2zz/redux-persist)を使用する。

### インストール

```sh
npm install redux-persist
```

### redux-persistの設定

`Store/index.js`を’開き、**redux-persist**から、**persistReducer**と**persistStore**をインポートする。
また、**react-native**から**{AsyncStorage}**もインポートしておく。

続いて`persistConfig`としてpersistの設定を書く。
保存先には**AsyncStorage**というライブラリを使用する。
Reduxのツリー全てを保存するか、一部を保存するかの設定では、今回はホワイトリスト形式で一部(user)のStateを保存するようにする。

また、`persistedReducer`として、インポートした**persistReducer**に作成した**persistConfig**ともともとあった**rootReducer**を指定する。

次に、`createStore`の中身を書き換える。
今まで、`rootReducer`だったものを、設定した`persistedReducer`にする。

最後に、**persistor**をexportする。

```index.js
import { createStore, combineReducers } from 'redux'
import userReducer from './reducers/user'
import { composeWithDevTools } from 'redux-devtools-extension'
import { persistReducer, persistStore } from 'redux-persist'
import { AsyncStorage } from 'react-native'

const rootReducer = combineReducers({
  user: userReducer,
})

const persistConfig = {
  key: 'root',
  storage: AsyncStorage,
  whitelist: ['user'],
}

const persistedReducer = persistReducer(persistConfig, rootReducer)

const store = createStore(persistedReducer, composeWithDevTools())

export const persistor = persistStore(store)
export default store

```

### App.jsの改修

App.jsも変更する必要がある。
まず、**redux-persist/integration/react**から**PersistGate**をインポートする。
また、storeの中に作成した`persistor`もインポートする。

最後にpersistReducerが使えるように<Provider>の中に<PersistGate>を定義する。
設定内容は公式のリファレンスを参照。

```App.js
import React from 'react'
import AppNavigator from './navigation/AppNavigator'
import { Provider } from 'react-redux'
import store, { persistor } from './store'
import { PersistGate } from 'redux-persist/integration/react'

export default function App() {
  return (
    <Provider store={store}>
      <PersistGate loading={null} persistor={persistor}>
        <AppNavigator />
      </PersistGate>
    </Provider>
  )
}
```

これでアプリを再起動してもクリップが保存されるようになった。

# アプリの仕上げ

以下のような細かい仕上げを実装する。

- スプラッシュスクリーン
    - アプリが起動した時に表示される画面
- アプリアイコン
    - ホーム画面に表示されるアイコン
- インジケーター
    - ローディング中のクルクル表示
　

## スプラッシュスクリーン


- Expoのスプラッシュスクリーン作成についての[リファレンス](https://docs.expo.io/guides/splash-screens/)
- スプラッシュスクリーンについての[ガイドライン](https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/launch-screen/)

基本的にはスプラッシュイメージを`app.json`に指定するのみ。

スプラッシュスクリーンの画像を作成する際には`1242×2436ピクセル`の画像を作る。
スマホによる画面サイズの大きさに差異があるが、**splash.resizeMode**によって画面サイズによって自動調整することができる。

使用するスプラッシュイメージ及びアイコンイメージは、`asetts`フォルダ内に配置する。

配置したスプラッシュイメージは、`app.json`内で`"splash"/"image"`でasettsにおいたイメージファイルPATHを指定する。
（デフォルトでイメージファイルがあり、PATHもasetts/splash.pngに指定されている）

## アプリのアイコン

- 公式のアプリアイコンについての[リファレンス](https://docs.expo.io/guides/app-icons/)

- アプリアイコンについての[ガイドライン](https://developer.apple.com/design/human-interface-guidelines/ios/icons-and-images/app-icon/)

スプラッシュスクリーンと同様にアイコンイメージファイルを`asetts`フォルダに配置し、`app.json'内の`icon`で指定する。

アイコンの場合、iOS用とAndoroid用で分けて指定する事も共有で指定することも可能。

アイコン画像のベストプラクティスとしてiOS用は`1024×1024`のサイズが推奨となっている。

## ローディング中のインジケーター

起動したタイミングのAPIコール中などにローディング中にクルクル回るインジケーターをComponentとして実装する。

`component`フォルダに`Loading.js`を作成する。

[ActivityIndivator](https://reactnative.dev/docs/activityindicator?redirected)というライブラリを使用する。

```Loading.js
import React from 'react'
import { View, StyleSheet, ActivityIndicator } from 'react-native'

const styles = StyleSheet.create({
  container: {
    position: 'absolute',
    top: 0,
    left: 0,
    width: '100%',
    height: '100%',
    backgroundColor: 'rgba(255, 255, 255, 0.5)',
    alignItems: 'center',
    justifyContent: 'center',
  },
})

const Loading = () => {
  return (
    <View style={styles.container}>
      <ActivityIndicator size="large" />
    </View>
  )
}

export default Loading
```


HomeScreenから作成したLoadingコンポーネントをインポートし、表示できるように、<SafeAreaView>内に`<Loading/>`を追加する。
また、ローディング中だけ表示するように、`laoding, setLoading`を定義し、デフォルト値を"false"とする。
```js
const [loading, setLoading] = useState(false)
```

この条件で表示/非表示を切り替えるため、`<Loading>`を以下のようにする。

```js
{loading && <Loading />}
```

続いて、"loading"のフラグを"setLoading"を呼ぶことで指定する。
LoadingがTrueとなるタイミングは、"fetchArticle"が実行されるタイミングなので、"fetchArticle"の中に"setLoading"を入れ込む。

fetchする前に"setLoading"を"True"にしてローディングが始まる。
fetchArticleが終わる(ローディングが終わる)部分でsetLoadingを"False"にする。


```js
  const fetchArticles = async () => {
    setLoading(true)
    try {
      const response = await axios.get(URL)
      setArticles(response.data.articles)
      console.log(response)
    } catch (error) {
      console.error(error)
    }
    setLoading(false)
  }
```

上の変更を加えると以下になるが、これで読込中だけインジケーターが表示されるようになる。

```HomeScreen.js
import React, { useState, useEffect } from 'react'
import { StyleSheet, View, FlatList, SafeAreaView } from 'react-native'
import ListItem from '../components/ListItem'
import Loading from '../components/Loading'
import Constants from 'expo-constants'
import axios from 'axios'

const URL = `http://newsapi.org/v2/top-headlines?country=jp&apiKey=${Constants.manifest.extra.newsApiKey}`

export default HomeScreen = ({ navigation }) => {
  const [articles, setArticles] = useState([])
  const [loading, setLoading] = useState(false)

  useEffect(() => {
    fetchArticles()
  }, [])

  const fetchArticles = async () => {
    setLoading(true)
    try {
      const response = await axios.get(URL)
      setArticles(response.data.articles)
      console.log(response)
    } catch (error) {
      console.error(error)
    }
    setLoading(false)
  }

  return (
    <SafeAreaView style={styles.container}>
      <FlatList
        data={articles}
        renderItem={({ item }) => (
          <ListItem
            imageUrl={item.urlToImage}
            title={item.title}
            author={item.author}
            onPress={() => navigation.navigate('Article', { article: item })}
          />
        )}
        keyExtractor={(item, index) => index.toString()}
      />
      {loading && <Loading />}
    </SafeAreaView>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
  },
  itemCotainer: {
    height: 100,
    width: '100%',
    borderColor: 'gray',
    borderWidth: 1,
    flexDirection: 'row',
  },
  leftContainer: {
    width: 100,
  },
  rightContainer: {
    flex: 1,
    flexDirection: 'column',
    padding: 10,
    justifyContent: 'space-between',
  },
  text: {
    fontSize: 16,
  },
  subText: {
    fontSize: 12,
    color: 'gray',
  },
})
```

### ArticleScreenのloading中の表示

HomeScreen以外に、WebViewにてブラウザで記事を読み込んでいる瞬間もインジケーターを追加する。

WebViewについては、[**renderLoading**](https://github.com/react-native-community/react-native-webview/blob/master/docs/Reference.md)
を利用してもう少し簡単に実装可能。

```ArticleScreen.js
<WebView
        source={{ uri: article.url }}
        startInLoadingState={true}
        renderLoading={() => <Loading />}
      />
```

上を追加するのみで、WebView読み込み中のインジケーターを設定することができた。

# 作ったアプリをストアに公開

- 社内や知人にテスト用アプリを配布
- iOSのストア申請
- Androidのストア申請

## Expoでアプリを配布

### 社内で動作確認

テスト用アプリを使用したい場合、**Expo Clientアプリ**を使用する。
開発者は`expo publish`コマンドによりExpoサーバーにアプリを送信し、テストアプリ利用者は開発者と同じExpoアカウントでExpo Clientアプリにログインしてテスト用アプリを使用する。

### ストア公開

App StoreやGoogle Playに公開する。
アプリのビルドには以下コマンド。

```sh
$ expo build:ios
$ expo build:android
```
jsバンドルがExpoサーバーにアップロードされるのと同時に、iOS=ipaファイル、Android=apkファイルが自動生成される。
ビルドが完了すると開発者に通知され、ipa、apkファイルをダウンロード可能となる。

これらのファイルを各Storeに申請することでアプリが公開される。


## 社内や知人にアプリを配布

Expoのドキュメント[Publishing](https://docs.expo.io/workflow/publishing/)を見る。

ブラウザの**Publishボタン**をクリックするか、以下のコマンドでPublishする。

```sh
$ expo publish
```

ビルドが完了するとURLが発行されるのでアクセスしてみると、Publishした自分のアプリが確認できる。

他の端末でテスト用アプリを使う場合は、ブラウザに表示されるQRコードをスキャンし、Expo Clientアプリで起動できる。
Androidの場合はどのExpoアカウントでも使用できるが、iOSの場合は対象アプリの開発者アカウントでないと使用できない。



## release-channel

ストア公開時にExpoにアップするJSバンドルはテスト用と本番用と分けて管理したほうがよい。
その際使用するのが、**release-channel**

`expo publish`するさいにオプションとしてリリースチャンネル名を指定することでExpoサーバー上でJSバンドルが分けて管理される。
指定しない場合は、defaultとして管理される。

```sh
$ expo publish --release-channel production
```


## iOSの申請用アプリをビルド

iOSのAppStore申請用のビルド方法
[手順](https://docs.expo.io/distribution/building-standalone-apps/)

**app.json**内の`"ios:""bundleIdentifier"`を設定する。
ユニークな値を設定する必要があり、一般的にはドメイン名を逆から指定する。
`"supportsTablet"`にてタブレット端末に対応するかどうかを"True/False"で選択できる。

以下コマンドでビルドする。

```sh
$ expo build:ios --release-channel production
```
ビルドは対話形式になる。

- Apple ID
- パスワード
- 所属チーム
- iOSの証明書などのクレデンシャ
- ルをExpoで管理するか、自分で管理するか
- Distribution用のCertificateをExpoで用意するか、自分で用意するか
- プッシュ通知のサービスキーをExpoで管理するか、自分で管理するか

上記は初回のみで、次回からは必要ない。

途中、ビルドの進行状況が確認できるURLが発行され、ブラウザからビルドログが確認できる。

完了すると、ipaファイルのダウンロードリンクが発行される。

このipaファイルを使用してAppStoreに申請する。

あとは、AppleのAppStore Connectから申請する。
このとき、MacのAppStoreから**Transporter**というアプリが必要になるのでインストールする。

## Androidのの申請用をビルド

iOSと同じ様に**app.json**の`"android": "Package"`にパッケージ名を指定する。

ビルドコマンドは以下

```sh
$ expo build:android --release-channel production
```

対話形式を終えるとビルドが始まり、apkファイルが発行される。
これをGoogle Play Consoleでアプリを登録する。


# 補足

## Expo SDKのバージョンアップ
Expoは最新より5バージョン前のSDKしかサポートされないため、なるべくこまめに最新に追従していくのがおすすめ。
更新情報は[Expoのブログ](https://blog.expo.io/)に[このような記事](https://blog.expo.io/expo-sdk-37-is-now-available-dd5770f066a6)で公開される。
内容は、更新された内容や詳細が書かれる。ベースになるReact Nativeのバージョンアップや機能追加など。

アップデート方法は下部に手順が記載されているが、以下のコマンドを叩く。
最新のexpo-cliではない場合に備えてnpmインストールから。

```sh
$ npm -i -g expo-cli
$ expo upgrade
```
## トラブルシューティング

- `expo start`でプロジェクトが起動した時にエラーが発生する
    - Xcodeを最新にしてみる(iOSシミュレータ利用の場合)
    - iOSシミュレータ内のExpo Clientを最新にしてみる。`expo client:install:ios`でシミュレータに最新のExpo Clientがインストールされる
    - `expo start -c`で起動してみる