Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?

React/FluxでSPAを開発してぶちあたった問題

(この資料は専用のプリプロセッサで動くことを全体にしたドキュメントです)

React/FluxでSPAを開発してぶちあたった問題

mizchi / Increments, Inc.

@ React Meetup #1


このプレビューツールは何だ

  • Kobito for windowsをプレビュー用にちょっといじったもの

About

  • mizchi / 竹馬 光太郎
  • Qiitaの会社より
  • Reactバズらせ手

俺が言いたいことはだいたいみんなが消化してくれた

もう話すことない!

エッジケースと、なんでそのライブラリ作ったか、みたいな話します


今日のテーマ

Reactで一つのアプリケーションを仕上げるにあたり、直面した問題と解決方法


何を作ったか


コンセプト

既存のkobitoの機能(markdown preview + Qiitaへの同期) + Markdownエディタとしての使い勝手向上


アプリケーションの性質

  • 画面数は少ない
    • 閲覧/編集/設定/ログイン
  • 画面ごとのロジックの密度が高い
    • テキストエディタ
    • Markdown Preview
    • 記事一覧のソート/検索
    • Qiitaとの同期/コンフリクト検知

規模感

  • 開発開始から4ヶ月
  • およそ12000行
  • エンジニア 1+α人
  • デザイナ 0.5人

主な技術スタック

  • React
  • Electron(旧AtomShell)
  • CodeMirror: テキストエディタ
  • IndexedDb: ストレージ
  • Qiita API v2: サーバー

前回の進捗

Real World Virtual DOM // Speaker Deck

  • ↑は2ヶ月前時点のもの
  • Fluxの設計や思想、Ardaについては↑で

今の開発状況

  • クローズドβテスト中
    • プロトに1週間
    • 設計に2ヶ月
    • フィードバックからの品質改善
  • バグ潰し/ブラッシュアップ
  • (来月にはリリースするかも?)

設計に2ヶ月?

  • ライブラリ選定/プロトタイピング/学習 のフェーズ
  • 結局フレームワークからストレージ層まで自分が求めるのなくてライブラリを自作した

(去年の11月時点ではReact/Fluxの情報が少なかった)


直面した問題

  • 画面遷移を表現できるFluxが少ない
  • ReactのdangerouslySetInnerHtml
  • (jsx辛い)

画面遷移


Reactで画面遷移どうする?

  • おそらくreact-routerが一番使われてる
  • 自分は気に食わなかったので自作した

画面遷移を記述しやすくしたFluxフレームワークを書いた


Ardaのコードの例

router = new Arda.Router(Arda.DefaultLayout, document.body)
router.pushContext(MainContext, {})             # Main
.then => router.pushContext(SubContext, {})     # Main, Sub
.then => router.pushContext(MainContext, {})    # Main, Sub, Main
.then => router.popContext()                    # Main, Sub
.then => router.replaceContext(MainContext, {}) # Main, Main
.then => console.log router.history

前後の状態をキャッシュして画面をpush/popする


Reactで発生した問題


Reactで発生した問題

  • HTMLのリアルタイムプレビューでdangerouslySetInnerHtmlを使うと頻繁に ブラウザが固まる

dangerouslySetInnerHtml

  • ReactにHTML文字列をそのまま挿入する唯一の方法
  • 名前の通り危険なので推奨ではないAPI
function createMarkup() { return {__html: 'First · Second'}; };
<div dangerouslySetInnerHTML={createMarkup()} />

kobitoでのdangelously~

  • markdownをプレビューするというアプリの性質上、プレビューが必須
  • 公式のサンプルにもmarkdownプレビューはあるが、巨大なドキュメントを突っ込むといとも簡単に壊れる
  • React JS Markdown Editor - JSFiddle

試した方法

  • iframeに突っ込む
  • 毎回クリーンしてから表示

どちらもパフォーマンス上の問題を抱える


結局どうしたか


md2reactの開発

  • markdownをReactElementに変換して挿入
  • Reactで差分描画することで 巨大なドキュメントでも高速に描画できるようになるはず
  • mizchi/md2react

md2react playground


md2reactの使用例

