入門!RxSwift

こんにちは、オープンエイトでiOSアプリエンジニアをしている常盤です。 通勤時間に小説を読むようにしたらQOLが爆上がりしました。

さて、私は約半年前にオープンエイトにジョインし、その時初めてSwiftに触れました。 弊社のサービスである女性向けおでかけ動画メディア、Letronc。iOSアプリではRxSwiftが使われています。 初めて触るSwiftという言語に加え、Rxという概念を理解するまでは艱難辛苦の日々でした。。。 そこでRx初心者に向けて、どうやって概念を理解してコードを書けるようになったかポイントを伝授しようと思います。

Rx is 何

f:id:open8tech:20190328180006p:plain

Rx(Reactive X)とは、「オブザーバパターン」「イテレータパターン」「関数型プログラミング」の概念を実装している拡張ライブラリです。

Rxを導入するメリットは、「値の変化を検知できる」「非同期の処理を簡潔に書ける」ということに尽きると思います。 値の変化というのは変数値の変化やUIの変化も含まれます。 例えばボタンをタッチする、という動作もボタンのステータスが変わったと捉えることができRxを使って記述することができます。

Rxの考え方

私がRxで躓いたポイントは3点あると考えています。

1.Rx特有のキーワードがそれぞれどのような働きをしているか理解できていない
2.メソッドチェーンのような書き方で値の流れが理解できていない
3.値の変化に応じて発火するイベントが理解できていない

...そう...何も理解できていないからである!
しかし理解できていない対象が分かっていれば対処は可能です。

1.に関しては基本のキーワードを押さえていき、 2.3.は実装例から紐解いて行こうと思います。

基本のキーワード

SubjectとRelay

すごく簡単に言うと、イベントの検知に加えイベントの発生もできる便利なラッパークラスです。 代表的としてよく使われる4種類は以下です。
・PublishSubject
・BehaviorSubject
・PublishRelay
・BehaviorRelay
それぞれ特徴が異なるのですが割愛します。本記事のコードではBehaviorRelayを使用します。

ObservableとObserver

Observableは監視可能なものを表すクラスで通知を行うことができます。 変数xがObservableでラップされているならば、変数xの値が変化する毎にObserverへ通知されます。 Rxではストリーム(stream)とも表現されています。

Observerは監視を行う側です。ObserverObservableから通知されるあらゆる値に反応することができます。

f:id:open8tech:20190327113910p:plain

subscribe()

subscribe()はメソッドで、監視する側の働きを持ちます。 Observableから通知を受け取ったらどのような振る舞いをするかを定義します。これをRxでは購読と表現します。 引数であるonNextがとるクロージャー内でやりたい処理を記述します。

DisposeBag

一度購読をし始めるといつまで購読し続けるのか?メモリに残ったままにならないのか?そんな疑問が湧くかと思います。 DisposeBagは購読の破棄をうまいことやってくれてメモリリークを回避するためのクラスです。 イメージとしては、購読を定義した際に返ってくる破棄可能なインスタンスをDisposeBagインスタンスに入れることで、そのDisposeBagインスタンス自身が破棄されると同時に購読も破棄してくれます。 購読はビューが破棄(deinit)されるタイミングで一緒に破棄したいことが多いので、ビュー、またはビューコントローラーのインスタンス変数として定義することが多いです。

実際に使ってみた

最低限の実装でも良いのでコードを書いてその働きを追うことが理解への第一歩かと思います。 実装例として数字カウントアプリを考えてみましょう。
ボタンを押すと、ラベルに表示される数字が+1されるというアプリです。これをRxを使って実装していきます。 ここでRxが適用できる処理はどの部分でしょうか?以下の2つの処理はRxを用いて記述することができます。

1.カウントボタンを押したら、変数の値を変更する
2.変数の値が変化したら、変化後の値をビューに表示する

iOSアプリでよく見る形のMVVMアーキテクチャに当てはめると、実装のイメージは下記図の通りです。

f:id:open8tech:20190327113912p:plain

それではコードに落とし込んでいきましょう。 今回MVVMアーキテクチャは主題から外れる、かつコードは非常にシンプルなので、処理はすべてビューに集めています。

// SomeViewController.swift
import UIKit
import RxSwift
import RxCocoa

class SomeViewController: UIViewController {

    @IBOutlet weak var button: UIButton!
    @IBOutlet weak var label: UILabel!

    private let count: BehaviorRelay<Int> = BehaviorRelay(value: 0)
    private let disposeBag: DisposeBag = DisposeBag()

    override func viewDidLoad() {
        super.viewDidLoad()

        bindButtonToValue()
        bindCountToText()
    }

    // 1.の処理
    private func bindButtonToValue() {
        button.rx.tap
            .subscribe(onNext: { [weak self] _ in
                self?.increment()})
            .disposed(by: disposeBag)
    }

