Reactで非同期通信をする場合は、Fetch APIや axiosを使うことが多い気がするが、RxJSを使うのもオススメだ。
RsJSは、アクティブ・プログラミング用のライブラリであり、イベントのハンドリングや非同期処理をラップし、簡潔にコーディング出来ることを主な目的としています。
スポンサーリンク
React プロジェクトの作成
まずは、Reactのプロジェクトを作る。今回は TypeScript を使用する。
npx create-react-app --template typescript sample-app
RxJSのインストール
次のコマンドで、RsJSをインストールする。
npm i --save rxjs-hooks rxjs
スポンサーリンク
非同期処理の実装
バックエンドのサーバーから Todoの一覧を取得して、画面に表示するサンプルを作ってみる。
あくまでフロント側の実装のサンプルなので、Todoのデータを返すバックエンド側の実装は、無料で JSONデータのサンプルを返してくれる JSONPLACEHolder を使う。今回は https://jsonplaceholder.typicode.com/todos
にアクセスして Todoの一覧を取得します。
インターフェイス
最初に Todoのインターフェイスを定義する。
interface Todo {
title: string,
completed: boolean
}
Todoの一覧を格納する stateの定義
const [todos, setTodos] = useState(new Array<Todo>());
RxJSの Ajaxライブラリで Todoの一覧を非同期で取得
ロード時に、RsJSの Ajaxライブラリを使って Todoの一覧を取得します。
useEffect(() => {
ajax.getJSON(`https://jsonplaceholder.typicode.com/todos`)
.subscribe({
next: (data: any) => {
setTodos(data);
}
});
}, []);
Viewの作成
ビューの実装をします。リストに Todoの完了状態とタイトルを表示します。
return (
<div className="App">
<ul>
{todos.map(todo => (
<li>
<input type="checkbox" checked={todo.completed} name="controlled"></input>
<span>{todo.title}</span>
</li>
))}
</ul>
</div>
);
redux-observable で実装する
Reduxは、Reactが扱う UIの state(状態)を管理をするためのフレームワークです。
redux-observableは、reduxの非同期処理を RxJSを使って実装するためのライブラリである。
上で紹介した Todoの一覧を表示する処理を、redux-observableを使って書き直すと次のようになる。redux を使う場合、いろいろなファイルを編集する必要があるが、じっくり見ていこう。
インストール
npm install --save redux-observable
Reducer, Epic
■ redux/modules/todo.ts
import { ofType } from "redux-observable";
import { map, mergeMap } from "rxjs";
import { ajax } from "rxjs/ajax";
// interface
export interface TodoState {
todos: Todo[]
}
export interface Todo {
title: string,
completed: boolean
}
// action creators
export const FETCH_TODO = "todo/FETCH_TODO";
export const FETCH_TODO_FULFILLED = "todo/FETCH_TODO_FULFILLED";
// initial state
const initialState: TodoState = { todos: []};
// reducer
const reducer = (state = initialState, action: any) => {
switch (action.type) {
case FETCH_TODO_FULFILLED:
return { todos: action.payload }
default:
return state
}
};
// epic
export const fetchTodoEpic = (action$: any) => action$.pipe(
ofType(FETCH_TODO),
mergeMap(action =>
ajax.getJSON(`https://jsonplaceholder.typicode.com/posts`).pipe(
map(rs => {
console.log(rs);
return rs;
}),
map(response => ({ type: FETCH_TODO_FULFILLED, payload: response }))
)
)
);
export default reducer;
■ redux/modules/root.ts
import { combineEpics } from 'redux-observable';
import { combineReducers } from 'redux';
import todo, { fetchTodoEpic, TodoState } from './todo';
export interface RootState {
todo: TodoState
}
export const rootEpic = combineEpics(
fetchTodoEpic,
);
export const rootReducer = combineReducers({
todo,
});
■ redux/configureStore.ts
import { createStore, applyMiddleware } from 'redux';
import { createEpicMiddleware } from 'redux-observable';
import { rootEpic, rootReducer } from './modules/root';
const epicMiddleware = createEpicMiddleware();
export default function configureStore() {
const store = createStore(
rootReducer,
applyMiddleware(epicMiddleware)
);
epicMiddleware.run(rootEpic);
return store;
}
■ App.tsx
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { RootState } from './redux/modules/root';
import { FETCH_TODO } from './redux/modules/todo';
interface Todo {
title: string,
completed: boolean
}
function App() {
const todos = useSelector((state: RootState) => state.todo.todos);
console.log(todos)
const dispatch = useDispatch();
useEffect(() => {
dispatch(({ type: FETCH_TODO }));
}, []);
return (
<div className="App">
<ul>
{todos.map((todo, index) => (
<li key={index}>
<input type="checkbox" checked={todo.completed} name="controlled"></input>
<span>{todo.title}</span>
</li>
))}
</ul>
</div>
);
}
export default App;
■ index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './App';
import './index.css';
import configureStore from './redux/configureStore';
import reportWebVitals from './reportWebVitals';
const store = configureStore();
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
reportWebVitals();
0 件のコメント:
コメントを投稿