redux-api-struct
ライブラリを公開したのでqiitaに残しておきます。
僕は普段Reactを業務で書く場合に、
- typescript
- styled-component
- redux
- react-router
- axios
あたりを使って書くのですが、このAPIに対してstoreの値をどのように持っておくかを簡単に定義することができるものをOSSにしました。
公開したものは 俺の考えたRedux + Atomic DesignでTypeSafeにコンポーネントを扱う方法という記事にでてくるもの何ですが、普段からよく書くのでライブラリにしておくと楽なので公開したといった具合です。
使い方
使い方は README.mdにも書いていますが、ざっくり書きます。
例えば、ブログサービスで記事詳細画面を作る場合を想定します。
modelは
interface Article {
title: string
body: string
}
とします。フロントエンドでやる流れとしては
- 画面を描画する(ローディング画面)
- APIにアクセスする
- 200 or 4xxが帰ってくる
- その状況に応じてUIを適応する
とざっくり仮定します。
この詳細の管理を article
というstateで管理するとします。
interface InitialState {
article: Article
}
const initialState: InitialState = {
article: null
}
さてこれを表示する画面を作成します。
import * as React from "react"
interface Props {
article: Article
}
export const Article: React.FunctionComponent = ({ article }) => {
return (
<>
<h1>{article.title}</h1>
<div>{article.body}</div>
</>
)
}
ここまで書いておけば
<Article article={store.sampleReducer.article} />
これで、記事を表示するコンポーネントを定義することができました!
しかし、これだとAPIをfetchしている間、真っ白な画面がでてしまいます。ローディング画面を追加してみましょう。
ローディング画面を追加する
import * as React from "react"
export const Loading: React.FunctionComponent = () => (
<p>now loading...</p>
)
ローディングコンポーネントを定義できたので、これを使って画面をつくりたいですね!
と、なった時に今の状況ではapiのfetchを行っている時の状態をstateで管理していないのでローディング画面をだすことができませんね。stateで管理しましょう。
interface InitialState {
isFetching: boolean
article: Article
}
これで、
import * as React from "react"
import { Loading } from "./Loading"
interface Props {
article: Article
isFetching: boolean
}
export const Article: React.FunctionComponent = ({ article, isFetching }) => {
if(isFetching) {
return <Loading />
}
return (
<>
<h1>{article.title}</h1>
<div>{article.body}</div>
</>
)
}
isFetchingがtrueの間は、ローディング画面ができてfetchが終わりfalseになった時はちゃんと記事の表示ができたと思います。
しかし、例えば存在しない記事にきた場合、APIは404を返しますね。isFetchingはfalseになりますが、 article.title
は undefined
になります。
と、いった具合にAPIと連携することを想定すると気にする点が多数出てきます。
これがあるたびに initialState
にその状態管理を行うstateをはやしていくのはとてもめんどくさい作業になると思います。
そこで、redux-api-structを使います。
redux-api-structを使い、簡単に記述する
redux-api-structは、状態。実データ。エラーを一つのstateで管理するようにするためのライブラリです。
initialStateの書き方を以下のようにしてみます。
import { ReduxAPIStruct } from "redux-api-struct"
interface InitialState {
article: ReduxAPIStruct<Article>
}
こうすると、この article
stateは以下のようなtreeを持つようになります。
article/
- status
- data
- error
このstatusは INITIAL
, FETCHING
, SUCCESS
, FAILURE
の4つの状態をもつenumです。
dataに実際のresponseが入ります。
errorには、そのresponseのエラー内容がはいります。
これをコンポーネントで使う時は
import * as React from "react"
import { ReduxAPIState, ReduxAPIStruct } from "redux-api-struct"
interface Props {
article: ReduxAPIStruct<Article>
}
export const Article = ({ article }) => {
switch(article.status) {
case ReduxAPIState.INITIAL:
case ReduxAPIState.FETCHING:
return <Loading />
case ReduxAPIState.SUCCESS:
return <ArticleTemplate article={article.data} />
case ReduxAPIState.FAILURE:
if(article.error.statusCode === 404) {
return <p>page not found!</p>
} else {
return <p>error view</p>
}
}
}
これでAPIの状態別でviewを出すことができて、成功した場合にちゃんと記事詳細を表示することができ、エラーの場合のハンドリングまで行うことができます。
簡単にtypesafeにコンポーネントとデータを扱うことができるので、typescriptで書いているプロジェクトはぜひ。
その他、質問などがありましたら こちらにお願いします。