    private func increment() {
        count.accept(count.value + 1)
    }

    // 2.の処理
    private func bindCountToText() {
        count.asObservable()
            .subscribe(onNext: { [weak self] count in
                self?.label.text = String(count) })
            .disposed(by: disposeBag)
    }
}

実際の動きは以下のようになります。これ以上ないくらいシンプルです。

f:id:open8tech:20190327192116g:plain

上の実装例からRxの書き方を紐解いていきます。

メソッドチェーンのような直感的な書き方

Rxはメソッドチェーンのように、メソッドの結果に対して更にメソッドを呼ぶことができます。 この形式はRx特有の特徴ではなく関数型プログラミングではよく見られます。 意識して頂きたいのは各メソッドの返り値です。返り値の型によってチェーンできるメソッドが異なります。

bindCountToText()を見てみましょう。これは典型的な変数購読の書き方です。 返り値をコメントアウトで表してみます。

    count.asObservable()  // Observable<Int>
        .subscribe(onNext: { [weak self] count in
            self?.label.text = String(count) }) // Disposable
        .disposed(by: disposeBag) // 返り値なし

asObservable()メソッドは、返り値としてObservableを得ることができます。 基礎キーワードで出てきたObservable、繰り返しになりますがこれは監視可能なクラスです。つまり変数countの値の変化を監視している状態になります。 監視しているだけじゃ何もおきないので、購読をします。値が変化したらやりたい処理をsubscribe()メソッドで定義します。ここでやりたいのはラベルに変化した値をいれることです。
subscribeメソッドの返り値はDisposableインスタンス。なにか見覚えがありますね?ここで、基礎キーワードに出てきたDisposeBagによる購読の破棄を行います。 disposed(by:)メソッドの引数にインスタンス変数として定義したdisposeBagを与えることで、SomeViewControllerが破棄(deinit)されるとdisposeBag自身も含め購読も破棄してくれます。disposed(by:)メソッドを呼び出したらすぐに購読が破棄されることはありません。

値の変化でイベントが発生

画面上ではボタンを押すと表示される数字がカウントアップするシンプルなものなのでRxを使う旨味がないように思われるかもしれません。 しかし実際のアプリではアーキテクチャに当てはめたりと複雑性は増します。 上記の実装1、2のように分けて考えることができれば、値の変化を検知しイベントが発生することが捉えやすくなるかと思います。 そのイベントに応じて、やりたい処理を簡単に記述できるのがRxの便利なところです。 特に非同期処理(例えば、ネットワークを介したAPI処理)において、従来ではコールバックとして定義していた箇所がRxを適用することによりコールバック地獄から解放され、スッキリとしたコードに収めることができます。

1ステップ先へ

bindCountToText()は以下のようにも書き換えることができます。

    count.asObservable()
        .map{ String($0) }
        .bind(to: label.rx.text)
        .disposed(by: disposeBag)

今回取り上げてはいませんが、mapというオペレーターを使ってObservableから流れてきた値を加工しています。 そして加工した値をbind(to:)メソッドでラベルのテキストに直接バインドしています。
このようにRxではObservableとViewを直接つないだりすることもできます。(Swift標準のUIKitをRxで扱うにはRxCocoaというライブラリが必要です。そのためRxSwiftと一緒に使われることが多い。)

まとめ

今回、RxSwiftについてまとめました。少しでも概念がイメージできたなら嬉しいです。 Rxは学習コストが高いやらパット見わかりにくいと言われておりますが、慣れていくとすごく便利なライブラリだと感じます。 私はもうRx無しでは生きられない身体になりました。 難しく考えずにほんとにシンプルなことから実装してみるのが一番の近道かもしれません。 ぜひ自分のモノにしてRxのパワーを感じて欲しいです。

実践!クリーンアーキテクチャ + RxSwift

こんにちは、最近ほんのり?ふくよかになったオープンエイトの正原です。 ダイエットをしたいとは思ってはいるのですが、 「まだ寒い、まだ慌てるような時間じゃない」と自分に言い聞かせています。 そもそも一度に10kgのみかんが届くことなどないよう、ふるさと納税は計画的にすべきでしたね。

さて、今回のテックブログなのですが、 最近私が設計しているiOSアプリケーションにおけるクリーンアーキテクチャとRxSwiftを導入する際につまづいた点についてお話したいと思います。

クリーンアーキテクチャ

f:id:open8tech:20190310002048j:plain [引用元] : The Clean Code Blog

クリーンアーキテクチャはよく分からなくても「この図は見たことある!」という方も多いのではないでしょうか? 2012年にRobert C. Martin (Uncle Bob)氏がThe Clean Code Blogにて示した図です。 この図では主に「関心事の分離」と「依存性のルール」を表現しているのですが、 この図に関しては前述の本家のブログやその日本語訳版Clean Architecture 達人に学ぶソフトウェアの構造と設計、または多くのテックブログにて説明されているのでここでは割愛します。