global.React = require('react');
var md2react = require('md2react');

var md = '# Hello md2react';
var html = React.renderToString(md2react(md));

出力

<div data-reactid=".14qrwokr3sw" data-react-checksum="20987480"><h1 data-reactid=".14qrwokr3sw.$_start_root_0_heading"><span data-reactid=".14qrwokr3sw.$_start_root_0_heading.0">Hello md2react</span></h1></div>'
//'<div data-reactid=".58nba97pxc" data-react-checksum="-55236619"><h1 data-reactid=".58nba97pxc.0"><span data-reactid=".58nba97pxc.0.0">Hello</span></h1></div>'

md2react の構成

  • mdast でASTにパースする
  • ASTをトラバースしてReactElementに変換
  • HTML埋め込み記法はプリプロセスで別途ASTに変換(thx @uasi)

他パフォーマンス上の問題の解決.1

Componentがpropsに対して冪等な設計すると、やっぱり一部重いのでキャッシュ戦略が必要だった

  • componentWillUpdate(object nextProps, object nextState) を使う
  • 前後のプロパティで比較してキャッシュ破棄/更新

他パフォーマンス上の問題の解決.2

boolean shouldComponentUpdate(object nextProps, object nextState)

この関数をオーバーライドしてfalseを返すとComponentの更新をキャンセルする


JSX


JSXの問題

  • かなりJS寄り
  • デザイナに気軽に覚えてもらえるようなテンプレートエンジンがほしい
  • 「Arrayをmapしてリスト要素作って変数にいれたのを別の箇所で展開する」のをデザイナに要求するのきつくないですか?

react-jade

  • jadeテンプレートを拡張したもの
  • hamlとかslim知ってると問題なく使える
doctype html
html(lang="en")
  head
    title= pageTitle
    script(type='text/javascript').
      if (foo) {
         bar(1 + 5)

react-jadeの問題

  • 複数行のコードの入力ができない
  • 生JSしか書けない
  • React固定

複雑なコンポーネントを書き始めるとインラインJSが書きづらいのがかなり辛い


Reiny

テンプレートエンジン作り始めた

mizchi/reiny

WIP


特長

  • ReactElementを吐く関数をテンプレート(一種のAltJS)
  • インラインCSSが書きやすい
  • インラインコードはBabelでプリプロセスする
  • 任意なVirtualDOM実装を吐く(React/Mithril/Deku/virtual-dom)

Advancedな機能

  • インラインHTMLからSCSSを吐く
  • React.PropType を吐く

reinyの例: 1

.main.container&mainCotnainer(
  data-id = 'this-is-id'
) {
  background-color: #eee
  width: 640px
  height: { 40 * 12 }
  font-size: 1em
}
  // text
  h1 This is a title
  | expand with span
  span = @greeting

reinyの例: 2

- let onClick = () => console.log('clicked);
div(onClick=onClick)

インラインコードはbabelでプリプロセスされる


reiny 使えるの?

パーサーから作っててエッジケースが潰れてない

とりあえずreact-jade倒すぞ!!


Reactを4ヶ月使った感想まとめ


感想

必要なパーツが足りない/or既存実装が気に食わないので、結局全部自分で作った


React周辺のエコシステムの問題点

  • ポテンシャルの割に個々のユースケースの差を吸収できるほどのライブラリは、まだ揃っていない。
  • ↑自分で作るチャンス!!

「お前自分で全部作ってるけど本当に他人に薦められるの?」

  • 選択肢が少ないことが我慢できれば使える
  • 自分は我慢できない
  • 趣味みたいなもん

Reactの良さ

  • 表面的にはAPIが少ない
  • ヘッドレステストができる
  • (jQueryとかどうでもよくなる)

一般的なウェブサイトにおけるReactの適用

  • サイズが大きい(.min.jsで114kb)
  • 一般的なjQueryと混ざらない
  • ある程度以上複雑(一定以上の頻度の書き換え)じゃないとメリットがない

Reinyの目的の一つ

というわけで普通のウェブサイトでもランタイムが小さいmithril/deku等を使えるようにしたい!


おわり

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment