はじめに
仕事でRailsを使用して開発をしているのですが、事情によりReactを導入することになりました。
しかし、「Reactって何?」状態の自分にとって、いきなりReactを導入と言われても...
なので、事前にReactを勉強するため、色々参考にしながら、簡単なサンプルを作ってみました。
使用した技術とバージョン
ruby 2.5.1
Rails 5.2.2
react-rails 2.4.7
WebPacker 3.5.5
参考にした記事や書籍
Reactの学習には、こちらの書籍を使用しました。
今回記述したサンプルコードも、こちらの内容を大いに参考にさせて頂きつつ、記述していきました。
また、絞り込み部分はこちらの記事を参考にさせて頂きました。
「Reactで絞り込み機能付き検索を実装してみた」
こちら2つについて、参考にさせて頂きありがとうございました。
成果物
内容としてはシンプルで、タスク管理のページを作りました。
一応タスクの名前で検索できるようになってます。
(ちなみにタスクの登録は未実装...時間ができたら作ります。)
実装方法は?
上の画面は、全てReactで描画するようになっています。
よって、html自体には、Reactのコンポーネントを呼び出す処理しか書いてないです。
実装の流れ
1、前準備
react-railsのgithubに載っている方法でプロジェクトを作ったりしつつ、
Get started with Webpacker
まで進めます。
そこから、
rails g controller schedule
rails g model Task name:string description:text start_date:string end_date:string
rails db:migrate
で必要なコントローラやモデルを作り、DBとテーブルも作成します。
面倒ですが、タスク登録がないので、insert文でタスクを頑張って入れます...
(start_dateやend_dateがstringなのは、日付の処理が面倒だったからです...)
2、rails側の整備
コントローラ、ビュー、ルーティングに実装を追加し、rails側の準備を完了させます。
views/scheduleにindex.html.erbを追加し、以下のように実装を加えます。
<%= react_component("Task", { tasks: @tasks }) %>
またコントローラ、ルーティングは以下のようにします。
class ScheduleController < ApplicationController
def index
@tasks = Task.all();
end
end
Rails.application.routes.draw do
get '/schedule', to: 'schedule#index', as: 'schedule'
end
Rails側でやっていることは、
①タスクを表示するためのページを作る
②コントローラで、DBからTaskを全て取得し、ビューに渡してあげる
③ビューでは、コントローラから渡ってきたTaskを、Reactコンポーネントに渡す
だけです。
3、Reactの実装
いよいよReactのソースを実装します。
app/javascript/components配下に、
Task.js
を追加します。
もしくは、
rails g react:component Task
でも作成できるようです。
今回実装した、Task.jsの中身は以下の通りです。
一応部分部分にコメントを入れて解説していますが、
間違ってたらごめんなさい...
import React, { Component } from 'react'
import PropTypes from 'prop-types'
//メインのコンポーネント
class Task extends Component {
//コンストラクタ。
//このコンポーネントのstateには絞り込み前のタスク一覧と、絞り込み後のタスク一覧をそれぞれ用意する
constructor(props){
super(props)
this.state = {initialTasks: this.props.tasks, tasks:[]}
}
//ブラウザロード時の処理。
//最初はタスク全部を表示しておく
componentDidMount() {
this.setState({tasks: this.state.initialTasks})
}
//検索のメソッドをここで用意しておく
searchByName(name) {
const result = this.state.initialTasks.filter((task) => {
return task.name.toLowerCase().search( name.toLowerCase()) !== -1;
})
this.setState({tasks: result})
}
/*
ページ全体のrenderメソッド。
大事なのは、FilterFormのprops(search)に、上記で定義したsearchByNameを定義しておくこと。
これにより、Taskコンポーネントのstateにあるtasksを変更することができる。
そして、変更したtasksを、TaskListコンポーネントにpropsで渡してあげることで、
絞り込み後のタスク一覧を表示することができる。
*/
render() {
return (
<div>
<h1>タスクリスト</h1>
<FilterForm search={(name) => this.searchByName(name)} />
<TaskList tasks={this.state.tasks} />
</div>
)
}
}
//検索フォームのコンポーネント
class FilterForm extends Component{
//コンストラクタ。ここでは、検索値nameをstateとして持っておく
constructor(props){
super(props)
this.state = {name: ''}
}
//検索のテキストボックスの中身が変更された時の処理。
//stateに検索値を挿入しておく
onChangeName(event) {
this.setState({name : event.target.value})
}
//検索ボタンをクリックされた時の処理。
//上記で書いた通り、 Taskのコンポーネントで渡されたsearchメソッドを実行することにより、
//Taskコンポーネントのstateに、絞り込み後のタスク一覧を表示することができる
onClickSearch() {
this.props.search(this.state.name)
}
//検索フォームのrenderメソッド。
render() {
return (
<div className="entry">
<fieldset>
<legend>検索</legend>
<div>名前で検索: <input type="text" value={this.state.name} name="name" onChange={(e) => this.onChangeName(e)} placeholder="例:買い物" /> </div>
<div> <input type="submit" value="検索" onClick={(e) => this.onClickSearch(e)} /> </div>
</fieldset>
</div>
)
}
}
//タスク一覧についてのコンポーネント。
//こちらは上2つのクラスコンポーネントとは違い、関数コンポーネントになっている。
//クラスコンポーネントとは違い、できることが限られているため、シンプルな表示だけしたいときに使う。
const TaskList = (props) => {
//タスク一覧を表示する。
//繰り返し処理にはmap関数を使用。
return (
<div>
<table className="task">
<thead data-type="ok">
<tr><th>名前</th><th>詳細</th><th>開始日</th><th>終了日</th></tr>
</thead>
<tbody>
{props.tasks.map((task) =>
<TaskItem task={task} key={task.id} /> )}
</tbody>
</table>
</div>
)
}
//TaskListコンポーネントが受け取るpropsを定義。
//ここではタスク一覧を受け取ることができるように定義しておく。
TaskList.propTypes = {
tasks: PropTypes.array.isRequired
}
//タスクの1つの行を表すコンポーネント。
//上と同様関数コンポーネント。
const TaskItem = (props) => {
//受け取ったタスクのオブジェクトの値を、それぞれ行のセルに挿入。
const {name, description, start_date, end_date} = props.task
return (
<tr>
<td>{name}</td>
<td>{description}</td>
<td>{start_date}</td>
<td>{end_date}</td>
</tr>
)
}
//TaskItemコンポーネントが受け取るpropsを定義。
//ここではタスクオブジェクト。
TaskItem.propTypes = {
task: PropTypes.object.isRequired
}
//react-railsではこの行がないとエラーになるっぽい。
//メインとなるクラス名(ここではTask)、ここで書いているクラス名、ファイル名の3つの名前が一緒じゃないと、
//エラーが起こって実行できなかった。
export default Task
Rails側から渡ってきたタスク一覧を、メインのTaskコンポーネントに渡すところから始まってます。
最初にこのページに遷移したときは、
①渡されたtasksを、TaskコンポーネントのstateのinitialTasksに入れて、それをそのまま同じstateのtasksに挿入する。
②state.tasksをTaskListコンポーネントに渡して、ループを回してTaskモデルの内容を抜き取って表示する
という感じの処理をしているかと思います。
一方、何かしらの検索がなされた時は、
①FilterFormにあらかじめ渡してあるsearchByNameメソッドを使って、入力値による絞り込みを行う(これにより、Taskコンポーネントのstate.tasksを更新することができる)。
②更新されたstate.tasksがTaskListコンポーネントに渡される。あとは上記と同様、ループを回してTaskモデルの内容を表示する
ということをしています。
実装できたら、
rails s
でサーバーを起動し、/scheduleにアクセスします。
タスク一覧が表示でき、絞り込みができたら成功!
最後に
Reactが全くわからない状態から、サンプルの力を借りてここまで来れました。
まだ触ったばかりなので内容としては全然かと思いますが、
これからReact+Railsを始める方にとって一助となれば幸いです。
ここまで読んで頂き、ありがとうございました!