Clean Architecture 達人に学ぶソフトウェアの構造と設計はソフトウェア開発における問題に対して、 クリーンアーキテクチャ(またはクリーンアーキテクチャで使われるテクニック)でどのように解消するかが分かりやすく書いてあるので、 一度は読んでおいて損はないと思います!ぜひぜひ!

どんな構成にしたの?

恐らく、多くの方が最初は私と同様に「理屈はなんとなく分かるけど、どのように実装したら良いかが分からない」といった状況になるのではないでしょうか。 私は多くのテックブログやgithub上にあるサンプルアプリケーションを参考にして以下のような構成にしました。

f:id:open8tech:20190310035102p:plain

以降ではこの構成を基に今回のトピックである「どのようにつまづいたり転んだりしたか」をみていきます。

参照と依存の向きによる違和感

クリーンアーキテクチャを勉強したら「実際に少し書いてみよう」となるでしょう。 しかし、実際に書くとなると何か参考になるソースコードが欲しくなると思います。 そんなとき、githubで公開されているサンプルアプリケーションが非常に役立ちます。

ビューコントローラとプレゼンター

では、まずエントリポイントとなるビューコントローラとプレゼンターを見ていきたいと思います。

// SomeViewController.swift
class SomeViewController: UIViewController {

    var presenter: SomePresenter?

    override func viewDidLoad() {
        super.viewDidLoad()
        presenter?.doByPresenter()
    }
}

extension SomeViewController: SomePresenterDelegate {

    func doByDelegate() {
    }
}
// SomePresenter.swift
protocol SomePresenter {

    func doByPresenter()
}

protocol SomePresenterDelegate: AnyObject {

    func doByDelegate()
}

class SomePresenterImpl: SomePresenter {

    weak var delegate: SomePresenterDelegate?

    func doByPresenter() {
    }
}

ビューコントローラおよびプレゼンターはお互いインターフェース(プロトコル)のみに依存して詳細を知らないようにします。 そしてDIコンテナにて以下のように依存性を注入すると思います。

let viewController = SomeViewController()
let presenter = SomePresenterImpl()
viewController.presenter = presenter
presenter.delegate = viewController

ここまではまだ違和感を抱かないで理解できるのではないでしょうか。 ちなみに、同じファイルにプロトコルとその実装を書くことで、 Xcodeでジャンプしても迷子にならずにすみます。 参照元が要求しているのはプロトコルなので、プロトコル定義にジャンプしてしまい、 実装まで直接ジャンプできなくなってしまうんですね。

ゲートウェイとレポジトリ

では、次にゲートウェイリポジトリを見ていきたいと思います。

// SomeGateway.swift
protocol SomeGateway {

    func doByGateway()
}

protocol SomeGatewayDelegate: AnyObject {

    func doByUseCase()
}

class SomeGatewayImpl: SomeGateway {

    private let repository: SomeRepository
    weak var delegate: SomeGatewayDelegate?

    init(repository: SomeRepository) {
        self.repository = repository
    }

    func doByGateway() {
    }
}
// SomeRepository.swift
protocol SomeRepository {

    func doByRepository()
}

protocol SomeRepositoryDelegate: AnyObject {

    func doByGateway()
}

class SomeRepositoryImpl: SomeRepository {

    weak var delegate: SomeRepositoryDelegate?

    func doByRepository() {
    }
}

この辺で「ん?ゲートウェイがレポジトリを持つ形になって良いのか・・・?」となりました。 先程の構成図に依存性の方向を入れた図を見てみたいと思います。

f:id:open8tech:20190311100910p:plain

レポジトリは最も円の外側であるDBやAPIなどの詳細が含まれている領域です。 それは正しいのですが、なぜゲートウェイがレポジトリを持つような形になってしまうのかが違和感でした。 ですが、それもそのはず。iOSでは基本的にビューコントローラが参照元となり、 プレゼンターやユースケースゲートウェイ、レポジトリの順に参照されているからです。 参照元と参照先の関係は以下の図のようになると思います。

f:id:open8tech:20190311100919p:plain

これは恐らく、ビューコントローラが破棄された際、 その画面を構成するインスタンスも同様に破棄されて欲しいためだと思います。 ですが、クリーンアーキテクチャの依存性の方向と参照の方向が異なるため、 このような違和感を抱くことになったんですね。

(もっと上手にやれば参照の方向なども自然な設計になったりするんでしょうか?)

ユースケースとはどのようなもので何を実装するか?

