Slide 1

Slide 1 text

jQuery + Sass な SPA Rails アプリを React + CSS in JS にリプレースした話 Rails Developer meetup 2019 - Keisuke Ishizawa, Masayuki Goto

Slide 2

Slide 2 text

2 今日話すこと ● リプレースをうまく進めていくコツ、小口化 ○ 機能:画面をパーツごとに ○ 網羅性:保守性を上げる目的で及第点を目指す ○ 影響度:限定公開することで ● Rails における React を活かした UI/UX への刷新事例 ○ Sass → CSS in JS への置き換え ○ コンポーネントの振る舞いを jQuery → React に ○ jQuery・React それぞれの spec を並行稼動 ○ Rails における React i18n 化

Slide 3

Slide 3 text

jQuery ⇒ React の リプレースをうまく進めていくコツ

Slide 4

Slide 4 text

4 自己紹介 ● 氏名:石澤 佳祐 ● GitHub, Qiita: @kei-p ● 株式会社 SonicGarden 3 年目 ● Rails 歴 4 年 ● 今日は @jnchito に送り込まれてきました ○ 発表初めて!

Slide 5

Slide 5 text

5 ● 納品のない受託開発 ● 自社サービス → 今回は自社サービス「Remotty」についての話をします

Slide 6

Slide 6 text

6 Remotty について ● SonicGarden のバーチャルオフィス / お客さんにも提供 ○ https://www.remotty.net/ ● 技術的な要素 ○ SPA ○ チャット、掲示板 ○ カメラ撮影 ○ リアルタイム

Slide 7

Slide 7 text

Q:jQuery 辛いひとー  

Slide 8

Slide 8 text

8 ● 状態管理が煩雑 ○ data-xxx 、selector がいっぱい ○ change イベント拾って、ごにょごにょ ● イベントが分散しがち ○ 実際に、セレクターが複数の箇所で bind されてたことも。。。 ● 技術的なトレンドから遅れてる感(2017年時点) jQuery 辛い

Slide 9

Slide 9 text

そうだ、React にしよう

Slide 10

Slide 10 text

10 でも、なかなか進まない。。。 ● まとまった時間がとれない ● 既存のサービスの改善が優先されてしまう ● 両方を進める同時に進めづらい ○ コンフリクト怖い ● そもそも、引き継いだプロジェクトだし ○ どんぐらい大変?なんか不安。。。

Slide 11

Slide 11 text

そうだ、小口化だ

Slide 12

Slide 12 text

12 小口化とは? ” 工程ごとに一斉に作るのではなく、動くものまで一気通貫で少しずつ作ってい く方法 “ 「生産性の高い組織を実現するための小口化の原則」 https://kuranuki.sonicgarden.jp/2018/08/babysteps.html SonicGarden の価値観の一つ

Slide 13

Slide 13 text

13 リニューアルを小口化する ● 機能:パーツごとに開発 ● 網羅性:保守性を上げる目的で及第点を目指す ● 影響度:限定公開することで安全にリリース

Slide 14

Slide 14 text

機能の小口化

Slide 15

Slide 15 text

15 ● 機能的に疎結合なパーツに分けて、段階的に置き換え ● 順番としては、要素(機能)が少なそうなパーツから着手 ○ ボタン、イベントの数 ○ 閲覧 / 編集 ● 順番 ○ ① アクティビティログ ○ ② ビデオキャプチャ ○ ③ チャット ○ ④ 掲示板一覧 ○ ⑤ 掲示板 機能の小口化 ① ② ③ / ⑤ ④

Slide 16

Slide 16 text

16 jQuery から React へ部分的に置き換え rooms/show.html.haml .room .board-menu - # 掲示板一覧 .board - # 掲示板 components/room.jsx export default class Room extends React.Component { render() { return (
) } }

Slide 17

Slide 17 text

網羅性の小口化

Slide 18

Slide 18 text

18 網羅性の小口化 ● すべてのコンポーネントを完全に React には置き換えない ○ ⇒ イベントハンドリングが複雑、state の管理が大変なものを優先 ● tooltip や popover など複雑な処理がないものは、jQuery を流用 ○ React への置き換えは後半で説明します

Slide 19

Slide 19 text

19 componentDidUpdate() { this.updateParticipationTooltip(); } updateParticipationTooltip() { const $target = $(this.tooltipRef); const participation = this.props.participation; const content = ``; $target.popover('destroy').data('content', content); } render() { return (
this.tooltipRef = ref }>
/* 省略 */
) } React と jQuery をミックス props の内容が変わったときに、 popover の中身を更新

Slide 20

Slide 20 text

影響度の小口化

Slide 21

Slide 21 text

