-
Notifications
You must be signed in to change notification settings - Fork 17
Step3:APIから値を取得する
redux-plutoでは agreed という mock サーバを立て、それを使って 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 の設定はこれだけです。
ターミナルに以下のコマンドを入力し、正しく設定できているか確認してみましょう
$npm run start:dev:agreed // agreed-server の起動
$curl http://localhost:3010/hello
ターミナル上に
{"results":{"comments":[{"id":"0001","text":"hello"},{"id":"0002","text":"world"}]}}
と表示できれば成功です!
redux-pluto では BFF(Backends for Frontends) というフロントエンド用のサーバーが立っています。
ここではAPIゲートウェイとしてのBFFの設定を行い、 API から値を取得できるようにしていきます。
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";
/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からの値取得成功です!
ページを表示するタイミング、ルーティングと同時に redux の action を 呼び出して 非同期に API から値を取得してみましょう。
ルーティングをトリガーにデータの非同期呼び出しを行うには redux-async-loader を使います。
利用module
redux-async-loaderasyncLoader(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 が表示されるのが確認できると思います。