React + ReduxでRssリーダーもどきを作ってみる
はじめに
前にReact + Reduxはやったことがあったけど、型を付けてなかったり、非同期処理を入れてなかったりするので、今回はその辺を踏まえてやってみる。
RSSリーダーとは言ったけど、
自分が望んだ形式で返ってくる前提で作っているのでRSSリーダーもどきとしてる。
TL;DR.
事前準備
まずはcreate-react-app
を使ってプロジェクトの雛形を作るとこから。
作れたら必要なライブラリをインストールする
# プロジェクト作成 $ create-react-app react-sample -typescript $ cd react-sample # ライブラリインストール # redux, react-reduxはreduxやるのに入れるいつもの # redux-thunkはreduxで非同期処理をする際によしなにしてくれるライブラリ $ npm install --save redux react-redux redux-thunk # 必要に応じて型定義も $ npm install --save-dev @types/redux @types/react-redux @types/redux-thunk
実装
Action
よくあるActionと非同期用のActionを定義しておく。
ThunkActionに型が付いてないところがあるけど、getState
とextraArgument
の戻り値なので一旦付けないでおく。
import {Action, Dispatch} from 'redux'; import {RssReaderStates} from '../types/RssReaderStates'; import axios from 'axios'; import {ThunkAction} from 'redux-thunk'; // type定義 export enum RSS_READER_TYPES { ADD_LIST = 'ADD_RSS_LIST', FETCH = 'FETCH_RSS' } // アクション定義 interface IAddRssListAction extends Action{ type: typeof RSS_READER_TYPES.ADD_LIST; payload: RssReaderStates[] } // ActionCreator群 // リストに追加用Action export const addRssList = (rssList: RssReaderStates[]): IAddRssListAction => ({ type: RSS_READER_TYPES.ADD_LIST, payload: [...rssList] }); // Fetch用Action export const fetchRss = (): ThunkAction<void, any, any, IAddRssListAction> => { return async (dispatch: Dispatch<IAddRssListAction>) => { // 今回取得先は`json-server`で返すJSONから取得 const result = await axios.get<RssReaderStates[]>('http://localhost:4000/result'); dispatch(addRssList(result.data)); } }; // Actionの型を返す export type RssActions = ( ReturnType<typeof addRssList> );
Reducer
よくあるReducerのままだと思う。
今回はAction1つしか用意していないので、意味ないけど一応switchで書いておく。
import {RssReaderStates} from '../types/RssReaderStates'; import {RSS_READER_TYPES, RssActions} from '../actions/RssReaderAction'; export const rssReaderReducer = (state: RssReaderStates[] | never = [], action:RssActions): RssReaderStates[] => { switch (action.type) { case RSS_READER_TYPES.ADD_LIST: return [...state, ...action.payload]; default: return state; } }; // ================= // 以降index.tsに記載 // ================= import {combineReducers} from 'redux'; import {rssReaderReducer} from './RssReaderReducer'; export default combineReducers({rssReaderReducer});
Store
今回ReduxThunk
を使用したので、middleWareを追加しておく。
import ReduxThunk, {ThunkMiddleware} from 'redux-thunk'; import {applyMiddleware, createStore} from 'redux'; import rootReducer from '../reducers'; import {RssReaderStates} from '../types/RssReaderStates'; import {RssActions} from '../actions/RssReaderAction'; export default createStore( rootReducer, applyMiddleware(ReduxThunk as ThunkMiddleware<RssReaderStates, RssActions>) );
Container
非同期のリクエストをする用のボタ用の結果を表示する用のリストの2つを用意する。
Button
import React from 'react'; import {fetchRss, RssActions} from '../actions/RssReaderAction'; import {connect} from 'react-redux'; import {ThunkDispatch} from 'redux-thunk'; import {RssReaderStates} from '../types/RssReaderStates'; type DispatchProps = { onClick: () => void; }; // Action発火用ボタン class FetchButton extends React.Component<DispatchProps> { // 初期表示用に読み込まれたときにイベントを発火させる componentDidMount(): void { this.props.onClick(); } render() { return ( <button onClick={() => { this.props.onClick(); }} > 追加 </button> ); } }; // props返却用 const mapDispatchToProps = ( dispatch: ThunkDispatch<RssReaderStates, undefined, RssActions> ): DispatchProps => ({ onClick: () => { dispatch(fetchRss()); }, }); export default connect(null, mapDispatchToProps)(FetchButton);
List
受け取ったStateをPropsに変換して表示用のListに渡す。
正直、ここなくても良い気がするけど一旦噛ませない方法がわからなかったので入れておく。
import React from 'react'; import {connect} from 'react-redux'; import {RssReaderStates} from '../types/RssReaderStates'; import ListComponent from '../components/ListComponent'; import {RssReaderProps} from '../types/RssReaderProps'; const stateToProps = (state: RssReaderStates[]): RssReaderProps => { return {rssList: state}; }; export default connect(stateToProps)(ListComponent);
Component
表示用の処理群。表示する以外の役割を内容にする。
propsのrssListが何故か1段ずれて入ってくるので、対応している。(型内容は後述)
なんとかしたいけど、ぱっと解決できなかったのとサンプルだからという言い訳で放置する。
解決策ご存知の方教えて頂けると幸いです。
import React from 'react'; import {RssReaderStates} from '../types/RssReaderStates'; import {RssReaderProps} from '../types/RssReaderProps'; export default (props: RssReaderProps) => ( <ul> {props.rssList['rssReaderReducer'] .map((rss: RssReaderStates, index: number) => <li key={index}> <a href={rss.url} target="_blank">{rss.title}</a>: {rss.description} </li>) } </ul> );
型一覧
RssReaderProps
props用の型。
ここで指定した型と何故かずれるので↑のような解決策をしている。
import {RssReaderStates} from './RssReaderStates'; export type RssReaderProps = { rssList: RssReaderStates[] }
RssReaderState
Stateの型。
export type RssReaderState = { title: string, description: string, url: string };
実行結果
画像のような結果になればOK!
ちなみにボタンを押したところで同じ内容が追加されるだけになる。
まとめ
今回はReact + ReduxでRssリーダーを作ってみた。
Reduxで非同期処理ができないことがびっくりだったり、型付けたらReact,Reduxの型が難解過ぎて苦労した。
今回はなんとか動くように作っただけなので、また何か作って行きたいところ。
それでは、今回はこの辺で。