今回の題材について~掲示板アプリの改修
本記事では、第4回や第7回で題材にした、掲示板アプリを改修していきます。これまでに作ってあった掲示板アプリは投稿内容をReactコンポーネントのstateに保持しており、あまり実践的なものではありませんでしたが、今回はサーバーとのやり取りを行う想定で作り変えるので、実践的な場面でも使える考え方になります。
改修の方針
今回の改修には大きく分けて2つの目的があります。
- 状態の管理をReactからReduxに移管する
- 新しい投稿にIDを採番して保存する処理や、最新の投稿を取得してくる処理を、サーバーとやり取りする想定で非同期に記述する
これらは従来、どちらもApp.jsが行っていた処理です。そのため、今回改修を行うReactコンポーネントはApp.jsのみで、Form.jsやList.jsは従来のものをそのまま使うことにします。リスト2は、これまで扱ってきた掲示板アプリのソースを含む、今回扱うファイルの一覧です。
src/
actions/
posts.js (★)
api/
ApiClient.js (★)
reducers/
index.js (★)
posts.js (★)
App.css
App.js (▲)
Form.js
index.js (▲)
List.js
★マークがついているものは、今回のサンプルで新しく追加したものです。また、▲マークがついているものは、以前のサンプルから改修するものになります。
Reduxとやり取りできる形にコンポーネントを改修する
まずは第8回で学んだ範囲でできることとして、App.jsをReduxに接続してみましょう。まだ作っていないAction CreatorやReducerの名前を使用しますが、これらは後から作っていくつもりで使う側から先に命名した方がうまくいくことがあるので、オススメの開発手法です。
まずは、データの受け取り側としてconnect関数の適用を行います(リスト3)。
class App extends Component {...}
function mapStateToProps(state) {
return {
postsState: state.posts // (1)
}
}
export default connect(mapStateToProps)(App);
前回の範囲なので詳しくは説明しませんが、(1)のように、掲示板アプリとして管理する状態にpostsという名前をつけてReducer側で管理し、AppコンポーネントにはpostsStateという名前のpropsを渡すことにしています。postsStateのPropTypes定義はリスト4の通りです。
postsState: PropTypes.shape({
posts: PropTypes.arrayOf(
PropTypes.shape({
name: PropTypes.string.isRequired,
age: PropTypes.oneOf([
Ages.TEEN,
Ages.TWENTIES,
Ages.THIRTIES,
Ages.FOURTIES,
Ages.FIFTIES,
]).isRequired,
body: PropTypes.string.isRequired
})
),
error: PropTypes.bool.isRequired
}),
これまで管理していた投稿データと同じ型のpostsとは別に、非同期処理でのエラーの有無を通知するためのerrorという真偽値型のプロパティも持たせています。今回は表示に使いませんが、こういった状態をReducer側で管理しておくと、エラー表示が簡単に行えるようになります。
さて、このpropsに合わせて、render内のListコンポーネントへのデータ渡しをリスト5のように変更します。コンポーネントのstateから取り出していたpostsが、props由来のものに置き換わりました。
<List
posts={this.props.postsState.posts} />
次に、初期化や新しい投稿の追加など、状態更新に関するところを変更します(リスト6)。
import { initPosts, sendNewPost } from "./actions/posts";
class App extends Component {
componentDidMount() {
// 投稿を表示するための初期データをサーバーに請求する
this.props.dispatch(initPosts()); // (2)
}
/** Formが作成した投稿を保存する処理 */
saveNewPost(newPost) {
// サーバーにデータを送る
this.props.dispatch(sendNewPost(newPost)); // (1)
}
// 略
}
Formコンポーネントから渡されてきたデータを処理する役割だったsaveNewPost関数のインターフェースはそのままに、内部実装だけを変更しました。投稿データはsendNewPostというAction Creatorで加工され、Reduxにスルーパスされます(1)。また、サーバー側にある既存のデータを表示したいので、新たにinitPostsというAction Creatorを作成しました(2)。
さて、これでAppコンポーネントのRedux化が完了しました。propsでReducer管理下の状態を受け取って表示に使用し、Action Creator関数で作ったActionオブジェクトをdispatchして状態の更新を促すだけの、素直なRedux向けコンポーネントです。今見えている範囲における処理の流れをシーケンス図にすると、図5のようなものになります。
まだ非同期処理の詳しいことを説明していないので、大きなブラックボックスがありますね。次節ではこのブラックボックス部分について具体的な実装を見ていきます。