21 影響度の小口化 ● いきなりお客さんに公開するのではなく、限定公開 ○ ⇒ 社内(SonicGarden) での試験運用 ○ フィーチャートグル ● jQuery 版, React 版の両バージョンの動作はテストで担保

Slide 22

Slide 22 text

22 class RoomsController < ApplicationController before_action :set_react, only: %i[show] def show if react? render 'react' end end def set_react return unless @room.sonicgarden? && params[:react].present? session[:react] = params[:react].to_i end def react? session[:react] == 1 end end フィーチャートグル 限定公開 params で切り替え

Slide 23

Slide 23 text

23 def react? session[:react] == 1 || ENV['FORCE_REACT'] end ● テスト実行時に強制的に react 状態にさせる React のテスト ● feature spec は jQuery 版、React 版で共通化 - run: bundle exec rspec - run: FORCE_REACT=1 bundle exec rspec

Slide 24

Slide 24 text

24 まとめ ● リプレースの小口化 ○ 機能 / 網羅性 / 影響度 ● React のリプレースは、だいたい 8ヶ月 くらい ■ 2017/07 ~ 2018/02 ● 小口化の効用 ○ サービスの改善とReact 化を柔軟に対応 ○ 考える範囲や不安が減って、堅実に安心して進められた

Slide 25

Slide 25 text

React へのリプレースはできた! ⇒ 次はより React を活かした UI/UX へ

Slide 26

Slide 26 text

React を活かした UI/UX へ Rails での刷新事例

Slide 27

Slide 27 text

27 自己紹介 ● 氏名:後藤雅之 ● GitHub / Qiita: @gotchane ● 株式会社 SonicGarden もうすぐ2年目 ● Rails 歴 約1年弱 ● 入社してからインフラ→アプリに転向 ● 僕も初発表!

Slide 28

Slide 28 text

28 ● ユーザー数拡大のため、利用しやすい UI/UX を目指す ● 現在、7割程度リニューアルが進んでいる状況 Remotty サイトリニューアルについて

Slide 29

Slide 29 text

29 サイトリニューアルの方針 ● 公開されているサービスに影響を与えず刷新を進める ● サイトリニューアルと併せて、React 正式公開も目指す

Slide 30

Slide 30 text

30 道は険しかった。。

Slide 31

Slide 31 text

31 いざ取り組むも、未知の領域 ● React でのリニューアルの知見がほとんどない状況。。 ● React どころかフロントエンドの経験もほぼゼロ。。 ● 何度も課題にぶち当たりながら道を模索 ● 自分との闘い

Slide 32

Slide 32 text

32 課題に対する取り組み 1. Sass → CSS in JS への置き換え 2. コンポーネントの振る舞いを jQuery → React に 3. jQuery 版 / React 版での RSpec 並行稼動 4. Rails における React i18n 化

Slide 33

Slide 33 text

1. Sass → CSS in JS への置き換え

Slide 34

Slide 34 text

34 課題 ● 既存のデザインには影響を出せない不安。。 ● CSS in JS を使い、Sass と疎の状態で刷新できないか? ● 簡単に試行錯誤を進められそうな CSS in JS は?

Slide 35

Slide 35 text

35 const Button = styled.a` padding: 0.5rem 0; margin: 0.5rem 1rem; color: white; ` ReactDOM.render(
styled-components
, document.getElementById('root') ) そこで styled-components ● CSS in JS の一種で、シンプルに CSS を記述可能 ● Sass でできることはほぼ実現可能 タグ付きテンプレートリテラルで囲んだ形

Slide 36

Slide 36 text

36 styled-components 適用の進め方 ● 機能の小口化と同じ要領で、小さいパーツから適用 ③ ② ① ④ ⑤ ⑥

Slide 37

Slide 37 text

37 コード置き換え例 entry-badge.sass =badge display: inline-block .entry-badge +badge background: green &.is-mention background: red entry-badge.jsx const badge = () => css` display: inline-block; ` const EntryBadge = styled.div` ${badge()} background: ${ props => props.isMention ? 'red' : 'green' }; ` ReactDOM.render(
, document.getElementById('root') ) entry-badge.html.haml .entry-badge = render 'xxx' mixin の利用 props による切り替え

Slide 38

Slide 38 text

2. コンポーネントの振る舞いを jQuery → React に

Slide 39

Slide 39 text

39 課題 ● 詳細な振る舞い、ペンディングしてたよな。。 ● デザイン刷新にあたり、新たな振る舞いも出てくる ● なるべく React に閉じた形を目指す

Slide 40

Slide 40 text

