いっきのblog

技術とか色々

Reactでクエリパラメーターが変更された場合のリソースの更新する方法

どうも、くずきです。

Reactでクエリパラメーターが変更された際にどうやってリソースの更新をするかについて書きたいと思います。
当たり前だがreact-routerではhogehoge.comhogehoge.com?sort=abcは同じコンポーネントと認識されるため、画面の再描画などは行われない。
そこで、クエリパラーメーターが変更されるたびにリソースの更新する実装してみた。

componentWillReceivePropsを使った更新

Reactには、componentWillReceivePropsといわれるpropsが更新されるたびに呼ばれる関数がある。
react-routerを使っているならば、クエリパラメーターに変更があった場合もここが呼ばれるみたいだ。

github.com

これがわかってしまえばすごく簡単。

  componentWillReceiveProps(nextProps) {
    if (nextProps.location.search !== this.props.location.search) {
      // Actionから新しいリソースを取得しに行く
      Action.get(nextProps.location.search);
    }
  }

このように、次のpropslocation.searchを比較することで更新するかしないかを決めることができる。

簡単なんですけどグーグル先生でパッと検索できず、実は他の簡単なやり方を皆は使ってるんじゃないかと思ってるので知ってたらご連絡くだせい。。

ReactのEventEmitterで登録したリスナが削除できない問題の解決法

どうも、くずきです。

EventEmitterで登録したリスナが削除できない問題について解決した方法をメモしときたいと思います。

構成

  • EventEmitter
    • ErrorStore(EventEmitterを継承したクラスをStoreとして利用)
    • サーバーサイドから取得したエラーを登録したリスナに通知
  • Component
    • App.js
    • エラー時の関数をEventEmitter(Store)に登録する

EventEmitter

本記事では書いてないが、Actionでサーバー側に通信した後エラーだった場合に呼ばれるStore
this.onで通知するリスナを登録している。

class ErrorStore extends EventEmitter {
  handleActions(action) {
    if(action.type === "ERROR"){
      // エラー通知
      if(action.res.hasOwnProperty("statusCode")) {
        switch (action.res.statusCode) {
          case 500:
            break;
          case 400:
            this.emit(EVENT.ERROR.VALIDATION, action.res.body.errors);
            break;
          case 401:
            this.emit(EVENT.ERROR.AUTHENTICATION, action.res);
            break;
          default:
            break;
        }
      }
    }
  }

  addValidListener(listener) {
    this.on(EVENT.ERROR.VALIDATION, listener);
  }

  removeValidListener(listener) {
    this.removeListener(EVENT.ERROR.VALIDATION, listener);
  }

  addAuthListener(listener) {
    this.on(EVENT.ERROR.AUTHENTICATION, listener);
  }

  removeAuthListener(listener) {
    this.removeListener(EVENT.ERROR.AUTHENTICATION, listener);
  }

}

const errorStore = new ErrorStore();
dispatcher.register(errorStore.handleActions.bind(errorStore));

export default errorStore;

初期のComponent

一番初期(問題があるとき)につくったコンポーネント
handleError関数をErrorStoreaddValidListenerでリスナに登録している。

class App extends Component {

  constructor(props) {
    super();
    this.state = {
      errors: null
    };

  }

  componentWillMount() {
    errorStore.addValidListener(this.handleError.bind(this));
  }
  
  componentWillUnmount() {
    errorStore.removeValidListener(this.handleError.bind(this));
  }
  
  handleError(_errors) {
    // エラーの内容を解析する関数を呼ぶ
    let errors = Validation.getValidationErrors(_errors);
    this.setState({
      errors: errors
    });
  }


  render() {
    if (this.state.errors) { 
      ....
    }
  }
}

当初はcomponentWillUnmountで既に登録したリスナ(this.handleError.bind(this))を消すはずが

Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the undefined component.

というエラーを吐き、できなかった。

EventEmitterのコードを追い、removeListenerで消す条件を確認したところ、関数同士を比較してるようだ。

実際にthis.handleError.bind(this) === this.handleError.bind(this)を行なったところfalseになり、bindした関数同士はイコールにならないみたい。
bind関数の仕様をみると、確かに関数を新たに作成してるっぽいのでこれはイコールにはならない。。。

改善版Component

ここまで分かればもう簡単で、コンストラクタでbindした関数を保持し、それを利用すれば良い。

class App extends Component {

  constructor(props) {
    super();
    this.state = {
      errors: null
    };
    
    this.handleError = this.handleError.bind(this);

  }

  componentWillMount() {
    errorStore.addValidListener(this.handleError);
  }
  
  componentWillUnmount() {
    errorStore.removeValidListener(this.handleError);
  }
  