ユースケースとはどのようなものでしょうか? 本家のブログや書籍には「アプリケーション固有のビジネスルール」とありますが、 APIから取得したデータをビューに表示するだけのシンプルなアプリの場合、 恥ずかしながら何がユースケースに相当するのかがイマイチ想像できませんでした。

こんなとき頼りになるのがグーグル検索です。 そこで「ユースケース」と検索しますと、様々なユースケース図が出てきて気づきました。 iOSアプリケーションにおいては純粋にユーザーのアクションで良いのではないか?と。 例えば、皆さんが知ってるTwitterアプリなどの場合、以下のようになるかと思います。

f:id:open8tech:20190311121355p:plain

Twitterアプリケーションでなくとも、多くのアプリケーションにおいて 「お気に入り」や「再読み込み」、「追加読み込み」などの機能があると思います。 これらユーザーのアクションをユースケースとするのがiOSアプリケーションにとって 自然なのではないでしょうか。

(クリーンアーキテクチャの「ユースケース」にとらわれて広義での「ユースケース」がすっぽり抜け落ちてたのは内緒です)

エンティティとRxSwift

皆さんRxSwift使っていますでしょうか? タイトルに入れたくせにこれまで全く触れられてこなかったRxSwiftについてですが、個人的にはとても好きなライブラリです。 さて、クリーンアーキテクチャを用いた実装でRxSwiftをどのように使うかですが、意外とエンティティと相性が良いのではないでしょうか。 (ビュー層はこれまでと同様の使い方をすると思います。また必ずしもエンティティにしなければならないわけではないと思います)

例えば、テーブルビューやコレクションビューにて1つ1つのセルを構成するデータをSomeDataとして、 そのデータの配列を SomeDataList とした場合、以下のような実装になります。

// SomeData.swift
struct SomeData {

    let title: String
    let description: String
}

(以下の SomeDataList ではBehaviorRelayを用いていますのでRxCocoaのインポートが必要です。)

// SomeDataList.swift

import RxSwift
import RxCocoa

class SomeDataList {

    let list: BehaviorRelay<[SomeData]>

    var stream: Observable<[SomeData]> {
        return list.asObservable()
    }

    init(value: [SomeData] = []) {
        list = BehaviorRelay(value: value)
    }

    func set(value: [SomeData]) {
        list.accept(value)
    }

    func add(value: [SomeData]) {
        list.accept(list.value + value)
    }
}

このように Observable で観測可能な状態にしてユースケースからプレゼンターへ、 そしてプレゼンターで Driver にしてからビューコントローラへ渡していくことで ビュー層では観測するだけですむのではないかと思います。 詳細な部分は違うかもしれませんが、目的やイメージとしてはfluxに近いかもしれません。

またエンティティ(SomeDataList)が扱う[SomeData]ですが、これは最初の構成図におけるdataに相当します。 クリーンアーキテクチャでは「境界線を超えるデータは単純なデータ構造で構成されている」とあるので、 structで定義した単純なデータ構造の配列である[SomeData]は境界線を超えても良いのではないかと思いました。

最後に、最初の構成図でエンティティがユースケースをまたがっている部分についてですが、 同一のエンティティを異なるユースケースで使うパターンがあるかもしれない、と思ったからです。 ログイン・ログアウトのような場合はユースケースを再利用したら良いかもしれませんが、 上記のようなデータの再読み込みと追加読み込みなどは、ユーザーのアクションは異なりますが、 扱うエンティティは同じ方が都合が良いと思います。

ただ、データの再読み込みと追加読み込みに関しては、 ゲートウェイやレポジトリも同じ方が都合が良いため、 今のところ「ロード」という1つのユースケースに集約しています。 (もしかしたらそのようなエンティティは存在しない・・・!?)

試してみた感想

まだ直面していないので自信を持って言えないですが、 利点としてはやはり「仕様変更に強そう」ではないでしょうか。 ですが、サービスそのものが開発途上のiOSアプリケーションにおいては、 中心のドメインの仕様変更も少なくないのが少々怖いところです。

逆に欠点ですが、やはり「ファイル数が多い」と「理解が難しい」です。 「理解が難しい」は勉強しろと言われればそれまでかもしれませんが、 チーム開発を行う上では全員に理解してもらわないといけないので 勉強会を行うなどしないといけないと思います。

まとめ

いかがでしたでしょうか? 正直、まだまだ実装している最中なので何か不都合が出てこないか、 そもそも自分のクリーンアーキテクチャの理解が間違っていないか毎日ドキドキしています。 また、今回お見せした例は「こうあるべきだ」とか「こうしないといけない」というものでもないです。 ですが、クリーンアーキテクチャを勉強を通して「こうしたらもっと仕様の変更に強くなりそうだ」というのが 以前より多くなったと思いますので、(プロダクトに導入するかはともかく)一度本家のブログだけでも 読んでみてはいかがでしょうか。