40 対応方法 ● 振る舞いを個別に切り出し、対応ライブラリで置き換え ● 新しい振る舞いも追加しやすくなった 振る舞い例 Before After モーダル Bootstrap reactjs/react-modal ポップオーバー Bootstrap littlebits/react-popover キーボードショートカット jQuery による実装 jaywcjlove/hotkeys

Slide 41

Slide 41 text

41 コード置き換え例 jump_view.html.haml %span.jump-btn = link_to 'Ctrl+k', '#' .jump-modal.modal = render 'modal' jump_view.js.coffee $(document).on 'click','.jump-btn',-> $('.jump-modal').modal('toggle') isShortCut = (event) -> event.key == 'k' && event.ctrlKey $ -> $modal = $('.jump-modal') $(document).on 'keydown',(event)-> if isShortCut(event) $modal.modal('toggle') jump_view_button.jsx class JumpView extends React.Component { constructor () { super() this.state = { showModal: false } } onKeyDown = () => {/* ... */} toggle = () => {/* ... */} render () { return ( )} } 振る舞いが閉じた形に

Slide 42

Slide 42 text

3. jQuery 版 / React 版での RSpec 並行稼動

Slide 43

Slide 43 text

43 課題 ● feature spec のセレクタは、旧デザインが適用された状態 ● 新旧共通の動きのテストで spec を再利用できない。。 ● 新旧両方で spec をうまく活用できる方法はないか?

Slide 44

Slide 44 text

44 ● デザインに依存しない、spec 用のクラスを適用 ● spec の再利用・新規追加がスムーズになった menu_container.html.haml .menu__container.spec-menu__container = render 'xxx' 対応方法 menu_container.jsx xxx spec 用クラスを付与

Slide 45

Slide 45 text

45 menu_container_spec.rb feature '掲示板', js: true do scenario '掲示板の表示' do expect(page).to have_selector ".spec-menu__container" end end feature spec コード例 spec 用のクラスを指定し、新旧両方で再利用可能に

Slide 46

Slide 46 text

4. React での i18n 化対応

Slide 47

Slide 47 text

47 課題 ● React 上では Ruby の I18n.t メソッドが使えない。。 ● 翻訳データは静的ファイルでなく自社 Web サービスで管理 ● Web サービスの翻訳データから i18n 化するには?

Slide 48

Slide 48 text

48 ● 静的な翻訳ファイルをエクスポートして表示する ● Web サービス上の翻訳データを直接活用できないか? React i18n 化:静的な翻訳ファイルを挟む react-i18next 翻訳ファイル (.yml など) react 翻訳データ管理 Webサービス

Slide 49

Slide 49 text

49 ● 翻訳データを返す GET メソッドを準備し表示 React i18n 化:Webサービスから直接 react-i18next 翻訳データ管理 Webサービス react i18next- xhr-backend Rails Controller 翻訳データを返すメソッドを準備し、XHR でリクエスト

Slide 50

Slide 50 text

50 コード例:翻訳データ取得 translations_controller.rb class TranslationsController < ApplicationController def index @translations = SampleTranslationClient .data render json: @translations end end i18n.js import i18n from "i18next" import { reactI18nextModule } from "react-i18next" import Backend from "i18next-xhr-backend" i18n.use(Backend).use(reactI18nextModule) .init({ backend: { loadPath: "./translations.json" , }, whitelist: ["ja", "en"] }) export default i18n 翻訳データを JSON で返すメソッドを準備 XHR でメソッドを実行する定義を追加

Slide 51

Slide 51 text

51 コード例:翻訳データ表示 sample_component.jsx import { t } from "i18next" import { withNamespaces } from "react-i18next" class Sample extends React.Component { render () { return ( {t("sample.component.text" )} ) } } export default withNamespaces( "translation")(Sample) キー指定で翻訳データを呼び出し

Slide 52

Slide 52 text

52 まとめ ● 新と旧を徐々に疎の状態にしていくことを目指す ● 課題を一つ一つ丁寧に潰すことで道が見えてくる ○ Sass には手を入れず styled-components で刷新 ○ jQuery に依存させず、React に振る舞いを閉じ込める ○ 新旧デザインに依存しないセレクタで spec を活用 ○ React i18n 化の仕組みを新たに準備

Slide 53

Slide 53 text

53 今日のまとめ ● リプレースをうまく進めていくコツ、小口化 ○ 機能:画面をパーツごとに ○ 網羅性:保守性を上げる目的で及第点を目指す ○ 影響度:限定公開することで ● Rails で React を活かした UI/UX への刷新事例 ○ Sass → CSS in JS への置き換え ○ コンポーネントの振る舞いを jQuery → React に ○ jQuery 版 / React 版での RSpec 並行稼動 ○ Rails における React i18n 化

Slide 54

Slide 54 text

ご清聴ありがとうございました!