  handleError(_errors) {
    // エラーの内容を解析する関数を呼ぶ
    let errors = Validation.getValidationErrors(_errors);
    this.setState({
      errors: errors
    });
  }


  render() {
    if (this.state.errors) { 
      ....
    }
  }
}

これでエラーも消えてめでたしめでたし。
ただjavascriptはこういったエラーを気にしないと気付かずバグをうんだりするので、もっと理解しないとなと思います。

Docker Compose + LaravelをCircleCI2.0上でテストする

どうも、くずきです。
こないだ久々にCircleCIを使ったらバージョンが上がってたのとdocker-composeを使ったやり方が変わっていたので、とりあえずテストまでできたレベルメモっておきます。

各バージョン

  • Dokcer(for MacOS)
    • Docker version 17.03.1-ce, build c6d412e
  • docker-compose
    • docker-compose version 1.11.2, build dfed245
  • Laravel
    • 5.5.11
  • PHP
    • 7.1.1
  • Mysql
    • 5.7
  • CircleCI
    • 2.0

※ 今回アプリケーションとしてLaravel使うけど、構築などの説明はしないです。

Dockefileの設定

すでにLaravelのアプリケーションがある前提で、さっさと構築。

FROM php:7.1.1-apache
RUN a2enmod rewrite
RUN set -ex \
  && buildDeps=' \
    libfreetype6-dev \
    libjpeg62-turbo-dev \
    libpng12-dev \
  ' \
  && apt-get update \
  && apt-get install -y --no-install-recommends \
    $buildDeps \
    libmcrypt-dev \
    libicu-dev \
    curl \
    zip \
    unzip \
  && rm -rf /var/lib/apt/lists/* \
  && docker-php-ext-configure \
    intl \
  && docker-php-ext-install \
    pdo_mysql \
    mysqli \
    mbstring \
    gd \
    iconv \
    mcrypt \
    intl \
  && curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \
  && apt-get purge -y --auto-remove $buildDeps

COPY php.ini /usr/local/etc/php/

Dockerfileとして、アプリケーションのルートディレクトリに配置。
目新しいことはしてない一般的なphp周りの設定。

docker-compose.ymlの設定

docker-compose.ymlを以下のように書く。

version: '2'

services:
  datastore:
    image: busybox
    volumes:
     - /var/lib/mysql
  db:
    image: mysql:5.7
    command: >
      bash -c '
      mkdir /var/log/mysql &&
      touch /var/log/mysql/general.log &&
      chown mysql:mysql /var/log/mysql/general.log &&
      tail -f /var/log/mysql/general.log &
      /entrypoint.sh mysqld
      --character-set-server=utf8mb4
      --collation-server=utf8mb4_unicode_ci
      --general-log=true
      --general-log-file=/var/log/mysql/general.log
      '
    environment:
      - MYSQL_ALLOW_EMPTY_PASSWORD=true
      - MYSQL_DATABASE=[データベース名]
    ports:
      - "3333:3306"
    volumes_from:
      - datastore
  cache:
    image: redis:3.2.3-alpine
    ports:
      - "6379:6379"
  app:
    build: .
    ports:
      - "8000:80"
    volumes:
      - .:/var/www/html
      - ./000-default.conf:/etc/apache2/sites-available/000-default.conf
    depends_on:
      - db
      - cache

Dockerfileと同じくルートディレクトリに配置。
ここも特に目新しいことはしてないが、mysql.logを常にtailしたかったので、いらない人はその部分を消してもらえると良いです。
一応apache000-default.confも載せておく。

<VirtualHost *:80>
    DocumentRoot /var/www/html/public
    <Directory "/var/www/html/public">
            AllowOverride All
    </Directory>
    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

CircleCI用の.envファイルの設定

APP_NAME=Laravel
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_LOG_LEVEL=debug
APP_URL=http://localhost

DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=[データベース名]
DB_USERNAME=root
DB_PASSWORD=

BROADCAST_DRIVER=log
CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_DRIVER=sync

REDIS_HOST=cache
REDIS_PASSWORD=null
REDIS_PORT=6379

MAIL_DRIVER=smtp
MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=
MAIL_FROM_NAME=
MAIL_USERNAME=
MAIL_PASSWORD=
MAIL_PRETEND=false

CircleCI用にLaravel.env.circleファイルの設定。
DB_HOSTREDIS_HOSTdocker-composeで設定した名前を書く。

CricleCIの設定

最後に、CircleCIの設定ファイルを.circleci/config.ymlに書く。

versin: 2
jobs:
  build:
    machine: true
    steps:
      - checkout
      - run:
          name: Setup docker
          command: |
            docker-compose build
            docker-compose up -d
            docker-compose run app composer install
      - run:
          name: Setup Laravel
          command: |
            cp .env.circleci .env
            docker-compose run app php artisan key:generate
            docker-compose run app php artisan migrate:refresh --seed
      - run:
          name: Run Test
          command: |
            docker-compose run app vendor/bin/phpunit

CircleCI2.0の新機能であるworkflowsを使ってないので物足りなさがあるが、これで一通り設定できた。

f:id:kzkohashi:20170929193055p:plain

CircleCIでの結果画面。

今後

今回の設定だと、そもそも毎回docker-composeをビルドするせいで遅く、キャッシュさせる必要がある。
CircleCI2.0だとsave_cacherestore_cacheを使えばできそうみたい。workflowsも含めて、次回へのお楽しみということで・・・。

とあるWebサービスの使用技術の調べ方

どうも、くずきです。

最近とあるWebサービスを知り合いと見ていて、

  • このWebサービスってどういう技術使ってるんだろう?
  • このグラフは何かライブラリ使っているのか?

と言っていたので、自分なりの調べ方について書いてみようかなと思います。

【結果】知りたい別の調べ方

やり方から書くと

www.wappalyzer.com

  • 大体ざっくりの使用技術わかる
  • かなり重宝してる(というか見ていて面白い)
  • 見えている部分の技術しかわからない
  • StackShare
    • 登録してくれてれば、見えない部分の技術も書いてくれてる
    • 登録しない場合が多いので、あればラッキー
    • 技術トレンド追う方にも使える
  • ググル
    • 基本ですね
  • ThoughWorks Radar
    • ThoughWorksがレポートで出している、技術トレンドみたいなやつ
    • 使用技術を調べたいというより、その技術の立ち位置とか見やすい
  • GithubTrend

余計なものも書いてしまいましたが大体Webサービスの技術で知りたいことってWappalyzerあれば結構事足りること多いです。 

Wapplalyzerの使い方

Chromeのプラグインで入れてしまえば終わり。 

こないだUserLocalのサイトで良さげなグラフがあったので、何使ってるんだろーと覗いて、そのまま採用した例です。

f:id:kzkohashi:20170922182428p:plain

覗いて見ると、Javascript GraphicsのところのグラフっぽいライブラリのHighchartsを見つけた。
あとはHighchartsをググれば調査は終了。
もちろん対応していないライブラリなどは覗けないので・・・そこは割り切り。

どんなのが調べられるのかが気になる方は検知可能なアプリケーション一覧があるので見とくといいです。

StackShareの使い方

StackShareのサイトへ行き

f:id:kzkohashi:20170922182453p:plain

検索窓からサービス名や会社名を打てばでてくる。   登録されてない場合はでないので・・・あきらめましょう。

自社のサービスをPR用に登録しておくのはありかもしれないですね。
(もしあったらお〜と自分はなります) 

StackShareはトレンドを見れたりするので、そっちのほうが面白かったりする。 ただ、有名どころがやっぱり上位きてしまうので・・・、タグなどを掘り下げてみていくと色々見つかる。

ThoughWorks RadarとGithubTrend

この二つは技術トレンド追うだけのものなので・・・大きな流れを見る場合はThoughWorks Radar、ライブラリなどや流行を見たい場合はGithubTrendという使い方かなー。。今の所。

以上です。よくよく考えたら求人から追ったほうが早いですね。

react-routerを使ったルーティング処理

どうも、くずきです。

こないだ、create-react-appを本番環境で使ってみた(導入編)について紹介しました。

kzkohashi.hatenablog.com

今回はreact-routerを使ったルーティング処理をやてみます。

バージョンは4.2.2を利用。 バージョン3の時とガラッと変わったみたいだけど、4からなのであまり知りませぬ。 興味がある人は検索してみてくだせい。

そーろーな方は、ここみてください。

react-router-domの追加

packege.jsonreact-router-domを追加する。
Webサイト作る人はreact-router-domにしとけば問題ないと思う。

{
  "name": "create-react-app-practice",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "react": "^15.6.1",
    "react-dom": "^15.6.1",
    "react-router-dom": "^4.2.2", # 追加
    "react-scripts": "1.0.13"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  }
}

忘れずインストール

npm install

これで下準備は完了。

Routes Componentの追加

ルーティングの起点となるComponentを作る。

まず、src以下にRoutes.jsを追加する。

import React, { Component } from "react";
import { BrowserRouter, Route } from "react-router-dom";
import App from "./App";

export class Routes extends Component {
  render(){
    return (
      <BrowserRouter>
        <Route exact path="/" component={App}/>
      </BrowserRouter>
    )
  }
}

export default Routes;

<BrowserRouter>がルーティングするための肝となる部分で、 構築するサイトが静的なファイルのみで構成されてないならこれでいいと思う。

既に存在するApp(Component)をインポートし、<Route>でパスとコンポーネントを指定している。

index.jsで読み込まれるコンポーネントの変更

最後に、index.jsAppの部分を、先ほど作ったRoutesに変更してあげれば完成です。

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import Routes from './Routes'; # 変更
import registerServiceWorker from './registerServiceWorker';

ReactDOM.render(<Routes />, document.getElementById('root')); # 変更
registerServiceWorker();

f:id:kzkohashi:20170915193653p:plain

とりあえず終わり。

(おまけ)Switchを利用したNotFoundページの作り方

あまりに簡単すぎたので、もう少しreact-routerの機能を使ってみます。 react-routerにはSwitchと呼ばれる排他的レンダリングする機能があります。
[参考] github.com

ようは、pathにマッチしなかったらpathがついてないコンポーネントを表示することができる。

src以下にNotFound.jsを追加する。

import React, { Component } from "react";

class NotFound extends Component {

  render() {
    return (
      <div clasName="NotFound">
        <p><strong>ページが見つかりません。</strong></p>
      </div>
    )
  }
}

export default NotFound;

ページが見つかりませんとでるだけのコンポーネント

次にRoutesに実装する。

import React, { Component } from "react";
import { BrowserRouter, Switch, Route } from "react-router-dom";
import App from "./App";
import NotFound from "./NotFound"; # 追加


export class Routes extends Component {
  render(){
    return (
      <BrowserRouter>
        <Switch> # 追加
          <Route exact path="/" component={App}/>
          <Route component={NotFound}/> # 追加
        </Switch> # 追加
      </BrowserRouter>
    )
  }
}

export default Routes;

実際に今回指定していないpathでアクセスして見ると

f:id:kzkohashi:20170915193659p:plain

このようにSwitchの機能を使ったNotFoundの実装ができた。

以上です。
こちらのリポジトリで、今回の内容も含めて随時commitしていってます。

github.com

create-react-appを本番環境で使ってみた(導入編)

どうも、くずきです。くずさんって呼ばれるの目指してます。

React経験0の僕が、会社の新規WebアプリでReactを使った例をご紹介します。

 Reactについての説明はまた後日にするとして、手を動かしながら理解していきましょう。

create-react-appとは?

github.com

React(というかフロントエンド)の課題として、一からの構築がなり大変なのは構築したことある方はお分かりかと思います。

 

Get Started Immediately

You don’t need to install or configure tools like Webpack or Babel. They are preconfigured and hidden so that you can focus on the code.

Just create a project, and you’re good to go.

READEMEから引用したもの。

TOEIC400点代の僕がやんわり略すと、「お前はコードを書くことに集中しろ」だ。 つまり、イニシャルの構築で必要なWebpackなどそういうのは俺がやっとくぜと・・・かなり粋な計らいですね。

実際にやってみましょう。 (ほとんど上記のgithubページのやり方なのでそちらみた方が早いっちゃ早い)

create-react-appのインストール

$ npm install -g create-react-app

 

npmでグローバルでインストールする。

$ npm -v 
3.10.9

また、一応npmのバージョンのせておきます。

Reactアプリの構築

次に、実際にアプリを構築していく。

$ create-react-app create-react-app-practice

色々インストールされる。 webpackとかbableとかフロントエンドで知ったような名前がでてきてます。

$ cd create-react-app-practice
$ tree -L 2 -I 'node_modules'
.
├── README.md
├── package.json
├── public
│   ├── favicon.ico
│   ├── index.html
│   └── manifest.json
└── src
├── App.css
├── App.js
├── App.test.js
├── index.css
├── index.js
├── logo.svg
└── registerServiceWorker.js

実際にフォルダに入って中身を見てみる。 ちょっとオシャレにみたいので、treeコマンドを使うといい感じにみれますよ。 node_modsulesは余計なのがいっぱいなので省く。

Reactの起動

ここまでも簡単ですが、ここまできても簡単です。 フォルダに入った状態で、

$ npm run start

とやれば、勝手に http://localhost:3000でReactのアプリが起動ができる。

f:id:kzkohashi:20170910210307p:plain

index.jsApp Componentをよんでるだけなので、構築をとりあえず楽にしたって意外は特になんもしてないですね。

(おまけ)githubにあげる

せっかくなんであげよう。

フォルダに入った状態で

$ git init
$ git remote add origin [自分のリポジトリURL]
$ git add .
$ git commit -m "create-react-appの導入編"
$ git push origin master

以上で終わり。

こちらのリポジトリで、今後の内容も含めてcommitしていく予定〜〜。

github.com

にんにちは

kzkohashi です。すごく名前が言いづらい、なんでこのHNしてしまったのでしょう。

前のSayKichoの方が少し言いやすかったのではないかと考えたんですが、「くず」さんって呼ばれるのも悪くないと思い始めました。

 

適度にブログ書いていこうかと思います。