reduxのstate設計についてスタートアップテックで話しました

こんにちは、オープンエイトでフロントエンドエンジニアをしている澤木です。 最近お腹の調子が良くないので朝にヨーグルトを食べるようにしたのですがかなりの改善を感じています。皆さんもヨーグルト食べましょう。

ところで弊社オープンエイトですが、昨年末からスタートアップテックと題してスタートアップ企業合同で様々話題をテーマに LT 大会を開催しております。 今回はその第2回目、スタートアップフロントエンドということで私も LT に参加させていただきました! ということでその時の発表内容についてブログでも紹介させていただければなと思います。

f:id:open8tech:20190214154427j:plain

発表内容

今回は「redux の state 設計の話」と題して、redux の state 設計の話をしてきました。まんまですね。 弊社では多くのプロジェクトで React が使われているのですが、中でも私の担当する VIDEO BRAIN では react+redux の SPA で開発を行っています。 その時に state 設計にかなり苦戦しましたので、どういう経緯で作っていったか、現在の設計はどうなっているか等についてお話しさせていただきます。

redux の state 設計

まず redux の state 設計についてですが、redux では state は原則として一つのツリーとして一箇所で管理されます。(Single source of truth) もちろん一枚フラットな state にするのは辛すぎるので、そこからどうやってツリーを切っていくかが state 設計の肝となります。

画面ベースでの設計

VIDEO BRAIN では最初 state を切る際に画面ごとに切るという方法を取っていました。 例えば編集画面、作成画面などがあった場合に編集画面用の state、作成画面用の state といった用に画面と1:1で state を切っていくようなやり方です。 ざっくり図を作るとこんな感じ。

f:id:open8tech:20190208183137p:plain

このような形で切っていくと画面数が少ないうちは良かったのですが、画面数が多くなってきた時にその数分 state を切らなければならない、画面に変更があった場合に state を作り直さなければならない、といった問題が出てきました。(特に VIDEO BRAIN では画面の変更が多かったためかなり辛かったです…) そこで、画面ベースでの切り方はやめて、ドメインベースで state を切っていくやり方に移行することにしました。

ドメインベースでの設計

こちらのやり方では state を画面から切り離して、user だったり projects だったりドメインで state を切っていく形になります。 ざっくり図を作るとこんな感じ。

f:id:open8tech:20190208183131p:plain

このような設計にすると、画面と state を分離することができるため、画面側に変更があった際にも state を作り直す必要がなく、ロジックの共通化も行いやすくなります。 実際には下のような形で state を切っていきました。

state
  ├─ entities // APIからフェッチしてきたデータを正規化して保持
  ├── 各ドメインのstate
  │   ├── lists // 各ドメインのリストデータを保持
  │   └── edits // 各ドメインの編集データを保持
  └── ui // グローバルなUIの表示状態を保持

entities

こちらの state では API から取得したデータを一括で保持するようにしています。 API から取得したデータの構造は呼び出し場所ごとに異なっており、また各ドメインの情報がネストして含まれていたりしています。 そういった場合にそれらを違う state で保持しているとデータをフェッチしてきた際に、各 state ごとに更新作業を行わなければならず操作が複雑になってしまいます。またデータがネストしている場合オブジェクトをたどっての変更が難しいという問題も出てきます。 データを正規化することでデータの構造がフラットになり、各ドメインごとのデータを同じ形式でまとめることができるようになります。

例えば projects とそれにひもづく scnens をもつオブジェクトをフェッチしてきたとします。この場合にデータの正規化を行うと下のようになり、projects と scenes をフラットな状態にすることができます。

正規化前のオブジェクト

{
  id: 1,
  title: "project1",
  scenes: [
    {
      id: 1,
      name: "scene1",
    },
    {
      id: 2,
      name: "scene2"
    }
  ]
}

正規化後のオブジェクト

entities: {
  projects: {
    1: {
      id: 1,
      title: "project1",
      scenes: [1, 2]
    }
  },
  scenes: {
    1: {
      id: 1,
      name: "scene1"
  },
    2: {
      id: 2,
      name: "scene2"
    }
  }
}

ドメインの state

こちらの state では各ドメインのデータを保持しています。この中でまた state をネストさせて一覧や編集状態の管理用の state を作成しています。 各 state では API からのフェッチしてきたデータは直接持たず、entities のデータを参照するようにしています。こうすることで、entities を変更すれば全ての場所で自動的に表示が更新されるようになり更新作業が楽になりました。 lists と edits の構造は下のような形にしました。

lists

lists: {
  [key]: {
    ids: [1, 2, 3],
    loading: false
  }
}

edits

edits: {
  [id]: {
    name: hugahuga,
    ...
  }
}

ui

