LoginSignup
7
9

More than 3 years have passed since last update.

React+Railsでモダン()なTODOアプリを作ってみた 〜備忘録も兼ねて〜

Last updated at Posted at 2019-12-21

Opt Technologies Advent Calendar 2019の21日目の記事です。

はじめに

2019年4月に株式会社オプトに入社したsh1okohと申します。
なんやかんやありまして、今はReact, Redux, Railsなどを使ったプロダクトで、日々奮闘しております。
今回は、お仕事中に出会った技術スタックを使ってモダン()なTODOアプリを作ってみたので、各種ライブラリの紹介をしつつ、Todoアプリの紹介をしていきたいと思います。(備忘録的な目的もあります)

ちなみに、今回のTODOアプリのソースコードは下記リンクにありますので、
興味のある方はみてみてください。

対象読者

今回は、テーマ的にも内容自体は広く浅くになっております。
各種ライブラリを使ったSPAの全体像を把握したい方などを対象としており、各ライブラリを深く学びたいといった方は対象外となっておりますので、よろしくお願いします。

今回使用した技術

フロントエンド

  • JavaScript (業務ではTypeScriptを使っています)
  • React
  • Redux
  • axios
  • eslint
  • prettier

バックエンド

  • Ruby 2.5.7
  • Ruby on Rails 5.2.4
  • rubocop
  • guard
  • rspec
  • rack-cors

CI/CD

  • Circle CI

フロントエンド編

React とは

A JavaScript library for building user interfaces

Facebookの作ったJavascriptライブラリです。
MVC(Model View Controller)モデルでいうとVの部分を提供しています。

Reactの考え方

Component-Based
Build encapsulated components that manage their own state, then compose them to make complex UIs.

Reactでは、画面をComponentの親子関係を持つツリーとして構成します。
各コンポーネントは、親から渡されたprops(プロパティ)、State(自分の状態)を元に、renderメソッドでDOM(Virtual DOM)を生成します。

axios とは

Promise based HTTP client for the browser and node.js

Http通信を簡単に行うことができるJavascriptライブラリです。
主な特徴としては、

  • ChromeやFirefox, Safariなどのブラウザに対応
  • リクエストとレスポンスデータを変換
  • Promise APIをサポート

と言った特徴があります。

今回は、このライブラリを使ってAPIとの通信を行なっていきます。

実装

以下がコードになります。

import React from 'react';
import axios from 'axios';

const Title = ({todoCount}) => {
  return (
    <div>
      {
          todoCount > 0 ? <h1>{todoCount}つのタスクがあります</h1> :
          <h1>タスクがありません</h1>
      }
    </div>
  );
}

const TodoForm = ({addTodo}) => {
  let input;
  return (
    <form onSubmit={(e) => {
        e.preventDefault();
        addTodo(input.value);
        input.value = '';
      }}>
      <input className="form-control col-md-12" ref={node => {
        input = node;
      }} />
      <br />
    </form>
  );
};

const Todo = ({todo, remove}) => {
  return (
    <li>
      <a href="#" className="list-group-item">{todo.contents}</a>
      <button onClick={() => {remove(todo.id)}}>削除</button>
    </li>
  );
}

const TodoList = ({todos, remove}) => {
  const todoNode = todos.map((todo) => {
    return (<Todo todo={todo} key={todo.id} remove={remove} />)
  });
  return (<div className="list-group" style={{marginTop:'30px'}}>{todoNode}</div>);
}

window.id = 0;
class TodoApp extends React.Component{
  constructor(props){
    super(props);
    this.state = {
      data: []
    }
    this.apiUrl = 'http://localhost:3000/api/todos/'
  }

  componentDidMount(){
    axios.get(this.apiUrl)
      .then((res) => {
        this.setState({ data: res.data });
      });
  }

  addTodo(val){
    const todo = {contents: val}
    axios.post(this.apiUrl, todo)
       .then((res) => {
          this.state.data.push(res.data);
          this.setState({data: this.state.data});
       });
  }

  handleRemove(id){
    const remainder = this.state.data.filter((todo) => {
      if(todo.id !== id) return todo;
    });

    axios.delete(this.apiUrl+id)
      .then((res) => {
        this.setState({data: remainder});
      })
  }

  render(){

    return (
      <div>
        <Title todoCount={this.state.data.length}/>
        <TodoForm addTodo={this.addTodo.bind(this)}/>
        <TodoList
          todos={this.state.data}
          remove={this.handleRemove.bind(this)}
        />
      </div>
    );
  }
}

export default TodoApp

上記のコードを見てもらうと分かりやすいと思うのですが、TodoAppコンポーネントにconstructorメソッド、componentDidMountメソッド, renderメソッドを定義しています。componentDidMountメソッド, renderメソッドは、Reactのライフサイクルメソッドの一種です。componentDidMountメソッドは、出力が DOM にレンダーされた後に実行されます。ここではライフサイクルの話は割愛しますので、気になる方は下記リンクを参考にしてみてください。

で、各関数コンポーネントに、propsを渡し、その値に応じた描画を行なうという流れになっております。

APIの疎通に関しては、

axios.get

axios.post

で行なって、APIからのレスポンスデータを取得したりしています。

