Webエンジニアの備忘録

およそ自己の作業メモとして残しております。

Reactjsのstate/propsの挙動を確認する極力シンプルなコード

以下の記事で紹介したサンプルコードです。 tak-taniguchi.hatenablog.com

概要

Reactのstate/propsの関係を簡潔に表すために書いてみました。 余計なコードをほとんど書いていないので、UIは見づらいですが動きがわかりやすいかと思います(笑)

  • 動作に成功すると、ブラウザの左上に「0」が表示されます。
  • 「0」をクリックすると数値が加算されていきます。

ファイル構成

─ /
 └index.html
 └bundle.js      // index.jsxをbabelifyして作ってください
 └index.jsx      // ソース本体

index.html

<!doctype html>

<head>
  <meta charset="utf-8">
  <title>react state/props sample</title>
</head>

<body>
  <div id="content"></div>
  <script src="./bundle.js"></script>
</body>

</html>

index.jsx

import React from 'react'
import ReactDom, { render } from 'react-dom'

/**
 * 親コンポーネントクラス
 */
var Parent = React.createClass({
  getInitialState: function () {
    // this.state.counterを初期化しています
    return {
      counter: 0
    }
  },
  handleAdd: function () {
    // this.state.counterを加算するハンドラーです
    this.setState({
      counter: this.state.counter + 1
    });
  },
  render: function () {
    // 子コンポーネントを呼び出しています。
    return (
      <Child counter={this.state.counter} onClickHandler={this.handleAdd} />
    );
  }
});

/**
 * 子コンポーネントクラス
 */
var Child = React.createClass({
  render: function () {
    // this.props.onClickHandlerは親クラスhandleAdd()を参照
    // this.props.counterは親クラスのstate.counterを参照
    // onClick()が実行されると親クラスのstate.counterが加算されるため、子クラスのthis.props.counterも表示が変わる
    return (
      <div onClick={this.props.onClickHandler}>{this.props.counter}</div>
    );
  }
});

render(
  <Parent />,
  $('#content')[0]
);

フロントエンド初心者がReactによるSPAを構築するに至るメモ

はじめに

最近Javascriptを用いたフロントエンド開発に従事することになり、取り急ぎ敷居が低そうなReactjsに飛びつきました。 jqueryを弄る程度で知識的に止まっていたので、学習は相当ステップを刻むことになってしまいました…

最近ようやくたたき台的なものが書けてきました。 これを一度節目に、私くらいのレベルからReactを始める人のため、バックエンドエンジニアからの理解のため、そして何より自分が忘れないためにここまでの流れを一旦まとめます。

学習の変遷(やってみたこと)

1. Reactチュートリアル

https://facebook.github.io/react/docs/tutorial-ja-JP.html

JSXに触れる

return <div>ほげほげ</div>
 ↓
return (
    <div>ほげほげ</div>
);

state/propsを理解する

  • propsはプロパティ、stateが状態を表す。
  • 親のstateをsetStateで更新し子のpropsが変更される、というのが基本的なアクション。

gulp導入

  • jsxの利用で必ずbabelifyが必要になるので、さっさと導入したほうがいい。
    • 以下は最低限な感じのgulpfile.jsサンプルです。(watchも導入したほうがいい)
var gulp        = require('gulp');
var pkg         = require('./package.json');
var browserify  = require('browserify');
var babelify    = require('babelify');
var source      = require('vinyl-source-stream');

gulp.task('js', function() {
  browserify({
      entries: './src/index.jsx',
      extensions: [".js", ".jsx"],
      debug: true
    })
    .transform(babelify)
    .bundle()
    .pipe(source('bundle.js'))
    .pipe(gulp.dest('./public/assets/js/'));
});

gulp.task('default', ['js']);

2. ES6で書き直してみる

参考にしたサイト

eslint

  • eslintを導入しコードを機械的にチェックさせる。
    • コーディングに安心感が出る。ソロ開発は検証者がいないので、変な書き方にきづけるよう入れておくと吉。
    • わたしはVisualStudioCode(VSC)で開発を行っています。フリーな上、非常に使いやすいです。
  • VSC + eslint
    • VisualStudioCode
    • eslintプラグイン(VSC内から追加) f:id:tak_taniguchi:20160917205230p:plain
    • eslintrc.json / .eslintignore ファイルの作成
      • jsを書いているディレクトリ下にてeslint --initを実行するとeslintrcが作られます。
      • .eslintignoreは対象にしたくないファイル(gulpfile.jsなど)を記載できます。
      • わたしの設定も晒しておきます。
{
    "env": {    // (windowなど)特定のグローバル変数を許可したりします
        "browser": true,
        "node": true
    },
    "extends": "airbnb",    // React界隈ではairbnbのコードスタイルがわりとメジャーのようです
    "plugins": [
        "react",
        "jsx-a11y",
        "import"
    ]
}