こちらの state ではモーダルなどグローバルな UI データを保持しています。各ビューごとの UI の状態はできるだけコンポーネント側で管理するようにして、redux 側での UI の状態保持は最小限にとどめています。

まとめ

画面ベースでの state 設計からドメインベースでの state 設計に切り替えたことで、ビューに変更があった際も state を書き変えずに最小限の変更で対応できるようになり、ある程度苦労を減らせたと思います笑 ただビュー側に固有の処理をどうするかなど、画面ごとの設計では気にすることのなかった問題も出てきたので、そこらへんに関しても綺麗な書き方を模索していければなと思います。(redux 難しい…)

styled-componentsのスタイル拡張について

2018年度に新卒で入社した東度です。配属当初はRailsでサーバーサイドを書いていたのですが、最近はReactでフロントを書いています。

自社プロダクトの一つであるVIDEO BRAINでは、CSS in JSのライブラリの一つであるstyled-componentsを使ってスタイリングをしているのですが、中でも度々登場するスタイル拡張について自身の勉強も兼ねて記事をまとめてみました。

styled-componentsとは

現在、CSS in JSの中で最も人気のあるライブラリで、要素にスタイルを付加したコンポーネントが作成できるというのが特徴です。

styled.div``のように記述すると、div要素にタグ付きテンプテートリテラル内のスタイルを付加したコンポーネントが返される仕組みとなっています。

以下の例では、button要素にスタイルを当てたButtonコンポーネントを定義しています。 定義したButtonコンポーネントを呼び出すと以下のボタンが出力されます。

// Button.js
import styled from 'styled-components';

export const Button = styled.button`
  margin: 1em;
  padding: 0.25em 1em;
  border: none;
  outline: none;
  border-radius: 3px;
  color: #fff;
  background-color: #404143;
`;

Image from Gyazo

スタイル拡張について

styledされたコンポーネントに対してstyled()コンストラクタでラップすると、スタイルを拡張した新しいコンポーネントを作成することができます。

例えばButtonコンポーネントの場合、色やサイズなどを調整して新しいコンポーネントを作成したい場合などがあるかと思います。以下の例ではButtonコンポーネントを継承し、スタイルを拡張した新しいボタン作成しています。

// Button.js
import styled from 'styled-components';

export const Button = styled.button`
  margin: 1em;
  padding: 0.25em 1em;
  border: none;
  outline: none;
  border-radius: 3px;
  color: #fff;
  background-color: #404143;
`;

export const RedButton = styled(Button)`
  background-color: #ff0000;
`;

export const GreenButton = styled(Button)`
  background-color: #00ff00;
`

export const BlueButton = styled(Button)`
  background-color: #0000ff;
`

Image from Gyazo

次に、styledされていないコンポーネントに対してスタイルを拡張する方法を紹介します。

styled-componentsでは、classNameに指定したスタイルを渡すことでスタイルを適用しています。 styledされていないコンポーネントに対しては、以下のように、propsで渡されたclassNameをコンポーネントのDOM要素に適用することで、スタイル拡張が行えるようになります。

// Hello.js
import React from 'react';
import styled from 'styled-components';

export function Hello({className, children}) {
  return(
    <a className={className}>
      {children}
    </a>
  );
}
export const StyledHello = styled(Hello)`
  color: #7F9DB5;
  font-size: 30px;
  font-weight: bold;
`; 
// App.js
import React, { Component } from 'react';
import { Hello, StyledHello } from './Hello.js'

class App extends Component {
  render() {
    return (
      <div>
        <Hello>Hello</Hello>
        <br />
        <StyledHello>Hello</StyledHello>
      </div>
    );
  }
}

export default App;

Image from Gyazo

まとめ

今回はstyled-componentsを使ったスタイル拡張について記事をまとめました。今後React等に触れていく中で、得られた知見などを共有できたらと思います。

最後に、告知になるのですが1月29日(火)に「スタートアップテック vol.2:スタートアップとフロントエンド」が開催されます。 弊社からも澤木(@ayatas0623)が「reduxのstate設計の話」について発表します。彼の勇姿を是非ご観覧ください!

イベントの詳細については下記のリンクからご確認ください。

connpass.com

「スタートアップテック vol.1」を開催しました!

https://connpass-tokyo.s3.amazonaws.com/thumbs/87/36/87360719e01f4b0d089645f47e2f7d45.png

こんにちは、インフラ・データ基盤部の川島です。最近寒くなってきましたね。今年も終わりますね。心はすでに年末休みです。

先日、エンジニア交流イベント「スタートアップテック vol.1」を開催しました。

今回のテーマは、みんなが大好きな「Ruby」です。弊社のプロダクトもほとんど全てと言ってもいいくらいRubyを使っています。 主催枠として、私もLTを行ったので、発表内容を紹介できればと思います。

発表内容

