TypeScript for React NativeでAmplify開発する

React Native を使った Amplify のチュートリアルを Typescript で進めたときの備忘録です。

前提として

  • macOS Catalina
  • Node.js 14
  • Yarn
  • Visual Studio Code
  • React Native CLI

https://docs.amplify.aws/start/q/integration/react-native のチュートリアルを元に Typescript で実装していきます。

以降の章では、チュートリアルのステップ毎に変更した箇所を挙げていきます。

そのため、特に記載がない場合はチュートリアルに従ってください。

Prerequisites

このステップは特に変更箇所はないので、チュートリアルに従ってください。

Set up fullstack project

React Nativce のプロジェクトを作成する場合は、次のコマンドに変更します。

npx react-native init RNAmplify --template react-native-template-typescript

index.js を index.ts に変更する

index.js を index.ts にリネームすることで、app.json の読み込みがエラーになります。

tsconfig.json に以下を追加して解消してください。

{
  "compilerOptions": {
    "resolveJsonModule": true, // add
  }
}

Connect API and database to the app

Deploy the API

amplify api pushする際の質問? Choose the code generation language targetは typescript を選択します。

これで graphql ディレクトリに作成されるファイルが typescript 版となります。

(Optional) Test your API

amplify mock apiを実行するには Java がインストールされていなければなりません。

今回は AWS ということもあるので、Corretto をインストールします。

macOS の場合、Corretto は brew でインストールできるので、以下のコマンドを実行します。

brew cask install corretto

Connect frontend to API

この章では API の呼び出しを TypeScript で実装するため、いくつか変更を加えていきます。

import React, { useState, useEffect } from 'react';
// aws-amplifyより@aws-amplifyが良い
import { API, graphqlOperation, GraphQLResult } from '@aws-amplify/api';
import { listTodos } from './src/graphql/queries';
import { ListTodosQuery } from 'src/API';
import { createTodo } from 'src/graphql/mutations';
import { StyleSheet, Button, TextInput, View, Text } from 'react-native';

type Todo = {
  id: string;
  name: string;
  description: string;
};
const initialState: Todo = { id: '', name: '', description: '' };

const App: React.FC = () => {
  const [formState, setFormState] = useState(initialState);
  const [todos, setTodos] = useState<Todo[]>([]);

  useEffect(() => {
    fetchTodos();
  }, []);

  function setInput(key: string, value: string) {
    setFormState({ ...formState, [key]: value });
  }

  async function fetchTodos() {
    try {
      // Unionの戻りをGraphQLResult<ListTodosQueyr>で型アサーションする
      const todoData = (await API.graphql(
        graphqlOperation(listTodos),
      )) as GraphQLResult<ListTodosQuery>;

      // Stateの型(Todo[])に合わせて型アサーションをする
      const todos = todoData?.data?.listTodos?.items as Todo[];
      setTodos(todos);
    } catch (err) {
      console.log('error fetching todos');
    }
  }

  async function addTodo() {
    try {
      const todo = { ...formState };
      setTodos([...todos, todo]);
      setFormState(initialState);
      await API.graphql(graphqlOperation(createTodo, { input: todo }));
    } catch (err) {
      console.log('error creating todo:', err);
    }
  }

  return (
    <View style={styles.container}>
      <TextInput
        onChangeText={(val) => setInput('name', val)}
        style={styles.input}
        value={formState.name}
        placeholder="Name"
      />
      <TextInput
        onChangeText={(val) => setInput('description', val)}
        style={styles.input}
        value={formState.description}
        placeholder="Description"
      />
      <Button title="Create Todo" onPress={addTodo} />
      {todos.map((todo, index) => (
        <View key={todo.id ? todo.id : index} style={styles.todo}>
          <Text style={styles.todoName}>{todo.name}</Text>
          <Text>{todo.description}</Text>
        </View>
      ))}
    </View>
  );
};

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: 'center', padding: 20 },
  todo: { marginBottom: 15 },
  input: { height: 50, backgroundColor: '#ddd', marginBottom: 10, padding: 8 },
  todoName: { fontSize: 18 },
});

export default App;

GraphQL から取得するあたりで型を合わせることが難しく、import するモジュールは基本的に@aws-amplifyを使ったほうがすっきりしていました。

それと型アサーションを多用することで、タイプセーフかつ思い通りに実装できました。

ここまでで、Run locally がうまく動作するはずです。

Add authentication

Create login UI

aws-amplify-react-nativeの参照は以下のように src 以下とします。 src 以下は全て TypeScript ファイルになっています。

import { withAuthenticator } from 'aws-amplify-react-native/src'