让我们使用AWS AppSync和React来创建一个ToDo应用程序(3)创建React应用程序

首先

通过前几篇文章,我们使用了AWS AppSync创建了一个GraphQL API。
这次,我们打算使用连接到GraphQL API的客户端React来完成。
AWS AppSync提供了与React相关的GraphQL客户端Apollo的支持,aws-appsync-react(绑定库)已经准备好了,所以这次我们将使用它来关联AWS AppSync的数据和组件。

项目的设置

创建原型

使用Create React App工具创建项目的雏形。
(本次使用的Node版本是9.2.0。)

$ mkdir aws-appsync-todo-app
$ npx create-react-app .

安装所需的附加软件包。

$ yarn add graphql-tag react-apollo aws-appsync aws-appsync-react uuid
    • graphql-tag

GraphQLのスキーマをJavaScriptのコード内に定義するために使用

react-apollo

GraphQLクライアント

aws-*のパッケージ

AWS AppSyncとApolloを連携するために使用

uuid

Todo個々のアイテムにクライアント側でユニークなIDをつけるために使用

获取和导入设置文件

从控制台界面打开“AWS AppSync > 创建的项目 > 首页”,然后下载AppSync.js文件,方法是在“入门指南”的最下方点击“下载 AWS AppSync.js 配置文件”。

スクリーンショット 2018-01-25 22.17.20.png

将下载的文件放置在创建的项目的src文件夹下,与其他包一起导入到App.js中。

import { ApolloProvider } from 'react-apollo';
import AWSAppSyncClient from "aws-appsync";
import { Rehydrated } from "aws-appsync-react";
import appSyncConfig from "./AppSync";

初始化AppSyncClient

使用从配置文件中读取的AWS认证信息和API信息作为参数来初始化AWS AppSyncClient。
可以通过auth参数选择认证方式,但本次使用API密钥进行认证。

// AWS AppSync Client
const client = new AWSAppSyncClient({
  url: appSyncConfig.graphqlEndpoint,
  region: appSyncConfig.region,
  auth: {
    type: appSyncConfig.authenticationType,
    apiKey: appSyncConfig.apiKey,
  }
});

将与AppSync数据进行集成的组件包括ApolloProvider和Rehydrated,在ApolloProvider中传递AppSyncClient。

class App extends Component {
  render() {
    return (
      <ApolloProvider client={client}>
        <Rehydrated>
          <div className="App">
            <header className="App-header">
              <h1 className="App-title">AWS AppSync Todo</h1>
            </header>
            <TodoListWithData />
          </div>
        </Rehydrated>
      </ApolloProvider>
    );
  }
}

创建GraphQL查询

使用graphql-tag的gql方法来定义查询。这次我们将其创建在src/GraphQL目录下。

查询所有完成的事项

import gql from "graphql-tag";

export default gql(`
query {
  getTodos {
    id
    title
    description
    completed
  }
}`);

创建一个名为Todo的变异

import gql from "graphql-tag";

export default gql(`
mutation addTodo($id: ID!, $title: String, $description: String, $completed: Boolean) {
  addTodo(
    id: $id
    title: $title
    description: $description
    completed: $completed
  ) {
    id
    title
    description
    completed
  }
}`);

Todo更新的Mutation

import gql from "graphql-tag";

export default gql(`
mutation updateTodo($id: ID!, $title: String, $description: String, $completed: Boolean) {
  updateTodo(
    id: $id
    title: $title
    description: $description
    completed: $completed
  ) {
    id
    title
    description
    completed
  }
}`);

删除Todo的变异

import gql from "graphql-tag";

export default gql(`
mutation deleteTodo($id: ID!) {
  deleteTodo(id: $id) {
    id
    title
    description
    completed
  }
}`);

实现ToDoList组件

这次,将Todo列表的全部功能简略地整合到一个组件中。
在src/Components文件夹下,新建了一个TodoList.js文件。

AWS AppSync和Component之间的协作

为了在React组件中与AWS AppSync的GraphQL API进行协作,我们将使用react-apollo。

当将组件作为参数传递给React-Apollo中的graphql方法时,可以接收到一个组件,该组件可以通过props从GraphQL API获取到的数据。

const TodoListWithData = graphql(QueryGetTodos)(TodoList);

此外,当需要将多个查询、变动和组件进行协作时,可以使用react-apollo的compose方法。实际上,这只是一个想象,因为在实际应用中可能需要为每个查询、变动和组件指定一些选项等。

const TodoListWithData = compose(
  graphql(QueryGetTodos),
  graphql(MutationAddTodo),
  graphql(MutationUpdateTodo),
  graphql(MutationDeleteTodo),
)(TodoList);

实际上,将每个查询(Query)和变更(Mutation)关联起来的部分如下所示。

