Skip to content
This repository has been archived by the owner on Sep 8, 2022. It is now read-only.

Step3:APIから値を取得する

Tomohide Takao edited this page Apr 26, 2019 · 5 revisions

APIを用意する

redux-plutoでは agreed という mock サーバを立て、それを使って API を扱うことができます。

agreedにAPIを定義する

ここでは/hello に get リクエストを送ると id と text を持つ オブジェクトの配列 comments を返してくれるapiを定義します。

spec/agreed/hello/getComments.ts

+import { APIDef, GET, Success200, ResponseDef, Error404 } from "agreed-typed";
+ 
+export type HelloGetAPI = APIDef<
+  GET, // HTTP Method
+  ["hello"],
+  {}, // request header
+  {}, // request query
+  undefined, // request body
+  {}, // response header
+  ResponseDef<Success200, { results: { comments: {id: string, text: string }[] } }>
+>;
+ 
+const api: HelloGetAPI = {
+  request: {
+    path: ["hello"],
+    method: "GET",
+    body: undefined,
+  },
+  response: {
+    status: 200,
+    body: {
+      results: {
+          comments: [
+            {
+                id: "0001",
+                text: "Hello",
+            },
+            {
+                id: "0002",
+                text: "world",
+            }
+          ]
+      },
+    }
+  },
+};
+ 
+module.exports = api;

次に作成したファイルを agreed.js ファイル 内に追加し export させます。

spec/agreed/agreed.ts

  ...flatten([
    require("./agreedsample/get"),
    require("./uploadsample/post"),
+   require("./hello/getComments"),
  ]),
);

api の設定はこれだけです。

APIの動作確認

ターミナルに以下のコマンドを入力し、正しく設定できているか確認してみましょう

$npm run start:dev:agreed // agreed-server の起動
$curl http://localhost:3010/hello

ターミナル上に

{"results":{"comments":[{"id":"0001","text":"hello"},{"id":"0002","text":"world"}]}}

と表示できれば成功です!

BFFの設定をする

redux-pluto では BFF(Backends for Frontends) というフロントエンド用のサーバーが立っています。
ここではAPIゲートウェイとしてのBFFの設定を行い、 API から値を取得できるようにしていきます。

service を作成する

redux-pluto での通信は ブラウザから BFF を経由し API サーバへリクエストを飛ばします。
ブラウザ → BFF 間の通信は fetchr
BFF → APIサーバ 間の通信は axios を利用しています。

ここでは API 通信の中間点となるMicroservice Architecture の service に当たるものを作成していきます。
src/server/services/配下に以下の Hello.js を作成します。

src/server/services/Hello.ts

+import Axios from "axios";
+import { read } from "./utils"; // axios を使い get request を投げる util
+ 
+export default class Hello {
+  name: string;
+  axios: any;
+  pathname: string;
+ 
+  constructor(config: any) {
+    this.name = "hello"; // hello と言う名前をつける
+    // server の設定ファイル (src/server/configs) の中から agreed 向けの axios を設定
+    this.axios = Axios.create(config.agreed.config.axios);
+    this.pathname = "hello";
+  }
+ 
+  read(req: any, resource: any, params: any = {}, config: any) {
+    return read(this.axios, this.name, this.pathname, params, {});
+  }
+}

利用module
axios baseURLをconfigとして設定し、それに続く url を与えることで API 呼び出しをする

src/server/services/index.js に以下の記述を追加し、先ほど作成した service を export させます。 ここで取りまとめられ export された services は src/server/middlwares/apiGateway.js でFetcherに登録され、後述の redux の action 経由で呼び出されることになります。

src/server/services/index.ts

~~~
 export { default as HackerNews } from "./HackerNews";
+export { default as Hello } from "./Hello";

BFFのAPIを呼び出すためのアクションを作成する

アクションを作成する

/src/shared/redux/modules/hello.js に 以下の アクションとreducerを追加します

src/shared/redux/modules/hello.ts

+import { steps } from "redux-effects-steps";
+import { fetchrRead } from "redux-effects-fetchr";
 
/**
 * Action types
 */
const HELLO_CHANGE_VISIBILITY = "redux-pluto/hello/visibility/change"; // 表示・非表示を切り替える Action の type
+const HELLO_GET_COMMENTS_REQUEST = "redux-pluto/hello/get/comments/request";
+const HELLO_GET_COMMENTS_SUCCESS = "redux-pluto/hello/get/comments/success";
+const HELLO_GET_COMMENTS_FAIL = "redux-pluto/hello/get/comments/fail";

type ChangeVisibility = {
  type: typeof HELLO_CHANGE_VISIBILITY;
};
+type CommentsRequest = {
+  type: typeof HELLO_GET_COMMENTS_REQUEST;
+  payload: {
+    resource: string;
+  };
+};
+type CommentsSuccess = {
+  type: typeof HELLO_GET_COMMENTS_SUCCESS;
+  payload: any;
};
type CommentsFail = {
  type: typeof HELLO_GET_COMMENTS_FAIL;
  error: boolean;
};

-type Action = ChangeVisibility;
+type Action =
+  | ChangeVisibility
+  | CommentsRequest
+  | CommentsSuccess
+  | CommentsFail;

/**
 * Action creators
~~~

+export function getCommentsRequest(payload: {
+  resource: string;
+}): CommentsRequest {
+  return {
+    type: HELLO_GET_COMMENTS_REQUEST,
+    payload,
+  };
+}
 
+export function getCommentsSuccess(res: any) {
+  return {
+    type: HELLO_GET_COMMENTS_SUCCESS,
+    payload: res,
+  };
+}
+ 
+export function getCommentsFail() {
+  return {
+    type: HELLO_GET_COMMENTS_FAIL,
+    error: true,
+  };
+}
+ 
+export function getComments() {
+  return steps(
+    getCommentsRequest({ resource: "hello" }),
+    ({ payload }) => fetchrRead(payload),
+    [getCommentsSuccess, getCommentsFail],
+  );
+}
 
/**
 * Initial state
 */
