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 にレンダーされた後に実行されます。ここではライフサイクルの話は割愛しますので、気になる方は下記リンクを参考にしてみてください。
- https://ja.reactjs.org/docs/state-and-lifecycle.html
- https://qiita.com/Julia0709/items/3c3fc8d29fd2e56ed7a9
で、各関数コンポーネントに、propsを渡し、その値に応じた描画を行なうという流れになっております。
APIの疎通に関しては、
axios.get
や
axios.post
で行なって、APIからのレスポンスデータを取得したりしています。
で、実際に見てみると、初期の出力結果が、こうなります。(めっちゃ殺風景・・・。)
で入力欄に値を入力してEnterを押すと、タスクが追加できて、削除までできます。
本当は最低限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
などとすると、下記のような出力結果を得ることができます。
またワンライナーでも書くことができるので、
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