LTのテーマは、「Rubyffmpegのfilter_complex」について発表しました。オープンエイトのプロダクト全てが動画をベースに展開しているため、Rubyと同じく、全てのプロダクトでffmpegを使っています。最近のプロジェクトの中でのffmpegを活用した実装事例について紹介しました。

ffmpeg は動画や音声に対して、非常に多くのことができる優秀なツールです。基本的にコマンドラインで操作を行うため、多くのオプションを設定しなければいけません。

下記のコマンドが実際に使用したものになります。

ffmpeg -y -r 15 \
-i ./image/%d.jpg -i 01.mp3 -i 02.mp3 \ 
-filter_complex "[0:v]fps=fps=15[img];[1:a]atrim=duration=20.0,afade=type=out:start_time=19.0:duration=1.0,asetpts=PTS-STARTPTS[OA1];
[OA1]amerge=inputs=1[a]" \
-map [img] -map [a] -s 640x360 -crf 30 -movflags faststart -vcodec libx264 -pix_fmt yuv420p -q:a 0 -strict -2 \
output.mp4

プロジェクトの中では、このコマンドを Ruby で生成しています。 LTの中では、複数あるオプションの中でも、 filter_complex 部分に焦点を絞って紹介しました。

filter_complexは大きく3つの構成に分類することができます。

  • filter
  • filter_chain
  • filter_graph
fps=fps=15 # filter
[0:v]fps=fps=15[img] # filter_chain
"[0:v]fps=fps=15[img];...." # filter_grpah

filter_graph[filter_chian[filter]] という形でそれぞれが内包されているイメージです。

コードで実装する場合にも、この構成を元にクラス定義していきます。

filter class
filter_chain class ( N filters )
filter_graph class ( N filter_chains ( N filters ))

対象のディレクトリの構成としては下記になります。

- filter_complex  
│
└──-filter
│   │   xx_filter.rb 
│   
└───filter_chain
│   │   xx_chain.rb
│   
└───filter_graph
    │   xx_graph.rb

名前空間、filter毎のクラスで分けることによって、それぞれのfilterの責務を明確にすることができます。また、追加したいfilterがあったとしても、filter, filter_chain, filter_graph をセットで追加することで、容易に作成ができます。

まとめ

ffmpegは、本当にたくさんのことができるツールです。この記事でffmpeg、特にfilter_complex に興味を持っていただけるとうれしいです。

次回のイベント告知

1/29(火)にも、弊社主催のLT大会を行なっていきます。次回のテーマは「フロント」です。 また、「一緒に勉強会を行いたい!」などのご要望がありましたら、ぜひご連絡(@hisatake)をお待ちしております!

おまけ

コードなどの内容は下記のスライドに掲載してあります。

www.slideshare.net

技術書典にサークル参加してきました

こんにちは。初めまして。 OPEN8の大津( @14__oz )です。

技術ブログを始めることになりました!! 編集長に抜擢され、初回の内容を任されています。 頑張っていきますのでよろしくお願いします!

OPEN8には2017年新卒エンジニアとして入社し、ルトロンのアンドロイドアプリを初期開発からやってきました。 ReactNativeを使っています。

詳しくは、Wantedlyインタビュー記事があるので読んでみてください。 www.wantedly.com

早速、初回のテーマですが、「技術書典5」に出展したときの話をします! 新機能の開発が忙しいなか、時間を作って書き上げた技術書を頒布してきました。 内容は、ReactNativeのアニメーションについて書いています。 今更ですが、振り返っていきます(笑)。

技術書典

技術書典とは、技術書オンリーイベントです。 技術者のためのコミケをイメージしてもらえると良さそうです。 出展者が普段から使っている・興味のある技術のノウハウをギュッと技術書に詰め込んでいて、とても読み応えがあります。 かなり幅の広いジャンルが揃っていて、内容も初学者に向けたものから、ニッチなものまで様々です。 どの技術書も販売されているものに劣らない内容となっています。

もともと興味があったなか、CTOの石橋( @hisatake )からの後押しでチャレンジしてみることになり、 10/8(月)に池袋サンシャインシティにて開催された技術書典5に出展してきました。

当日は、およそ10,000人も来場されたそうです! 前回の来場者数が、およそ6,000人で、技術書典の規模がどんどん拡大していますね。 今後も楽しみです!

初めてのサークル参加

技術書典5に無事当選はしたものの、当時は、内容さえ決まって書き始めれば何とかなるだろうという見切り発進をしていました。

  • 即売会にサークル参加する
  • 技術書を書く
  • 書籍を作る

執筆や書籍を作ろうとした事もないのに、余裕をこいていたのはどうしてなんでしょう・・・。

後々に大変痛い目をみる羽目に・・・。

技術書の内容

