83
Help us understand the problem. What are the problem?

More than 5 years have passed since last update.

posted at

updated at

Organization

Real World React 2 - 2016/09/27 - React Meetup #4

Real World React 2 - 2016/09/27 - React Meetup #4

by mizchi
1 / 80

About

  • @mizchi
  • Qiita のフロントエンドエンジニア
  • React勝手エヴァンジェリスト
  • 4ヶ月で14kg痩せた (80kg => 66kg)

近況

  • ビジネスドメインのリファクタが主でアウトプット少ない
  • 泥臭系の知見は溜まってる
  • 会社のメンバーが増えた + 自分以外のフロント系の人が増えたので、設計を明示しないといけない

この資料は何


ふつうのウェブアプリ

  • NOT SPA
    • SPA でなくともReactは使える
    • jQuery に支配された現代のフロントエンドを改善したい

=> 複雑なモジュールを局所的に解決するのにReactが有用であることを示したい


フロントエンドの流れ

  • AJAX以前(~2003)
  • jQuery時代(2004~)
  • 構造化jQueryの時代(2011~)
    • Backbone
  • データバインディングの興隆
    • Knockout
    • Angular 1/2
    • Vue
  • 仮想DOMの時代
    • React

(次は仮想DOMの隠蔽が次に来そう)


Reactの難しさ再考

大きく2つ

  • Reactを使うための前提となるエコシステム構築
  • Reactの複雑化に対処のためのFlux概念

今回話すのは前者を倒す


Reactが乗る為のモダンなJSとは(mizchi主観)

  1. npm/browserify or webpackで依存を解決
  2. Babel/ES2015
  3. React/Flux
  4. Testable
  5. No More jQuery plugins
  6. No Side Effect on module loaded

Qiita 2015

  1. CoffeeScript
  2. Sprockets / グローバル名前空間渡し
  3. Backbone
    1. JSのテストはjasmineで数件
  4. jQuery plugins や jQuery UI まみれ

※ Railsのリクエストスペックは豊富


初見での評価

  • Qiitaは2011末から開発されている
    • 当時の選択基準としてはスジ悪ではない
  • 十分にユーザーに価値を提供している(重要)
    • 価値を生まないコードはメンテする価値が無い
  • ただちょっとCTO(yuku_t)の手癖がちょっと強いかな…

降ってきた仕事

=> なんかよくしてくれ


なんかよくする


なんかよく

  • モダンな開発環境を導入する
  • 今のコードを洗練させる
  • 再利用するコードとできないコードを分別する

最初の失敗


失敗

  • 編集画面を書き換えようとした

inline


書き換えようとした理由

  • コードが多いので置き換えれば古いコードをごっそり消せる
  • 拡張の要望が多い
  • エディタならKobitoの開発のノウハウを活かせる

破綻へ

  • まずやっぱり分量が多い
  • つつくと無限に知らない仕様が出てくる
    • そもそもドメイン知識がなかった
  • 判明する仕様を継ぎ接ぎするとコードが綺麗にならない…

進捗

  • 「2週間ぐらいで終わらせるわ〜」
  • 書き直したコードが読み易くならず辛い
  • => 2ヶ月たっても終わらず

結果

  • 一部のぞいてコードを破棄して中断

教訓

  • 仕様を理解してないもののコードは書けない
  • モジュールの境界面が明示されてないものは分解できない
  • 「別実装で元の仕様を完全再現」はエネルギーの無駄
  • 見積もりは失敗する

振り出しに戻る


決意

  • エコシステムを整理しよう
  • 現時点での負債と使える部分を認識しよう

ゴールの設定

  • React/Babelで新規モジュールを受け入れられる環境
  • Turbolinks(PushState)が導入可能な初期化フローを作る(使うかはともかく)

Rails上のフロントエンドエンジニアの設定

  • React.Component提供おじさん
  • history.pushState が導入できる状態を作る
    • 運用するかは別
    • コードのリファクタ目標として有用

足元を見直す


やったこと

  1. npmに依存ライブラリを集約
  2. Sprockets => browserify(-rails)
  3. ユニットテストの導入
  4. Reactでコンポーネントを置き換え

1. npmに依存ライブラリを集約


npmに依存ライブラリを集約: 元の状態

  • ライブラリごとに異なるCDNを参照
  • オーバーヘッド大
  • どのライブラリのどのバージョンを使ってるか見通しが悪い

npmに依存ライブラリを集約: 変更後

  • npmとbowerでライブラリの依存を解決するようにした
  • 1つのファイルに固めて自前の S3から CloudFront で配信
  • 更にnpmに集約を進めてbowerを削除
  • bowerやめろ

2. Sprockets => browserify


browserify導入

  • commonjs形式で書かれたファイルを静的解析して1つのファイルにビルド
  • グローバル変数を使わずにモジュールの解決ができるようになる

そして Sprockets を捨てる


Sprockets

  • Rails上のデファクトの標準モジュールシステム
  • 拡張子ごとに変換

Sprocketsの問題

  • ファイルスコープで返り値を持てない
  • nodeで動かない
  • rubyの問題とjsの問題を切り分けられない
  • JSエコシステムに乗れない
  • Sprocketsで動くJS系のgemメンテされない

hoge.js.coffee.erb

# require_tree ./foo_dir
# require app

必ず asset_root(app/assets/javascripts) から解決



書き換える


