RIT Tech Blog

株式会社RITのエンジニアが知見を共有する技術ブログです。

React Hooks使ってリファクタしてみた話

CTOの福田です

React 16.8 リリースされましたね

新機能のReact Hooksを使って自社サービスのコードをリファクタしてみたので、全体的な使用感とかハマったポイントを挙げていこうと思います。

前提

  • Reduxは使ってません
  • statefulなコンポーネントを多用してます
  • React + React RouterのSPA
  • TypeScript

ざっくり

  • 〇 Class ComponentがFunction Componentにできてネストが一段減って行数も微妙に削減されてうれしい
  • useState使うとsetState({hoge: 'fuga'})setHoge('fuga')みたいにできてうれしい
  • useEffectの第二引数が直感的じゃない?

詳しく

Class ComponentがFunction Componentにできる & setStateがシンプルに

何かすごいメリットがあるかって言われると微妙なんですが、単純にネストと行数が削減されるのでよいとおもいます

単純なカウンターの例を書いてみると

class HelloComponent extends React.Component<{}, {count: number}> {
    state = {
        count: 0;
    };

    render() {
        return (
            <button onClick={() => this.setState({count: this.state.count + 1})}>{this.state.count}</button>
        );
    }
}

function HelloComponent() {
    const [ count, setCount ] = useState(0);
    return (
        <button onClick={() => setCount(count + 1)}>{count}</button>
    );
}

になります

useEffectの第二引数

componentDidMountajaxしたりcomponentDidUpdateでrefからdomを操作したい場合はuseEffectの第一引数にコールバック関数渡してその中で処理するんですが、asyncなコールバックが渡せなかったり何も考えずにasyncな関数呼び出してその中でsetStateしてると無限ループしちゃうのでちゃんとドキュメント読みましょう

メッセージ一覧を取得して表示する画面で最初書いてたコード(サンプル用に改変済み

import React, { useState, useEffect } from 'react';
import { Message } from '../entities';
import { MessageRepository } from '../repositories';

export default function Messenger() {
    const [ messages, setMessages ] = useState(new Array<Message>());

    const fetchMessages = async () => {
        setMessages(await MessageRepository.getAll());
    };

    useEffect(() => {
        fetchMessages();
    });

    return (
        <ul>
            {messages.map(m => <li key={m.id}>{m.text}</li>)}
        </ul>
    );
}

useEffectはstateやpropsが変更される度に呼び出されるので、この例ではsetMessagesを呼び出すたびにfetchMessagesが呼び出されて無限ループしてしまいますが、 useEffectの第二引数を使うことによって特定の値が変更された場合のみコールバックを実行するように制御できます

なので、ここに空の配列を渡してやることで初回だけ実行されるコールバックが実現できます

useEffect(() => {
    fetchMessages();
}, []);

これで初回だけ実行されるので、componentDidMount相当になります

最後に

ハマりポイント何個か書こうと思ったんですがほとんどハマらなかったので書くことがありませんでした

現状複雑な状態や副作用を持ってるコンポーネントだと大幅な改修が必要になっちゃうんですが基本的にコード量は減るので、新しいコンポーネント書くときには積極的に使うといいんじゃないでしょうか

RITでは新規事業に携わりたい方を様々な職種で募集しているので、ご興味ある方はぜひオフィスに遊びに来てください!

https://rit-inc.co.jp

昨年の振り返りと今年の目標

新年明けましておめでとうございます。CTOの福田です。

年始なので昨年の振り返りと今年の目標でも書いてみようと思います。

昨年の振り返り

マッチングサービス多いですね。 この他にも以前受託したプロダクトの改修案件や仕込み中の案件もあり、エンジニア3人(うち一人デザイナー兼務)で割とスピード感持って開発できたのではないでしょうか。

よかったこと

  • スピード感持って開発できた
    • だいたい1サービス2~3ヶ月でリリースできた
    • VIRECが最初で、そのマッチングサービスのノウハウで他サービスの基盤構築を短縮できた
  • 技術レベルの底上げができた
    • ほぼ新卒で未経験のエンジニアを採用したけど半年程度である程度RailsとReactが触れるようになってる(インフラはまだちょっと怪しい)

改善したいこと

  • マッチングサービス多すぎ
    • 中間マージンを排除するという世の流れもあるけどマッチングサービス以外も作りたい
  • 技術の幅を広げたい
    • 基本的にRails + React + AWS(Beanstalk)で固定されてしまっているので、どんどん新しい言語とかフレームワーク試してさらなる高速化に繋げたい

今年の目標

社内に蓄積されたナレッジで、サービス立ち上げを通じてチャレンジしやすい環境が整ってきたので、 様々なサービス立ち上げに対応できるような組織づくりを推進します。 具体的な目標は以下の通り

  • エンジニアとデザイナー増員
    • 技術の幅を広げて開発するサービスの幅も広げるために増員したい
    • 少なくとも今年2~3人は増やしたい
  • 開発部隊の年収15%以上アップ
    • 教育にちゃんとコストかけて外の世界でも魅力的な人材になってもらうとともに、それに見合った給与を支払えるように売り上げも作る
  • 6サービス以上リリース
    • 開発ラインを増やして前年比1.5倍で
  • イケてる技術をプロダクトに導入したい
    • GraphQL(AWS AppSync)とかFlutter興味あります

最後に

RITでは新しいサービスや技術が好きで、新サービスの立ち上げを通して成長したい方を募集しています。

まずはオフィスまで遊びに来てみませんか?

ActionMailerでdeliver_laterしてるテストが頻繁に止まるようになった話

内容は薄いですが結構ハマったのでメモ

結論

非同期処理内でDBからデータ取ってくるときはassert_enqueued_jobsとかassert_enqueued_emailsで囲んどこう

経緯

弊社のプロダクトではRuby on Railsを利用していてMinitestでテストを書いているんですが、最近になって頻繁にテストが失敗するようになりました。 成功する時もあれば失敗する時もあり、docker-composeでコンテナ立ち上げすぎて色々足りてないのかなーぐらいに思ってたのですが、大体止まったタイミイングのログを見るとActionMailer周りの処理だったので、非同期処理が何か悪さしてるのかと思って調査をはじめました。

予想

最近入れたactive_elastic_jobが悪いんじゃないか

最近非同期処理に影響を与えそうな改修をしたのはこれだったので一番怪しんでたのですが冤罪でした

そもそもtestのqueue_adapterにactive_elastic_job使ってなかったので関係ない

testのRollback先に走ってjobのdeserialize失敗してるんじゃないか

基本的にはログ出さずに止まってるだけだったのですが、たまにdeserializeでエラーログ出ることがあったので怪しいと思い調査 queueを処理してからテスト終わるようにしたらうまくいくんじゃないかと思ったので↓みたいにassert_enqueued_emailsで囲んでみたところ

class SampleTest < ActiveSupport::TestCase
  include ActionMailer::TestHelper

  test 'sample test' do
    assert_enqueued_emails 1 do
      Sample.test_method
    end
  end
end

ActiomMailer起因で失敗することがなくなりました

絶対失敗するわけじゃなかったり最近失敗が増えてきたのはコンテナの立てすぎでテストの処理が重くなってJobの処理する前にRollbackしちゃうケースが増えちゃったって感じなんですかね

JS/TSで特定ディレクトリ以下のモジュールをまとめてexport/importしたい

CTOの福田です

JSって書いてますがTSの話しかしませんごめんなさい

app.ts
├ modules
│  ├ a.ts
│  └ b.ts
└ ...

こんな感じのファイル構成があったときに、app.tsでmodules/aとmodules/bを使おうとすると

// modules/a.ts
export default class A {
}

// modules/b.ts
export default class B {
}


// app.ts
import A from './modules/a';
import B from './modules/b';

class App {
}

AとBを別々にimportする必要がある 2つぐらいだと問題ないけど増えてくると面倒

// app.ts
import { A, B } from './modules';

って書けると嬉しい

とりあえず一つのファイルでimportしてそこからexportしてみる

// modules/modules.ts
import A from './a';
import B from './b';

export { A, B };

// app.ts
import { A, B } from './modules/modules';

これだとmodules/modulesがダサい

index.(js|ts)ってファイルを置いておくと、親ディレクトリがimportされたときにそのファイルがimportされる(なんの仕様かはちゃんと調べてない)

// modules/index.ts
import A from './a';
import B from './b';

export { A, B };

// app.ts
import { A, B } from './modules';

index.tsでimportするモジュールをいちいち書かなきゃいけないのが面倒

// modules/a.ts
export class A {
}

// modules/b.ts
export class B {
}

// modules/index.ts
export * from './a';
export * from './b';

export * from 'xxx'でそのモジュールでexportしてるモジュールを全てexportできる ただ、default exportしてると動いてくれないのでexportにしてる

僕がやれたのはここまでなので、誰かJSとかTSのすごい人がいたら

import { A, B } from './modules';

import A from './modules/a';

ができてindex.tsを書くのが面倒じゃない良い方法あれば教えてください!

ActiveStorageでattachできるものについて調べてみた

福田です

ActiveStorage便利ですよね

railsのmodelにファイルを簡単に紐づけることができるんですが、form postされたファイルだけじゃなくてdirect uploadで発行された謎トークンとかもattachできて混乱しちゃったので、結局何がattachできるのかコード読んで調べてみました

↓はhas_oneなattachのコードです

# Associates a given attachment with the current record, saving it to the database.
#
#   person.avatar.attach(params[:avatar]) # ActionDispatch::Http::UploadedFile object
#   person.avatar.attach(params[:signed_blob_id]) # Signed reference to blob from direct upload
#   person.avatar.attach(io: File.open("/path/to/face.jpg"), filename: "face.jpg", content_type: "image/jpg")
#   person.avatar.attach(avatar_blob) # ActiveStorage::Blob object
def attach(attachable)
    blob_was = blob if attached?
    blob = create_blob_from(attachable)

    unless blob == blob_was
        transaction do
            detach
            write_attachment build_attachment(blob: blob)
        end
        blob_was.purge_later if blob_was && dependent == :purge_later
    end
end

どうやらattachは四種類の引数を受け取るようです

  • ActionDispatch::Http::UploadedFile
    • form postされたfile
  • signed_blob_id
    • ファイルそのものではなくてdirect uploadでアップロードされたファイルを表すID?
  • io
  • ActiveStorage::Blob
    • active_storage_blobsかな?
    • すでにattachされてるファイルを別のmodelに紐づけたりできそう

IOクラスのインスタンスを直接attachできるのは初めて知ったので試してみましょう

User model作って

class User < ApplicationRecord
    has_one_attached :avatar
end

適当に置いといたpngをattachしてみる

irb(main):012:0> f = File.open(Rails.root.join('storage', 'sample_avatar.png').to_s)
=> #<File:/app/storage/sample_avatar.png>
irb(main):013:0> User.first.avatar.attach(io: f, filename: 'sample_avatar.png', content_type: 'image/png')

urlを取得してみる

irb(main):023:0* include Rails.application.routes.url_helpers
=> Object
irb(main):024:0> default_url_options[:host] = "localhost:3000"
=> "localhost:3000"
irb(main):025:0> url_for User.first.avatar
  User Load (0.3ms)  SELECT  `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 1
  ActiveStorage::Attachment Load (1.9ms)  SELECT  `active_storage_attachments`.* FROM `active_storage_attachments` WHERE `active_storage_attachments`.`record_id` = 1 AND `active_storage_attachments`.`record_type` = 'User' AND `active_storage_attachments`.`name` = 'avatar' LIMIT 1
  ActiveStorage::Blob Load (0.4ms)  SELECT  `active_storage_blobs`.* FROM `active_storage_blobs` WHERE `active_storage_blobs`.`id` = 1 LIMIT 1
=> "http://localhost:3000/rails/active_storage/blobs/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBCZz09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--c0c210c1622d0f9c9d208352011ee63a9aa8d5f1/sample_avatar.png"

ちゃんとattachできてる

IOのインスタンスってことはopen-uriとか使ってweb上の画像とかも直接attachできるので、SNS連携で登録された時のデフォルトアバターとか設定するのに使えそうですね

tech blogはじめました & VIRECの裏側

初めまして、RIT CTOの福田(@gendaihyousyou)です。

RITにより多くの人から興味を持ってもらうために、エンジニアやデザイナーから知見を発信するtech blogを開設しました。

WEB系の技術を取り扱うことが多くなると思いますが、たまにコンサル系の記事も混じってくるかもしれません。

今回は初回ということでRITの紹介少しと現在開発中のサービスの裏側について書きます。

RITについて

会社の概要はホームページを見てもらうのが早いですが、実際にどんな仕事をしてるのかが想像しづらいかもしれないので補足してみます。

人材と仕事内容

2018年3月現在の社員数は8名で、大半がコンサルタントとして社外に常駐しています。

このブログを見てる方はおそらくエンジニアのはずで、コンサルタントの業務についてよく知らない方が多いと思う(自分が知らなかっただけかも)ので簡単に説明しておくと、

  • 何か課題を抱えている企業がコンサルタントを雇って課題の解決をお願いする
  • コンサルタントが関係各所にヒアリングを行って課題を洗い出す
  • 自身の持っている知見を駆使して解決方法を提案する

って事をやってるみたいです。知見というのは具体的には"電話での問い合わせに問題を抱えていればAmazon Connectで解決した事例がある"みたいなのですね。

なのでコンサルタントは領域によってはエンジニアよりもAWSやAzureのサービスに詳しかったりします。

エンジニアとデザイナーは基本的に自社のオフィスに常駐していて、コンサルタントが提案したソリューションを受託開発したり、社内で提案された新サービスを開発しています。

出社していたほうがコミュニケーション取りやすいので基本的には出社してますが、体調不良や家で荷物受け取らなきゃいけないみたいな時はリモートでも作業をしてます。

技術スタック

  • インフラ
  • サーバサイド
  • フロントエンド
    • TypeScript, Webpack, React(no Redux), Bootstrap, Sass
  • その他利用ツール

がメインですが、高速に開発する事を第一としているので、より良い選択肢があれば常にそれを試せるようしています。

現在開発中のサービスの裏側

現在RITではVIRECという、フリーのコンサルタントとクライアント企業をマッチングするサービスを開発しています。

普通のWEBアプリケーションなのですが、開発の特徴としては

  • Elastic Beanstalkにもろもろお任せ
  • だいたい使える限り最新のバージョンを使う
    • MySQL 5.7
    • Ruby 2.4
      • Beanstalkのプラットフォームで2.5系がまだ選べなかったので仕方なく
    • Rails 5.2
      • ユーザに紐づいた動画とか扱うのにActiveStorageをすごく使いたかったのでbeta版から使ってる
    • TypeScript 2.7
    • React 16.2
    • Webpack 4.0
  • 開発環境はdocker-composeにお任せ
  • DDD, TDDを一部採用

みたいな感じです。

バージョンに関してはこれで固定ではなく、可能な限りstable版が出ると同時ぐらいのタイミングで各々入れ替えていく事を目標にしています。

また、利用している技術スタックに関しても同様で、より良い方法があればフットワーク軽めに色々試してみる予定です。

最後に

VIRECの新機能開発やその他新サービスの開発など、開発するものは色々あるのにエンジニアの手が足りない状況です。

もしRITで働く事に興味があれば、ぜひ一度オフィスに遊びに来てください!

会社の問い合わせ用のアドレスにメール送ってもらうか僕のアカウントに連絡してもらえれば対応します!