頒布した技術書の内容は、

  • 業務ではAnimatedを使う機会がほぼなかった事
  • 新卒当時は難しくて断念していたアニメーションにリベンジしたかった
  • 実際に動かして、理解したかった

と言った理由から、ReactNativeのAnimatedクラス・コンポーネントをテーマとして選んでみました。

技術書を書くにあたり、ReactNativeでパズルアプリを作りました。 アプリ内でAnimatedを用いた部分のコードを載せて解説しています。 Animatedとスクロールの連携やinterpolateを用いた例を書いています。

Easingの使い方や、PanResponderを用いたフリックの取得も書いています。 知らないことが多かったので、ReactNative本体のソースコードを読んで理解しことや、 AnimationLayoutなど、一通りアニメーションに必要なものを含んだ内容になっています。

書籍の最後には、およそ1年ReactNativeを使ってきて、印象的な出来事を中心にトピックとして残しておくことにしました。

当日の様子

開場1時間前には、列ができていました。 この時点で早くも自分のテンションが上がり始めます。

緑色の服を来た運営の方たちに出迎えてもらい、自分のブースへと向かいます。 ブースに作った技術書が直接届いていたので荷物整理をしていました。

一息ついてからの、開封の儀。

そして、ブースの設営をしました。 必要そうなものの大半は前日・前々日に100均に駆け込んで買い揃えました。 書籍の内容がReactNativeだったのでFacebookの服を着て待機。

始まるまでに時間が少しばかり余ったので、顔見知り方のブースに挨拶に行ったり、 となりのブースの方と話していました。 なんだかんだ、お隣の方とは1日中話していました。

始まってすぐに3名の方がブースに来てくれたのは、かなり嬉しかったです。 そのあとは、入場規制の関係で人の波が一定間隔で押し寄せるといった感じでした。 ブースに来て見本誌を読んでくれる方がたくさんいて、読まれているときの緊張感は計り知れないです。 じっと観察されているような気分でした。 見本誌を見て買っていただけた時は嬉しくて顔に出ていたかもしれないですね。 同僚や知り合いなど、たくさんの方にブースへ来てもらえたのでありがたかったです。

最終的には、50部ほど頒布できました。 ただ、今だから思うと、調子に乗って100部刷ったのは完全にミスですね。 余ってしまったので、今でも家に眠っています。 また、機会があれば頒布しようと思います。

失敗したこと

失敗したことはたくさんあって、

  • 部数を刷りすぎた
  • 表紙・裏表紙の入稿データの形式が違っていた
  • PDFの用紙サイズが間違っていた
  • 書く時間をほとんど取れなかった

などなど、出し始めるとキリがなさそうです(笑)。

見切り発進した結果、ギリギリまで入稿方法について調べずにいました。 いざ、入稿するぞってときにノンブルがない、PDFの用紙サイズが違うなど、かなり焦りました。 表紙は塗り足しがなくて作り直しと、PDF同様にダメな点がありました。 事前確認を怠り、行き当たりばったりで良くなかったですが、ノウハウを得たので次は活かせられそうです。

一番キツかったのは、書く時間を取れなかったことです。 時間があまりにもなくて最後までずっと終わらないのではないかと内心思い、 まるで、締め切りに迫られた作家のようでした。 また、時間を置いてから内容を読み直して修正する時間もほとんど取れず、 完成度が下がってしまったのは悔やまれます。

技術書典を終えて

総合的にはかなり満足しています。 執筆に当たっては失敗しことも多いですが、やって良かったと思えたからですね。

実際に技術書を出そうとすると、 かなりのインプットがいることが身に染みました。 今回の執筆では体感で2倍くらいのインプットが必要でした。 業務でReactNative本体のソースコードを読むことはほとんどないので、いい機会だったと思います。 次回もまた出展したいとは思います!内容が決まればですが・・・。

技術書典では、参加者としてもたくさんの技術書を買いました。 手当たりしだい買っていたので、むしろ買いすぎた気がしています。 今でも出社前に読みふけっています。

おわりに

ここまで読んでいただきありがとうございました。

今後は、弊社の個性豊かなメンバーが更新していきます。 もちろん、僕も何度か登場することになります。 楽しみにしていてください!!

最後に告知があります!!

Ruby LT大会

11/29(木)に「スタートアップテック vol.1: Ruby LT大会」が開催されます。 スタートアップテックとは5社合同で開催するエンジニア交流会です。 初回はRubyに関するLT大会です。

OPEN8からは、インフラエンジニアとして活躍する川島が登壇します。 発表の内容は、OPEN8のコアテクノロジーにも関わってくる動画処理についてです。

connpass.com

参加枠は多めにとってあり、現在も枠は空いています。 Rubyに興味がある、これから始める予定でLTだけでも聞いてみたい方など、 ぜひご参加ください。

今後、様々なテーマで開催していきます。