export default compose(
  // 全件取得Query
  graphql(QueryGetTodos, {  // あらかじめ定義したGraphQLクエリを使用
    options: {
      fetchPolicy: 'cache-and-network'
    },
    props: (props) => ({
      todos: props.data.getTodos
    })
  }),
  // 追加Mutation
  graphql(AddTodoMutation, {  // あらかじめ定義したGraphQLクエリを使用
    props: (props) => ({
      onAdd: (todo) => {
        props.mutate({
          variables: { ...todo },
          // APIからのレスポンスが返ってくるまえにpropsに反映する値を設定
          optimisticResponse: () => ({ addTodo: { ...todo, __typename: 'Todo' } })
        })
      }
    }),
    options: {
      // 追加の後に全件リストを更新するアクション
      refetchQueries: [{ query: QueryGetTodos }],
      update: (proxy, { data: { addTodo } }) => {
        const query = QueryGetTodos;
        const data = proxy.readQuery({ query });

        data.getTodos.push(addTodo);

        proxy.writeQuery({ query, data });
      }
    }
  }),
  // 状態更新(チェック)Mutation
  graphql(UpdateTodoMutation, {  // あらかじめ定義したGraphQLクエリを使用
    props: (props) => ({
      onCheck: (todo) => {
        props.mutate({
          variables: { id: todo.id, title: todo.title, description: todo.description, completed: !todo.completed },
          // APIからのレスポンスが返ってくるまえにpropsに反映する値を設定
          optimisticResponse: () => ({ updateTodo: { id: todo.id, title: todo.title, description: todo.description, completed: !todo.completed, __typename: 'Todo' } })
        })
      }
    }),
    options: {
      // 更新の後に全件リストを更新するアクション
      refetchQueries: [{ query: QueryGetTodos }],
      update: (proxy, { data: { updateTodo } }) => {
        const query = QueryGetTodos;
        const data = proxy.readQuery({ query });

        data.getTodos = data.getTodos.map(todo => todo.id !== updateTodo.id ? todo : { ...updateTodo });

        proxy.writeQuery({ query, data });
      }
    }
  }),
  // 削除Mutation
  graphql(DeleteTodoMutation, {  // あらかじめ定義したGraphQLクエリを使用
    props: (props) => ({
      onDelete: (todo) => props.mutate({
        variables: { id: todo.id },
        // APIからのレスポンスが返ってくるまえにpropsに反映する値を設定
        optimisticResponse: () => ({ deleteTodo: { ...todo, __typename: 'Todo' } }),
      })
    }),
    options: {
      // 削除の後に全件リストを更新するアクション
      refetchQueries: [{ query: QueryGetTodos }],
      update: (proxy, { data: { deleteTodo: { id } } }) => {
        const query = QueryGetTodos;
        const data = proxy.readQuery({ query });

        data.getTodos = data.getTodos.filter(todo => todo.id !== id);

        proxy.writeQuery({ query, data });
      }
    }
  })
)(TodoList);

其余部分如下:

class TodoList extends Component {

  constructor(props) {
    super(props);
    this.state = {
      todo: {
        title: '',
        description: ''
      },
    };
  }

  // propsの初期値を設定
  static defaultProps = {
      todos: [],
      onAdd: () => null,
      onDelete: () => null,
      onUpdate: () => null,
  }

  todoForm = () => (
    <div>
      <span><input type="text" placeholder="タイトル" value={this.state.todo.title} onChange={this.handleChange.bind(this, 'title')} /></span>
      <span><input type="text" placeholder="説明" value={this.state.todo.description} onChange={this.handleChange.bind(this, 'description')} /></span>
      <button onClick={this.handleOnAdd}>追加</button>
    </div>
  );

  renderTodo = (todo) => (
    <li key={todo.id}>
      <input type="checkbox"checked={todo.completed} onChange={this.handleCheck.bind(this, todo)} />
      {!todo.completed && todo.title}
      {todo.completed && (<s>{todo.title}</s>)}
      <button onClick={this.handleOnDelete.bind(this, todo)}>削除</button>
    </li>
    );

  handleChange = (field, { target: { value }}) => {
    const { todo } = this.state;
    todo[field] = value;
    this.setState({ todo });
  }

  handleOnAdd = () => {
    if (!this.state.todo.title || !this.state.todo.description) {
      return;
    }
    const uuid = uuidv4();
    const newTodo = {
      id: uuid,
      title: this.state.todo.title,
      description: this.state.todo.description,
      completed: false
    }
    this.props.onAdd(newTodo);

    const { todo } = this.state;
    todo.title = '';
    todo.description = '';
    this.setState({ todo });
  }

  handleCheck = (todo) => {
    this.props.onCheck(todo);
  }

  handleOnDelete = (todo) => {
    this.props.onDelete(todo);
  }

  render() {
    const { todos } = this.props;
    return (
      <Fragment>
        {this.todoForm()}
        <ul>
          {todos.map(this.renderTodo)}
        </ul>
      </Fragment>
    );
  }
}

请按照以下选项进行参考

用ReactJS构建客户端应用程序 -AWS AppSync
使用React和Apollo尝试Github GraphQL API -Qiita
APOLLO客户端 -Apollo

简要总结

关于客户端实现的部分,很多都依赖于react-apollo。如果要充分利用AWS AppSync和React的组合,我们需要仔细查看react-apollo的帮助方法和缓存选项等。

另一方面,除此之外的部分都由aws-appsync-react自动处理,不需要担心。

本次只使用了Query和Mutation来进行GraphQL查询,但如有机会,我也想尝试使用Subscription来与服务器实时通信的模式。

bannerAds