// module 内で管理する state の型
export type State = {
  isVisible: boolean;
+ comments: { id: string; text: string }[];
+ loading: boolean;
+ loaded: boolean;
+ error?: boolean;
};

// store に展開される初期値
const INITIAL_STATE = {
  isVisible: true,
+ comments: [],
+ loading: true,
+ loaded: false,
};

~~~
+   case HELLO_GET_COMMENTS_REQUEST: {
+     return {
+        ...state,
+        loading: true,
+       loaded: false,
+     };
+   }
+   case HELLO_GET_COMMENTS_SUCCESS: {
+     const {
+       payload: {
+         data: { comments },
+       },
+     } = action;
+     return {
+       ...state,
+       comments,
+       loading: false,
+       loaded: true,
+     };
+   }
+   case HELLO_GET_COMMENTS_FAIL: {
+     const { error } = action;
+     return {
+       ...state,
+       error,
+       loading: false,
+       loaded: false,
+     };
+   }
    default: {
      return state;
    }

利用module
redux-effects-steps:
引数で与えたactionを順番にdispatchする
直前のactionの成否によって次のアクションを切り替えたい際には [success, fail] のようにlength2の配列の1つ目にresolve時のアクション2つ目にreject時のアクションを記入する

redux-effects-fetchr:
Fetcherに登録されている service を呼び出す

コンポーネントにアクションを設定する

コンポーネントを以下のように書き換えて、 get Text ボタンを押すと redux のアクションが発火するようにします。

src/shared/components/organisms/Hello/index.ts

~~~
import { compose } from "redux";
import { connect } from "react-redux";
-import { changeVisibility } from "../../../redux/modules/hello";
+import { changeVisibility, getComments } from "../../../redux/modules/hello";
import { RootState } from "../../../redux/modules/reducer";
import Hello from "./Hello";

~~~
  connect(
    (state: RootState) => ({
      isVisible: state.app.hello.isVisible, // store の state の中から、指定した isVisible を props として渡す
+     comments: state.app.hello.comments,
    }),
    dispatch => ({
      onChangeVisibility: () => dispatch(changeVisibility()), // changeVisibilityを store に dispatchする関数を返す
+     onClickGetComments: () => dispatch(getComments() as any),
    }),
  ),
)(Hello);

src/shared/components/organisms/Hello/Hello.tsx

 type Props = {
   isVisible: boolean,
+  comments: { id: string, text: string }[],
   onChangeVisibility: Function,
+  onClickGetComments: Function,
 };

 export default function Hello(props: Props) {
-  const { isVisible, onChangeVisibility } = props;
+  const { isVisible, onChangeVisibility, comments, onClickGetComments } = props;
   return (
     <div>
-      {isVisible && <div>Hello!</div>}
+      {isVisible &&
+      comments.map(comment => <div key={comment.id}>{comment.text}</div>)}
+      <button type="button" onClick={() => onClickGetComments()}>
+        get comments
+      </button>
       <button type="button" onClick={() => onChangeVisibility()}>

動作確認

http://localhost:3000/hello にアクセスすると以下のように get text ボタンが表示されています。

ボタンを押して hello world と表示されたらAPIからの値取得成功です!

ページを開くタイミングでAPIにリクエストを飛ばす

ページを表示するタイミング、ルーティングと同時に redux の action を 呼び出して 非同期に API から値を取得してみましょう。

HOCの追加

ルーティングをトリガーにデータの非同期呼び出しを行うには redux-async-loader を使います。

利用module
redux-async-loader

asyncLoader(loader) ルーティングによって非同期にデータ呼び出しを行う HOC を作成する
loader(props, store)
props と Redux の store オブジェクトを引数に取り、
データ呼び出しが完了したら Fulfilled する Promise オブジェクトを返す

src/shared/components/organisms/Hello/index.ts

~~~
import { compose } from "redux";
import { connect } from "react-redux";
+import { asyncLoader } from "redux-async-loader"; // redux-asynch-roder から asyncLoader をインポート
import { changeVisibility, getComments } from "../../../redux/modules/hello";
import { RootState } from "../../../redux/modules/reducer";
import Hello from "./Hello";

export default compose(
+ asyncLoader((props, store) => store.dispatch(getComments())),
  connect(
    (state: RootState) => ({
      isVisible: state.app.hello.isVisible, // store の state の中から、指定した isVisible を props として渡す
~~~
    }),
    dispatch => ({
      onChangeVisibility: () => dispatch(changeVisibility()), // changeVisibilityを store に dispatchする関数を返す
-     onClickGetComments: () => dispatch(getComments() as any),
    }),
  ),
)(Hello);

src/shared/components/organisms/Hello/Hello.tsx

~~~
export default function Hello(props: Props) {
- const { isVisible, onChangeVisibility, comments, onClickGetComments } = props;
+ const { isVisible, onChangeVisibility, comments } = props;
  return (
    <div>
      {isVisible &&
        comments.map(comment => <div key={comment.id}>{comment.text}</div>)}
-     <button type="button" onClick={() => onClickGetComments()}>
-       get comments
-     </button>
      <button type="button" onClick={() => onChangeVisibility()}>
        {isVisible ? "hide" : "show"}
      </button

動作確認

もう一度 http://localhost:3000/hello にアクセスしましょう。
ページを開くだけで getComments が呼ばれ、hello world が表示されるのが確認できると思います。

このSTEPのソースコード