で、実際に見てみると、初期の出力結果が、こうなります。(めっちゃ殺風景・・・。)
スクリーンショット 2019-12-21 22.14.48.png

で入力欄に値を入力してEnterを押すと、タスクが追加できて、削除までできます。
スクリーンショット 2019-12-21 22.17.35.png

本当は最低限CRUDの実装はしたかったのですが、諸々の事情により、updateの機能は後日追記いたします。

バックエンド編

Ruby on Rails とは

Rails is a web-application framework that includes everything needed to create database-backed web applications according to the Model-View-Controller (MVC) pattern.

Model-view-controller パターンを採用したフレームワークです。
特にActiceRecordは個人的には強力な機能だと思っています。

今回はView側はReactで実装していますので、RailsのAPIモードを使って実装しました。

APIモードは、下記のようにコマンドライン引数に --apiとつけるだけで、できてしまいます(さすがRails!)

rails new my_api --api

APIモード
https://guides.rubyonrails.org/api_app.html

Active Record とは

Active Record is the M in MVC - the model - which is the layer of the system responsible for representing business data and logic. Active Record facilitates the creation and use of business objects whose data requires persistent storage to a database. It is an implementation of the Active Record pattern which itself is a description of an Object Relational Mapping system.

MVCモデル言うところの、Mの部分に相当するものです。
ORM(O/Rマッピング)システムに記述されている「Active Recordパターン」を実装したもので、このパターンと同じ名前が付けられています。

ORM(O/Rマッピング)とは、アプリケーションが持つオブジェクトを、RDBMSのテーブルに接続することです。
ORMを用いると、SQL文を書かずに、簡潔なコードだけで、テーブルのレコードのデータを取得できたり、更新できたりしてとても便利です。

例えば、ターミナル上で、

rails console

を叩き

Todo.all

などとすると、下記のような出力結果を得ることができます。

スクリーンショット 2019-12-21 23.16.15.png

またワンライナーでも書くことができるので、
SQL文を書くより遥かに楽だと言うことが分かると思います。

他にも

  • モデル同士のアソシエーションを表現する
  • 関連付けられているモデル間の継承階層を表現する
  • データをデータベースで永続化する前にバリデーションを行う

などの特徴を持っています。

RuboCop とは

RuboCop is a Ruby static code analyzer and code formatter.

rubyのコードアナライザーであり、フォーマッターです。
例えば、チーム開発をするときにコード規約を定めると思うのですが、RuboCopを用いると捗ります。
また、警告を出すのみに止まらず、いくつかの問題を自動で直してくれることもしてくれます。
例えば、ターミナル上で下記のコマンドを実行すると、一括でフォーマットをかけてくれたりしますので、とても便利です。

bundle exec rubocop -a

参考
https://rubocop.readthedocs.io/en/stable/

実装

実装はとても簡単で、ターミナル上で、下記のコマンドをバーン!すると、
ルーティングやコントローラー、モデルからテーブル定義までの雛形を作ってくれます。

bundle exec rails generate scaffold Todo contents:string

実際のコード

controllers/todos_controller.rb

class TodosController < ApplicationController
  before_action :set_todo, only: [:show, :edit, :update, :destroy]

  def index
    @todos = Todo.all
    render json: @todos
  end

  def show
    render json: @todo
  end

  def new
    @todo = Todo.new
  end

  def edit
  end

  def create
    @todo = Todo.new(create_params)
    if @todo.save
      render json: @todo
    else
      render :new
    end
  end

  def update
    if @todo.update!(update_params)
      render json: @todo
    else
      render :edit
    end
  end

  def destroy
    @todo.destroy
    render json: @todos
  end

  private
    def set_todo
      @todo = Todo.find(params[:id])
    end

    def create_params
      params.require(:todo).permit(:contents)
    end

    def update_params
      params.require(:todo).permit(%i[id contents])
    end

    def destroy_params
      params.require(:todo).permit(:id)
    end
end

config/routes.rb

Rails.application.routes.draw do
  scope :api, defaults: { format: :json } do
    resources :todos, only: %i[show index create update destroy]
  end
end

models/todo.rb

class Todo < ApplicationRecord
  def as_json(options = {})
    super(options.reverse_merge(except: %i[created_at updated_at]))
  end
end

db/schema.rb

ActiveRecord::Schema.define(version: 2019_12_15_104719) do

  create_table "todos", force: :cascade do |t|
    t.string "contents"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

end

今回のAPIの設計方針として、REST APIを採用しました。
REST APIについては、下記を参照してみてください。
https://restfulapi.net/

CI編

後日書きます

まとめ

半年間、自分が使ってきた技術を用いて、アプリーケーションを開発するのは良い振り返りにもなるし、一年の節目にも良さそうと思いました。今後も、何か学ぶ機会があれば、やってみるといいかもと思いました。
実はまだまだ書きたいこともあるので、後日追記するか、別の記事に載せることにします。

その他の参考資料

https://qiita.com/matzkoh/items/90baab22ad489b78384b
https://restful-api-guidelines-ja.netlify.com/
https://scotch.io/tutorials/create-a-simple-to-do-app-with-react

7
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
9