ファイル構成の見直し、クラス分割

  • クラス単位でのファイル分割
    • ページ構成要素をcomponentsとし、ディレクトリを用意しました。
    • ページ構成要素が1つの場合はそのままファイル名に、複数の場合はディレクトリを作成しindex.jsを用意しました。
      • 外部参照時にcomponents/Mainと呼び出すことで、Mainがcomponent的に分割された際もcomponents/Main/index.jsxを作れば同じように呼び出せるため。
─src
  └jsx
    └components
      └Main.jsx       // components/Mainと呼びだす
      └Todos
        └index.jsx    // components/Todosと呼びだす
        └List.jsx      // Todosページの構成要素
        └Detail.jsx   // Todosページの構成要素
  • 共通クラスの用意
    • コンポーネントも使いまわすものはsharedディレクトリにまとめました。
      • とはいえ、この段階ではテンプレートエンジンにあるinclude的に使う程度です。
─src
  └jsx
    └components
      └shared
        └Header.jsx
        └Footer.jsx

3. ルーティングの考慮

React Routerの導入

import React from 'react';
import { render } from 'react-dom';
import { Router, Route, IndexRoute, useRouterHistory } from 'react-router';
import { createHashHistory } from 'history';

import NotFound from './Components/Error/NotFound';
import Main from './Components/Main';
import Todos from './Components/Todos';
import TodosSummary from './Components/Todos/Summary';

const appHistory = useRouterHistory(createHashHistory)({ queryKey: false });