書き換えたい…

  • 分量が多い
  • 平行して開発している機能がたくさんある
  • ちんたらやってると無限にコンフリクトする

解法


  • なんかやる
function convertSprocketPathToCommonjsPath(root, fpath, spath) {
  if (/^\./.test(spath)) {
    return spath;
  }
  let relToRoot = path.relative(fpath, root);
  let rel = path.join(relToRoot, spath).replace(/^(\.\.\/)/, "");
  return rel.indexOf("..") > -1 ? rel : "./" + rel;
}


fit


達成

  • ファイル単位の依存は明示された
  • グローバル変数依存なのは変わらず

Qiitaのモジュールシステム(旧)

  • Sprocketsでファイル連結
  • グローバル変数渡し
new Qiita.views.HomeView();

//= require foo
Qiita.util.foo();
Qiita.util.bar = function(){...};

const foo = require('./foo');
foo();
module.exports = function bar() {...}

browserify によってもたらされるもの

  • 依存がそれぞれのファイルで完結した状態
  • 単体テスト可能な閉じた参照の提供
  • 名前空間の初期化順に左右されなくなる

この過程でやったこと


gulp

  • 「フロントが更新されたら npm install と gulp を叩いてください~」
  • => しない
  • 「動かないんだけど」
  • => 対応で一日が終わる

Sprocketsを捨てる準備


browserify-railsについて

  • 中で使ってるのはbrowserify-incremental
  • ぶっちゃけwatchifyの方が速い
    • babelの初期化の差
  • いろんなトレードオフ考えてアリ

結果

すべてを browserifyのtransform で解決した


3. ユニットテストの導入


状況

  • browserifyによって各モジュールの依存が明示された
  • jasmineが重いのでnodeでユニットテストしたい
  • E2Eテストはまだ考えない

テスト対象

  • モジュールで分解された再利用性のコード
  • 新規に書かれる react component
  • 依存が壊れてないか、読み込んだだけで副作用が発生しないか検知

テストを書く


最初の書き換え

+ module.exports =
Qiita.util.foo = function() {...}

※ coffee が多いので commonjs


テスト

import foo from "../../src/util/foo";
describe("util", () => {
  describe("foo", () => {
    it("returns foo", () => {
      assert.equal(foo(), "foo");
    });
  });
});

潰す

  • git grep 'Qiita.util.foo'
  • 全部requireに書き換える
  • Jasmineのコードを全部置き換える

テストの例

  beforeEach(() => {
    deleteSrcCache();
    let jsdom = require('jsdom').jsdom;
    global.document = jsdom('<html><body></body></html>')
    global.window = document.defaultView;
    global.navigator = window.navigator;
    global.location = window.location;
  });

  afterEach(() => {
    delete global.document;
    delete global.window;
    delete global.navigator;
  });

毎回 require cacheは消す

export function deleteSrcCache() {
  Object.keys(require.cache).map(cachePath => {
    if (/src/.test(cachePath)) {
      delete require.cache[cachePath];
    }
  });
}

グローバル変数に副作用があると require cacheに引っかかってしまうと再定義されない


テストフレームワークの選定

  • Mocha が RSpec に雰囲気似てるので書いてもらいやすい
  • ava もいいけど独自概念が多くてめんどかった

React のテスト

今は airbnb/enzyme: JavaScript Testing utilities for React 一択


3 Reactに書き換え


ヘッダ

fit


スライド機能(これ)


コメントフォーム


React化

  • Fluxの選定面倒だったのでベタ書きした
  • react-rails の prerender: false で書き出し

react-dispatcher-decorator


なぜ Redux ではないか

  • 非SPAに統合的な管理機構が(たぶん)ワークしない
    • シングルストアにするのは困難
  • 解決したい問題に対してコードが大げさに

シングルストア設計を目指すには

  • DOMに紐付かない仮想ルートオブジェクトと、画面に出現しうる要素の紐付を行うモジュールを自作する必要

Rails上のReact


react-rails

  • Rails側で = react_component("Header", {a: 1}, {prerender: false})
  • <div data-props='<serialize化されたjson>'..>...</div>
  • Rails => JSのデータ受け渡しプロトコルがほしかった
  • SSRはサーバーの負荷みて段階的に導入(したい)

react-rails でのデータの渡し方

  • ApplicationHelperでハッシュに変換するヘルパを用意
def comment_to_component_props(comment)
  { ... }
end
  • Railsではこのメソッドをテストする
  • 将来的にGraphQLを使う場合、たぶんここを置き換える

react-railsの次

今は代替手段がたくさん

あたりが気になってる


Flowtype


Flowtypeの導入

  • 新規で書く部分はBabelで
  • 古いモジュールも今後も開発する部分をBabelで
  • Babelだったら段階的にFlow入れられるやん => 導入

Flowtypeの選定理由

  • 段階的に導入していける
  • React/JSX に対してかなり型が効く
  • 推論が優秀
  • フロントのテストしづらい部分をせめてでもカバー

参考: 型なき世界のためのflowtype入門 - Qiita


Typescript を選ばなかった理由

  • トップレベルで全部のコードを扱えないと不便
    • babel/coffeeと組わせるとgulpの複雑なタスクを組む必要
  • (最近の開発方針がなんか気に食わない)

色々辛かった


今やってること

編集画面のReact化


最後に

  • がんばっていきましょう
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
83
Help us understand the problem. What are the problem?