render((
  <Router history={appHistory}>
    <Route path="Todos">
      <IndexRoute name="Todos" component={Todos} />
      <Route name="TodosSummary" path="summary" component={TodosSummary} />
    </Route>
    <Route name="Main" path="/" component={Main} />
    <Route name="NotFound" path="*" component={NotFound} />
  </Router>
), document.getElementById('app'));
  • 呼び出すjsxクラスを切り替え、ルーティングのような事ができる。
    • 上記の場合はハッシュURLでのルーティングになる。(http://example.com/#/todos/summary/
    • ハッシュを利用しないこともできる。(http://example.com/todos/summary/
      • appHistory → browserHistory に置き換える。
      • 当然、アプリケーション・サーバー等でルーティングをindexに向ける等の作業が必要。

利用した書籍

入門 React ―コンポーネントベースのWebフロントエンド開発

入門 React ―コンポーネントベースのWebフロントエンド開発

ここまでで感じているReact私見(長短)

  • 良いと思ったところ
    • 仮想DOMの安心感
    • Viewに特化したわかりやすい処理系統
    • JSXでテンプレートエンジン不要
    • Flux、Redux、React Routerなどと柔軟に連携
  • 問題を感じているところ
    • デザイナーとの連携(テンプレートとロジックが近すぎるため)
    • 連携が柔軟である上でのModel、Controllerの選定リスク(Redux衰退したらどうしようとか)
    • テンプレートを内包していて容量的に重い

ひとまず締めくくります

わたしも絶賛勉強中ではありますが、上記の工程を積んでReactjsが使えるようになってきました。 次はReduxの導入・実装をおこなっていこうと思っています。

Ruby on Railsにおけるfrontend開発基盤を考えるメモ

概要

  • Ruby on Railsにおけるフロントエンド開発にて、現段階(2016年7月時点)で必要そうな技術ベース、開発規約を調査しています。
  • 唯一の方法策定ではなく、できるだけ多くの技術について俯瞰的に差異を調査します。

具体的な検討領域

フロントエンドと大分しましたが、具体的には以下のファイルが生成できればOKです。

  • html(≒テンプレートエンジン)
  • css
  • javascript
  • 上記を生成するための開発環境・タスクランナー

検討内容

html

テンプレートエンジン

tilt

GitHub - rtomayko/tilt: Generic interface to multiple Ruby template engines

複数のテンプレートエンジンに対してController側が同一コーディングできるようにしてくれるWrapperのようです。

CSS

フレームワーク

レスポンシブデザインを簡単に導入できるが、後述のマークアップ作法に抵触するので利用検討要。

  • Bootstrap
  • Foundation
  • Skeleton

メタ言語

  • less / sass

マークアップ作法

Javascript

シンタックスシュガー

フレームワーク

  • Angular
  • React
  • Aurelia
  • Vue.js

スクランナー

AssetPipelineの要否

Railsが標準で実装しているSprocketsを利用するか否かという問題があります。 こちらを利用する最大のメリットはAssetPipelineによる静的ビルドかと思いますが、ファイル構成の作法のため他のフレームワークの実装がしづらい、または特性を活かしきれない可能性があります。

Sprocketsと決別し、gulpなどに置き換える方法も紹介されていました。

メジャーツール

  • gulp
  • Grunt

その他、参照記事

rubyのメソッド引数が値渡しという話

Rubyを書き始めてまだ2〜3週間ですが、メソッドで思わぬ挙動があったので記録しておきます。

挙動が想定外だった

def test_add(arr)
  arr += [1]
  puts 'B=' + arr.to_s
end

def test_push(arr)
  arr.push(1)
  puts 'D=' + arr.to_s
end

arr = [0, 2]
puts 'A=' + arr.to_s

test_add(arr)
puts 'C=' + arr.to_s

test_push(arr)
puts 'E=' + arr.to_s

# => A=[0, 2]
# => B=[0, 2, 1]
# => C=[0, 2]
# => D=[0, 2, 1]
# => E=[0, 2, 1]  ※想定外

おや?っと思ったのはEなのですが、メソッド外でarrにtest_push()処理値が反映されていました。 Rubyのメソッドは「すべて値渡し」というふうに記憶していたからです。

オブジェクトIDによる確認

Rubyはすべての値がオブジェクトなので、オブジェクトIDを確認することにしました。

def test_add(arr)
  arr += [1]
  puts 'B=' + arr.object_id.to_s
end

def test_push(arr)
  arr.push(1)
  puts 'D=' + arr.object_id.to_s
end

arr = [0, 2]
puts 'A=' + arr.object_id.to_s

test_add(arr)
puts 'C=' + arr.object_id.to_s

test_push(arr)
puts 'E=' + arr.object_id.to_s

# => A=69943959716760
# => B=69943959716500  ※これだけ異なる
# => C=69943959716760
# => D=69943959716760
# => E=69943959716760

値渡しと言いながら、メソッド内外で利用されているオブジェクトIDが同じでした。 一点興味深かったのがarr += [1]をおこなったタイミングで新規オブジェクトが作成されている点です。

Rubyのメソッド引数はオブジェクトIDを値渡ししている

見出しの通り解釈することにしました。 そもそも参照渡しという解釈はメモリ上のポインタの話をした際に出てくるものなので、そこと混同すると参照渡しじゃないのか、という話になってくるのかと思いました。

オブジェクトをコピーして操作する

こう書けば想定通りでした。

def test_add(arr)
  arr += [1]
  puts 'B=' + arr.to_s
end

def test_push(arr)
  arr = arr.dup.push(1)
  puts 'D=' + arr.to_s
end

arr = [0, 2]
puts 'A=' + arr.to_s

test_add(arr)
puts 'C=' + arr.to_s

test_push(arr)
puts 'E=' + arr.to_s

# => A=[0, 2]
# => B=[0, 2, 1]
# => C=[0, 2]
# => D=[0, 2, 1]
# => E=[0, 2]  ※想定どおり

フロントエンド環境構築(node/nvm/npm/gulp)

以前のプロジェクトでgulpを利用していたのに、フロントエンド担当ではなかったため環境構築に携われませんでした。 正直よくわからないで使っていたので、自分で構築して何をやっていたのか考えてみます。 今回は「ドットインストール」を教材に習ったもののメモになります。

dotinstall.com ↑なので、普通にこっちをやったほうがいい。

やりたいこと

環境(ちょっとアレンジしています)

  • ubuntu(v14.04.4)
  • node(v5.12.0)
  • nvm(v0.31.2)※nodeのバージョン管理
  • npm(v3.8.6)※nodeのパッケージ管理(gulpもパッケージのひとつ)

nodeバージョンについて

  • 現時点で最新はv6.2.2だったがパッケージ対応が追いついていないものもあり、v5系で最新のバージョンを選択しました。
  • ちなみに、LTS(Long-Term Support)とstableについてもぶつかったのですが、こちらに説明がありました。

インストール

  • nvmを利用してnode/npmをインストール
$ git clone git://github.com/creationix/nvm.git ~/.nvm
$ echo 'if [[ -s ~/.nvm/nvm.sh ]] ; then source ~/.nvm/nvm.sh ; fi' >> ~/.bashrc
$ source ~/.bashrc
$ nvm --version
0.31.2
$ nvm ls-remote
 : // 利用可能なバージョンが羅列される
$ nvm install v5.12.0
$ nvm use v5.12.0
Now using node v5.12.0 (npm v3.8.6)
$ nvm alias default v5.12.0 
default -> v5.12.0
$ node -v
v5.12.0
$ npm -v
3.8.6

プロジェクト作成

$ mkdir project
project $ cd project
project $ npm init
    :    // 以下、エンターでOK(package.jsonが作成される)
project $ npm install --save-dev gulp
project $ touch gulpfile.js // あとでgulp実行内容を書く
  • こんなファイル構成になりました。
project/
    |-- node_modules/
    |    |    :
    |    |-- gulp/
    |
    |-- gulpfile.js
    |-- package.json

スクランナー(gulp)の実装

  • gulpfile.js
// src -> dist

var gulp = require('gulp');
var pkg = require('./package.json');
var imagemin = require('gulp-imagemin');

var coffee = require('gulp-coffee');
var concat = require('gulp-concat');
var uglify = require('gulp-uglify');
var plumber = require('gulp-plumber');
var header = require('gulp-header');
var webserver = require('gulp-webserver');

// htmlファイルコピー
gulp.task('html', function() {
    gulp.src('./src/index.html')
        .pipe(gulp.dest('./dist'));     // htmlコピー
});

// 画像ファイルコピー
gulp.task('img', function() {
    gulp.src('./src/img/*.jpg')
        .pipe(imagemin())               // 画像圧縮
        .pipe(gulp.dest('./dist/img')); // 画像コピー
});

// JS作成
gulp.task('js', function() {
    gulp.src('./src/coffee/*.coffee')
        .pipe(plumber())
        .pipe(coffee())             // JS変換
        .pipe(concat('all.min.js')) // JS結合
        .pipe(uglify())             // JS圧縮
        .pipe(header('/* copyright <%= pkg.name %> */', {pkg: pkg})) // ヘッダー
        .pipe(gulp.dest('./dist/js'));
});

// watch
gulp.task('watch', function() {
    gulp.watch('./src/coffee/*.coffee', ['js'])
    gulp.watch('./src/*.html', ['html'])
});

// webserver reload
gulp.task('webserver', function() {
    gulp.src('./dist')
        .pipe(
            webserver({
                host: '192.168.33.10',
                livereload: true
            })
        );
});

gulp.task('default', ['html', 'img', 'js', 'watch', 'webserver']);

スクランナーの実行

$ gulp
  • 上記gulpfile.jsで実行される内容
    • src/ディレクトリ下のhtmlファイルをdist/下にコピー
    • src/img/ディレクトリ下のjpgファイルをdist/img/下に圧縮してコピー
    • src/coffee/ディレクトリ下のcoffeeファイルをdist/js/下にJSに変換、連結・圧縮してコピー
      • おまけでヘッダーにプロジェクト名を挿入している
    • 上記の一通りを実行しつつローカルサーバー起動、watch待機する。
      • JS/htmlファイル更新時にwatch
      • サーバーはwatch動作時にreload

画像ファイル名の難読化や、sassコンパイルなども同じ要領で追加していけると思います。 ざっくり何をやっているのか、どのようにやっているのかがわかりました。

PostgreSQLのインストール

UbuntuPostgreSQLをインストールしてみました。 使い方についてもあまり知らなかったので、メモがてらCUI操作まで描いておきます。

パッケージのインストール

  • こちらで本体、クライアントなど一通りの関連パッケージがインストールされます。
$ sudo apt-get update
$ sudo apt-get install postgresql
$ sudo aptitude install libpq-dev
  • libpq-devはGemにて導入する際に必要とされたので、一応入れておくことにしました。

起動・終了コマンド

  • MySQLやNginxとだいたい同じ
$ sudo service postgresql start
$ sudo service postgresql stop
$ sudo service postgresql restart

コンソール

  • postgreというユーザーが作成されるので、最初はこちらでアクセス
$ sudo su - postgres // スイッチユーザー
$ psql  // PostgreSQL起動

postgres=#
  • ついでにvagrantに権限付与(suしなくてすむようになる)
    • ロール作成と、ユーザー名と同一のDatabase作成をおこなう。
postgres=# create role vagrant with createdb login;
CREATE ROLE
postgres=# \du
                             List of roles
 Role name |                   Attributes                   | Member of
-----------+------------------------------------------------+-----------
 postgres  | Superuser, Create role, Create DB, Replication | {}
 vagrant   | Create DB                                      | {}

postgres=# create database vagrant;
CREATE DATABASE
postgres=# \l
                                  List of databases
   Name    |  Owner   | Encoding |   Collate   |    Ctype    |   Access privileges
-----------+----------+----------+-------------+-------------+-----------------------
 postgres  | postgres | UTF8     | en_US.UTF-8 | en_US.UTF-8 |
 template0 | postgres | UTF8     | en_US.UTF-8 | en_US.UTF-8 | =c/postgres          +
           |          |          |             |             | postgres=CTc/postgres
 template1 | postgres | UTF8     | en_US.UTF-8 | en_US.UTF-8 | =c/postgres          +
           |          |          |             |             | postgres=CTc/postgres
 vagrant   | postgres | UTF8     | en_US.UTF-8 | en_US.UTF-8 |
(4 rows)

postgres=# \q
↑終了コマンド
  • ちなみに、権限削除は「drop role vagrant;」となる。

GUIツールの利用(Mac

こちらのツールを利用してみました。

f:id:tak_taniguchi:20160627165428p:plain

設定はこんな感じ、vagrantユーザーでvagrantSSHしつつ、ローカル(Vagrant内)のPostgreSQLにつなぐ感じです。 vagrantユーザーログイン時のデフォルトパスワードは「vagrant」です。

Vagrant上でNginx+Unicornサーバー設定

rails serverコマンドでアプリケーションサーバーを立ち上げるのも手間になってきたので、Unicornを入れてみました。 こちらは前回記事の続きの作業になります。

tak-taniguchi.hatenablog.com

Unicornをインストール

  • 以下、Vagrant内での作業になります。
  • Gemインストールをおこなう
gem install unicorn
  • bundle installをおこなう
$ cd /vagrant/myapp  # プロジェクトディレクトリ
$ vi Gemfile
gem 'unicorn' # 書き足す

$ bundle install
  • unicorn.rbコンフィグの作成
$ vi /vagrant/myapp/config/unicorn.rb # 以下のような内容で新規作成する
rails_root = File.expand_path('../../', __FILE__)
rails_env = ENV['RAILS_ENV'] || "development"

worker_processes 2
working_directory rails_root

listen "/tmp/#{rails_env}_unicorn.sock"
pid "/tmp/#{rails_env}_unicorn.pid"

stderr_path "#{rails_root}/log/#{rails_env}_unicorn_error.log"
stdout_path "#{rails_root}/log/#{rails_env}_unicorn.log"
  • Nginx側設定
  • conf作成
    • 以下の{RAILS_ROOT}はプロジェクトルート、{LOG_PATH}はnginxがログを残すパスを指定。
    • 指定ドメインはローカルなので、globalに存在しないドメインがよいです。
$ vi /etc/nginx/site_avabable/virtualhost-myapp.conf  # nginx.confでincludeされる
upstream unicorn {
  server unix:/tmp/development_unicorn.sock;
}

server {
  listen 80;
  server_name {指定ドメイン};
  root {RAILS_ROOT}/public;

  access_log {LOG_PATH}/access.log;
  error_log {LOG_PATH}/error.log;

  client_max_body_size 100m;
  error_page 500 502 503 504 /500.html;
  try_files $uri/index.html $uri @unicorn;

  location @unicorn {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_pass http://unicorn;
  }
}

$ ln -s /etc/nginx/site_avabable/virtualhost-myapp.conf /etc/nginx/site_enable/virtualhost-myapp.conf
$ sudo service nginx restart
  • Uniornを起動
$ bundle exec unicorn -D -c config/unicorn.rb -E development
  • ローカルPCのhosts修正
    • この時点でhttp://{指定ドメイン}/が閲覧可能になる。
    • 以下、ローカルPC(Mac)での作業になります。
$ sudo vi /etc/hosts
# VagrantIPアドレスと指定ドメインをスペースで区切り記載
# グローバルに存在するドメインを記載すると、アクセスがVagrantに向いてしまうので注意!
192.168.33.101  {指定ドメイン}

コンフィグ等はこちらの記事にあったものを少し書き換えました。 - Rails4.2 を Nginx + Unicorn で動作させる - Shred IT!!!! - rails + nginx + unicorn連携 - Qiita

動作確認方法

  • Unicornプロセス
    • 以下のコマンドでmaster及びworkerプロセスが確認できる。masterをkillでUnicornを終了できる。
$ ps aux|grep unicorn
  • Unicornで動いているか
    • デフォルトのままであればプロジェクト下のlog/development.logにアクセスログが落ちる。

developmentなんで開発系までですが、ざっくり動作しました。