読者です 読者をやめる 読者になる 読者になる
月 の 上

FRONTEND CONFERENCE 2017でReactハンズオンを開催しました

speakerdeck.com

2017-03-18 (土) 梅田で開催された FRONTEND CONFERENCE 2017 で、Reactのハンズオンを開催しました。

kfug.jp

今回のハンズオンでは

  • Reactアプリケーションの開発を身体で覚える
  • React開発環境におけるトレンドを体験する

の2つを目標に、Next.jsを利用した簡単なReactアプリを作成しました。

この記事では、ハンズオン開催にあたって考えたことや、工夫した点などについて書いていきます。

ハンズオンの方向性

2月半ばにハンズオン講師のお誘いがあり、引き受ける事にしたものの、どのようなテーマにするかしばらく悩んでいました。
ハンズオンの時間は60分です。
Reactのメリットについてひたすら説明しても良いけど、60分でどれだけ伝えられるかわかりません。
まずは参加者層を想定し、それからテーマを考えることにしました。

昨年のFRONTEND CONFERENCEに参加した際、東京のエンジニア向けカンファレンスとは参加者層が異なると感じました。
全体的に、デザイナーやコーダーといった、エンジニア以外の職種の人が多い傾向にあります。
同じエンジニアでも、受託系のWeb制作会社やフリーランスの方が多いイメージです。

Reactの非常に強いコンポーネント指向は、大規模なWebアプリや寿命の長いWebサービスにおいて、コードの保守性を高め、長期的な開発コストを抑えられます。
しかし、いわゆるMVCフレームワークの経験のない人にとっては、設計上の利点が実感しづらくもあります。

Reactを使う以上、参加者にはコンポーネント指向の利点を体感してもらいたいです。
しかし、ハンズオンでは、60分で未経験者にReactアプリケーションを完成させてもらう必要があります。
そのため、Reactの概念や設計について時間をかけて説明するよりも、実際に手を動かすことで自然にコンポーネント開発を体験してもらう、という方向にしました。

Next.jsを採用

github.com

Next.jsはReactで簡単なSPAを開発するためのフレームワークです。
Next.jsでの開発は、ページに対応するJSファイルを pages/ に作成する所から始まります。
まるでHTMLファイルをpublicディレクトリに配置するような感覚で開発でき、初めてSPAを開発するときに躓きがちなポイントを回避できます。
また、最初からServer-Side Renderingが有効になっていたり、CSS in JS用のツールが組み込まれていたりと、Reactコミュニティで流行している技術を簡単に体験できるようになっています。

ハンズオンの流れ

ハンズオンは8つのステップに分かれていますが、学習内容としては4段階に分類できます。

まずはページを作成し、Reactコンポーネントの作り方とNext.jsの基本を覚えます。
初見時のシンプルさを重視し、コンポーネントはStateless Functional Componentで作成するようにしました。

次に、共有コンポーネントを作成し、importprops について学びます。
これにより、参加者は一般的なテンプレートエンジンと同様の機能をReactで実現できるようになります。

スタイルはCSS in JSで定義するようにしました。
他の方法よりも強いコンポーネント指向を体験でき、Reactコミュニティの流行についても触れることができるからです。

最後に状態管理について学びます。
コンポーネントをまたいだ状態管理が必要なアプリを作ることで、「状態の管理は親コンポーネントで一元的に行なう」「末端のコンポーネントは状態を管理しない」というコンポーネント指向を身に着けます。

工夫した点

ハンズオンの準備は、リポジトリ作成、教科書作成、そしてスライド作成という順序で行ないました。

これらの準備において工夫した点について説明します。

お手本ブランチを用意する

ハンズオンは、教科書リポジトリを手元に git clone し、少しずつ実装を進めるという流れで行ないます。
ブランチの構成は以下のようになっています。

f:id:amagitakayosi:20170324175624p:plain

develop ブランチにお手本を作り、ステップに対応したタグを登録しました。
これにより、参加者がどこかで詰まったとしても、 git checkout step-4 のようにすることでキャッチアップできるようになっています。

スライドにはステップ毎に画面のスクリーンショットを載せ、上手く行っているか確認できるようにしました。

コード修正の指示をdiff形式で書く

developブランチからPull Requestを作る事で、各ステップで必要な変更内容を一覧できます。
教科書には各ステップのdiffを掲載し、編集する内容が正確にわかるようにしました。

f:id:amagitakayosi:20170324175516p:plain

diff形式に馴染みのない方も多いはずなので、ハンズオンの冒頭でdiff形式について解説しています。

自己紹介よりも前にセットアップをお願いする

ハンズオンで良くあるのが「npm installが終わらない〜〜」というやつです。
git cloneやnpm installはただでさえ時間がかかりがちですが、皆で一斉に実行すると会場のネットワークに負荷がかかり、余計に時間がかかってしまいます。

今回は、自己紹介より前にセットアップを行なうことで、この問題を回避しようとしました。

f:id:amagitakayosi:20170324175541p:plain

本番では開始時間5分前には皆着席していたので、更に時間が稼げてよかったです。
前日にTwitterで事前準備をお願いしたりもしました。

https://twitter.com/amagitakayosi/status/842633938275254272

反省した点

gitが手元にない人は git checkout できない

参加者にはgitを利用していない方もいると思い、レポジトリclone時には「git clone または zip でダウンロード」と説明しています。
しかし、実装でつまづいた時に git checkout できない事を忘れていました……。

ステップ毎にzipを用意しておくと良かったかも。

お手本ブランチ作るのしんどい……

れいなコミットログを作るため、developブランチを作る前に別ブランチで試行錯誤し、developブランチで清書したのですが、それでも後から修正箇所が沢山見つかりました。
git commit --fixup git rebase git push -f を活用し、無理矢理きれいなコミットログに仕上げたのですが、これが大変だった……😇

  • 過去のコミット foo12345 に間違いが見つかる
  • git commit --fixup=foo12345 && git rebase autosquash HEAD~n
  • git push -f
  • foo12345 移行のコミットにタグを打ち直す

なんかもっと効率いい方法見つけたいですね……

皆様の反応

ハンズオン

twitter.com twitter.com twitter.com

やっぱちょっと速かったかな🙇🙇🙇

資料

twitter.com 極端な話、Reactコンポーネントの書き方しか知らない人でもWebアプリが作れるので、HTMLベタ書きの時代よりも複雑ということは無いと思います。
Node.jsが動かないとデプロイできないという問題はありますが、Next.jsの次のバージョンでは静的ファイルへのエクスポートも出来るようになるらしいので、楽しみです。

twitter.com twitter.com

動的な要素の少ないWebサイトでは、Reactのメリットは想像しづらいかもしれませんね。
それでも、コンポーネント指向のおかげでパーツの再利用が容易だったり、テストしやすい、モダンな開発環境が整備されている、といったメリットはあると思います。

感想

ハンズオン講師は初めての経験でしたが、多くの方が完走できたようで良かったです。 もしまた開催するとしたら、今度はもっとシュシュッと準備できるといいな。

参加者の皆様、お誘い頂いた id:potato4d さん並びに運営の皆様、ありがとうございました!

Atomのはてな記法モードを作ったよ!!

https://cloud.githubusercontent.com/assets/1403842/23390963/93f9712a-fdb4-11e6-82c3-67798c57e2f6.png

atom.io

はてなブログはてなグループ、増田に投稿する時に便利です。
どうぞご利用ください。

もくじ

機能

はてな記法シンタックスハイライトしてくれる

こんな感じ

https://cloud.githubusercontent.com/assets/1403842/23390963/93f9712a-fdb4-11e6-82c3-67798c57e2f6.png

Markdown文字列をはてな記法に変換できる

md2hatenaを利用した変換機能です。

language-hatena をインストールすると、Language Hatena: Convert Markdown To Hatena Syntax というコマンドが使えるようになります。
文字列を選択し、Cmd + P でコマンドパレットを表示して、 hatena とか打ち込んだら出てきます。
何も選択されていないときはファイル全体を変換します。

https://cloud.githubusercontent.com/assets/1403842/23490546/8d97a4bc-ff3c-11e6-8514-20af7e062710.gif

便利〜〜〜〜!!!!

スニペット使える

よく使うスニペットをいくつか定義しています。

f:id:amagitakayosi:20170304095644g:plain

現在使えるスニペットは以下のとおり。

  • code: スーパーpre記法
  • inlineCode: <code></code> を出力する。文中にコードを書きたい時に。
  • table: 表組み記法
  • image: [(画像URL):image] を出力する (http記法) 。
  • comment: <!-- --> を出力する

インストール方法

AtomのInstall Packages画面で検索してインストールしましょう。

f:id:amagitakayosi:20170304095022p:plain

もちろん apm でもインストールできます。

apm install language-atom

Atomで新しい言語のモードを作る方法

ここからは、Atomで新言語に対応するパッケージを開発する方法を解説します!

基礎知識

Atomlanguage-*** の作り方、公式ドキュメントにはまとまった情報がありません。
今回得た情報を要約するとこんな感じです。

言語モードはAtomのpackageとして開発する

Atomでは、ほぼ全ての機能がpackageとして実装されています。
言語の解釈は language-*** といった名前のpackageで行ないます。
これを language package と呼びます。

言語のパースは正規表現で行なう

language packageは正規表現を羅列した Grammar ファイルを持ちます。
これはTextmateで使われていたGrammarファイルと似た形式になっています。

TextmatemacOS向けのテキストエディタです。
日本では殆ど使われていませんが、英語圏ではGUIでつかえる入門向けエディターとして、かつて人気を博していました。

github.com

Textmateでは、言語のパースは正規表現で行われていました。
Grammarファイルに記法ごとの正規表現を記述し、文字列を keywordstring といったトークンに分解していました。

Atomでは、Textmateとよく似たGrammarファイルを利用して言語のパースを行ないます。
TextmateのGrammarファイルは XML 形式でしたが、Atomでは CSON 形式で記述します。

また、Atomのパッケージ管理ツール apm には、TextmateのGrammarファイルをAtomの形式に変換する機能が存在します。
Atomのlanguage packageの多くは、この変換機能を利用して作成されたようです。

Converting from TextMate

というわけで、language packageは正規表現をこねくり回して作ることになります。

実際の手順

今回の開発は、次の手順で行ないました。

  • apm init -l で雛形を作る
  • apm link して手元のAtomにインストール
  • grammars/ にGrammarファイルを書く
  • snippets/スニペットを追加
  • settings/ を編集
  • apm publish

色々と適当ですが、順番に解説していきます。

apm init -l で雛形を作る

apmAtomのパッケージ管理ツールですが、新たにパッケージを開発するためのテンプレート機能が存在します。
apm init --language (または-l) を実行すると、language packageの雛形が作成されます。

$ apm init -l hatena
$ tree language-hatena
language-hatena
├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── grammars
│   └── hatena.cson
├── package.json
├── settings
│   └── language-hatena.cson
└── snippets
    └── language-hatena.cson

3 directories, 7 files

apm link を実行すると、開発中のパッケージを手元のAtomにインストールできます(apm unlink で解除)。

$ cd language-hatena
$ apm link
/Users/amagitakayosi/.atom/packages/language-hatena -> /Users/amagitakayosi/language-hatena

Window: Reload コマンドでAtomをリロードすると、選択できる言語のリストに新しい言語が追加されたのが確認できるはずです。

grammars/ にGrammarファイルを書く

早速Grammarファイルを書いていきます。

Grammarファイルの書き方は複雑なので、いきなりまっさらな状態から書き始めるのはオススメしません。
既存のlanguage packageからよく似た言語をのGrammarファイルを元に書くことをオススメします。
はてな記法の構造はMarkdownとよく似ているため、今回は laguage-gfm のGrammarファイルを参考にしました。

Grammarファイルは以下のように書きます。

'scopeName': 'source.hatena'  # 言語の識別子
'name': 'Hatena Syntax'  # 言語の名前。言語セレクターとかで出てくる
'fileTypes': [  # 拡張子
  'hatena'
  'h'
]
'patterns': [  # 記法/トークンに対応する正規表現を書いていく
  {
    'match': '^[\\+\\-]+'  # 正規表現
    'name': 'markup.list.hatena'  # クラス
  }
]

patternsには、記法/トークンに対応するパターンオブジェクトを書いていきます。
ここでは リスト記法 に対応するパターンを記述しました。
name には、 match正規表現にマッチした文字列にあてるクラス名を指定します。

ご存知のとおり、Atomではエディター内のすべての要素がHTMLとCSSで出来ています。
シンタックスハイライト機能は、language packageがHTML要素にクラスを与え、テーマのpackageがスタイルを当てる、という仕組みで実現されています。
name で指定した文字列は . で分割され、コードを表現するHTML要素のクラスに設定されます。

今回の場合だと、以下の文字列は

- foo
- bar

次のようなHTMLに変換されます(簡略化してあります)。

<span class="markup list hatena">-</span> foo
<span class="markup list hatena">-</span> bar

パターンオブジェクトには、 match name の他にも begin end captures などのプロパティを指定できます。
それらの使い方については、実際のGrammerファイルを見たほうが速いでしょう。
https://github.com/atom/language-gfm/blob/master/grammars/gfm.cson

Grammarファイルの開発は、次のような手順で進めました。

1. patternsにパターンを一つ追加する
2. Window: Reload コマンド(Cmd + Alt + Shift + L)でAtomをリロード
3. おかしい所を直す
4. 2.3.を繰り返して、直ったら git commit

snippets/ にスニペットを追加

スニペットの追加は、普段 ~/.atom/snippets.cson を書くのと全く同じ方法で行えます。
めっちゃ簡単だし日本語情報も沢山あるので割愛。

Snippets

settings/ を編集

Atomでは Cmd + / で文字列をコメントアウトできますが、そのためには settings にコメントの記法を追加する必要があります。
language-hatena の場合はこんな感じ。

'.source.hatena':
  'editor':
    'softWrap': true
    'commentStart': '<!-- '
    'commentEnd': ' -->'
GitHubにpush

Atomパッケージを公開するには、GitHubにレポジトリを公開する必要があります。
レポジトリを作成したら git push して、 package.jsonrepository フィールドを追加してください。

{
  "name": "language-hatena",
  "version": "0.0.0",
  "description": "Syntax highlighting and snippets for Hatena Syntax (?????).",
  "repository": "https://github.com/fand/language-hatena",
  "license": "MIT",
  "engines": { "atom": ">=1.0.0 <2.0.0" }
}
apm publish

いよいよ公開です!
apm publish するとパッケージが atom.io に登録され、パッケージマネージャーからインストールできるようになります。

f:id:amagitakayosi:20170304095119p:plain

公開成功です!!!!

apm publish [更新の単位] を実行すると、パッケージのバージョン更新、gitのタグ作成、Atomへの公開まで apm が自動でやってくれます。
今回は更新の単位に minor を指定したので、バージョンは 0.0.0 から 0.1.0 に更新されました。

公開したら、実際にインストールできるか試してみましょう。
apm unlink で手元のパッケージをアンインストールし、Atomをリロードします。
Install Packages画面で検索すると、公開したパッケージが見つかるはずです。

f:id:amagitakayosi:20170304095022p:plain

お疲れ様でした!!!!🎉🎉🎉🎉🎉

参考リンク

Markdown→はてな記法変換ツール『md2hatena』を作った

Atomで変換する様子

github.com

Markdownで書いてしまった文章を増田に投稿したい方など、どうぞご利用ください。
ライセンスはMIT, テストカバレッジは C0 100% です。

インストール方法

$ npm install --global md2hatena

使い方

ターミナルから利用できます。

$ md2hatena foo.md > foo.hatena

拙作のAtomはてな記法モード language-hatena をインストールすると、コマンドパレットから実行することもできます。
(冒頭の画像)

Atomで変換する様子

JS用のライブラリとしても使えます。

import fs from 'fs';
import { md2hatena } from 'md2hatena';

const md = fs.readFileSync('foo.md', 'utf8');
md2hatena(md).then((hatena) => {
  console.log(hatena);
})

作ったメモ

今回は remark, meow といったツールを利用しました。

remark: Markdownパーサー

github.com

remark はMarkdown文字列をパースしたり処理するためのツールです。
内部的には remark-parseremark-stringify といったモジュールで構成されています。

今回は remark-parse を利用して Markdown文字列の AST (構文木) を取得し、node毎にはてな記法に変換しています。
ASTの仕様は syntax-tree/mdast というレポジトリにまとめられています。

Markdown で遊びたい人は使ってみると良いでしょう。

meow: CLIフレームワーク

github.com

CLI ツールを開発するときは、コマンドライン引数やオプションを扱うためのライブラリをよく使います。
Node.js だと minimist が一番有名です。

meow は minimist のラッパーです。
--help--version オプションを渡した自動で追加してくれます。
今回開発した md2hatena のような、単純な用途にはピッタリだと思います。


次回はAtomはてな記法モード language-hatena の紹介記事を書きます。
よろしく〜

2016年お焚きあげ / 2017年の抱負

もう1月終わっちゃったけど……

2月は仕事も生活も転機になる。 これからが本番ということで、今年の抱負を書いてみる。

2016年にやったこと

  • Kyoto.js復活
  • フロントエンドランチなど、会社でフロントエンド活動をやるようになった
  • GitHubに課金して、人生のいろんなタスクもGitHubで管理するようになった
  • ポモドーロテクニックを使うようになった

Kyoto.js復活

眠っていたKyoto.jsを復活させた。 京都でJS系の勉強会がなくて寂しく思っていたんだけど、会社の人が昔 Kyoto.js というのを知った。 2013年のKyoto.js #8以来とまっていたので、2年半ぶりの開催となった。

2016年は3回、2017年は既に1回開催している。 もうちょっとコミュニティのメンバーと相談しつつ、コンスタントに続けられたらいいなって思っている。

会社でフロントエンド会の活動を始めた

僕のいる会社は、中くらいの規模のWebサービス屋さんだ。 エンジニアの人数はあまり多くなく、大体みんなフロントからバックエンドまで全部見る、という運用になっている。

知っての通り、フロントエンド開発はどんどん複雑になってきてるけど、社内でのフロントエンドについての会話は、たまに飲みながら会話したりするくらいだった。 もっと社内でフロントエンドに関心をむけてほしい、知見を集めたいと思って、定期的に集まるイベントを作った。

活動内容はこの記事に書いたとおり。 http://developer.hatenastaff.com/entry/2016/12/16/120000

最近KPTをやったり、社内の人と相談したりした。 具体的な目標はまだ決めてないけど、以下2点に気をつけて活動していこうと思っている。

  • フロントエンド会の目標を立てて、組織的に動く
  • フロントエンドランチに参加していない社員ともコミュニケーションする

なんでもGitHubで管理する

f:id:amagitakayosi:20170203200053p:plain

2016年は、仕事ともプログラミングとも関係ない活動をすることが多かった。 活動するにあたって、スケジュールを考えたり、TODOリストを作ったり、そういった諸々を管理する必要があるんだけど、仕事で散々GitHubを使っている僕はどうしてもGitHubを使いたくなる。 世の中の人達はGoogle SpreadsheetとかLINEのノートを使ったりするらしいけど、GitHubが便利すぎるので、ついにGitHubに課金することにした。

自分の名前のプライベートレポジトリを作って、とにかくなんでもissueを立てるようにしている。 GitHubアカウントを持っている人なら招待できるし、最近ではProject機能なんかも追加されて、カンバン管理もできる。

惜しむらくはGitHubアカウントを持っていないと使えないこと。 TwitterアカウントでGitHubにログインできるようになるといいなあ……。

ブログ活動

2016年の記事のブクマランキング

# タイトル
1位 React のソースコードを読んでみよう! - マルシテイア
2位 『さよなら、インターフェース』を読んで、スクリーンの無い未来について妄想した - マルシテイア
3位 macOSがダサいかは知らないが僕は「を」を綺麗にかけない - マルシテイア
4位 Yarn速かった - マルシテイア
5位 CLIから青空文庫を閲覧するツール書いた - マルシテイア
6位 自分が依存しまくってるnpmパッケージを表示するツールつくった - マルシテイア
7位 FRONTEND CONFERENCE 2016で、Reactコミュニティの動きについて発表してきた - マルシテイア
8位 Slackのファイルアップロード機能をTODOリストとして使う - マルシテイア
9位 npm事件対応メモ - マルシテイア
10位 エンジニア立ち居振舞い「自分の担当範囲を意識する」 - マルシテイア

generated by 年間ブックマークランキングジェネレーター

去年は後半めっきりブログを書かなくなってしまった。 ブログを書かないと、その他の対外的なコミュニケーションも難しくなってしまう気がする。

技術的に有用な記事じゃなくてもいいから、日常思ったことを、もうすこし頻繁に書くようにしたい。

2017年の抱負: 切り替えをがんばる

2016年に色々始めたことで、自分の時間が食いつぶされがちになっている。 今やっていることを続けつつ、気持ちに余裕を作ったり、新しいことをやったりするためには、今やってることの効率を良くする必要がある。

仕事も頑張りたいし、プライベートももっともっと充実させたい。

配属かわったので頑張る

2月から、これまでのチームを離れ、B2Bサービスのチームに配属になった。 新しいチームはスクラムを元にしたシステムで回っていて、なんというか、明文化された仕組みにもとづいてイシューをこなしていくというのが初めてなので、楽しみにしている。

技術スタックも、これまでは Perl + JS + React だったのが Scala + Go + TypeScript + AngularJS となる。 去年は結局新しい言語を勉強できなかったけど、今回は半ば強制的に覚えることになる。 これだけで技術的な好奇心が満たされてしまいそうな気がするが、引き続きフロントエンドの流行を追いかけていきたい。

やり方を変えていく

去年は色々な事を始めたけど、Kyoto.jsももフロントエンド会も、まだまだ改善点がいっぱいある。 より活動としていくために、今年は継続的にやり方を改善していきたい。 どれも自分の思いつきで進めているようなところがあるが、手伝ってくれる方々ともっとコミュニケーションして、うまく続けていける仕組みを整えていきたい。 (JSer.infoの作り方の発表は、仕組みを作っていく姿勢という点で衝撃だった……)

個人活動を増やす

去年は土日はしっかり遊んでたけど、平日になんとなく仕事してしまったりとか、Podcastの編集を夜中までやってしまって、気付いたら仕事と遊びしかしてないという状況になることがあった。 エンジニアとして成長するためにも、今後のキャリアの可能性を広げるためにも、形になる個人活動を増やしたいと思う。 まだどういう活動をするかは考えてないけど、やりたいことは無限にある。

そのためには、自分の時間を上手く管理して、公と私とをちゃんと切り替えるようにしなければ。

英語

他の目標に比べると大分具体的になってしまうけど、英語を勉強していきたい。

ふだんから、技術情報を得るために勉強しなきゃって思ってたけど、タイに旅行に行った時に結構苦労して、危機感を覚えた。 海外旅行はたのしいので、これからいろんな所に行くためにも勉強しないとな。


今年もどうぞ、よろしくおねがいします。

React のソースコードを読んでみよう!

f:id:amagitakayosi:20161207035726p:plain

こんにちは id:amagitakayosi です。
株式会社はてなで主にフロントエンド開発を担当しています。

この記事では React 本体のコードを読んでみます!!

この記事は Reactアドベントカレンダー 2016 の7日目の記事です。
昨日は yutaszk さんで「react-router v4 でFlux アプリケーションをHot Module Replacement する」でした。

目次

はじめに (Kyoto.js の宣伝)

僕は Kyoto.js というコミュニティを運営しています。
Kyoto.js は京都界隈の JavaScript 開発者のためのコミュニティです。
(実際は京都以外のメンバーのほうが多い……)

これまで Kyoto.js では3,4ヶ月おきに勉強会を開催してきましたが、もっと気軽に交流したい!活動を増やしたい!ということで、先月からオンラインコードリーディング会をすることにしました。

コードリーディングは1,2週間おきに1度のペースで、Google hangoutを使って開催しています。
興味のある方は是非 Slack からご参加ください!

準備

github.com

ここでは、 facebook/react のレポジトリの構造について簡単に説明します。

React の処理の概要やディレクトリ構成については、公式のドキュメントである程度解説されています。
英語が苦でない方は読んでおくと良いでしょう。

まずはレポジトリをcloneしてきましょう。
cloneしたら、npm installなどの準備もしておきます。

# まずはクローン
git clone https://github.com/facebook/react

# yarnをつかって依存パッケージをインストール
npm install -g yarn
yarn

# react.js 等をビルド。エラー出るかもしれないけど大丈夫!
npm run build

いくつか注意点!

  • 本記事では 2be0d93c 時点でのコードを参照します。
  • 行番号は (L123) のように表します。

ディレクトリ構造

まずは、レポジトリがどんな構造になってるのか見てみましょう。
ディレクトリ構造は以下のようになっています(面白いとこだけ抜粋)。

react/
├─ src/
│   ├─ addons/
│   ├─ isomorphic/
│   │   ├── React.js
│   │   ├── children/
│   │   ├── classic/
│   │   ├── hooks/
│   │   └── modern/
│   ├─ renderers/
│   │   ├── art/
│   │   ├── dom/
│   │   ├── native/
│   │   ├── noop/
│   │   ├── shared/
│   │   └── testing/
│   └─ testing/
├─ build/
├─ grunt/
├─ gulp/
├─ packages/
├─ Gruntfile.js
├─ gulpfile.js
├─ package.json
└── yarn.lock

monorepo

package.jsonを見ると、このレポジトリ自体のパッケージ名は react-build となっています。
また、 packages/ を見ると、複数のnpmパッケージのpackage.jsonが置かれています。

このように、 React では一つのgitレポジトリで複数のnpmパッケージを管理する構成をとっています。
このような構成のレポジトリは monorepo と呼ばれています。 monorepo は他にも Babel や Angular などの大規模なプロジェクトで採用されています。
また、 monorepo を管理するための lerna というツールも存在します。
monorepo を採用することで、 複数のnpmパッケージで重複するコードを管理しやすくなったり、デバッグが容易になるといったメリットがあるようです。

Haste

src/ 以下のファイルを眺めると、 require('ReactMount') のように、ファイル名べた書きの不思議な require に出くわします。
これは Facebook 内製の Haste というモジュールシステムによるものです。

Haste では、 全てのファイル名をユニークにする という制約を設けています。
ビルド時には、browserify の前段で全てのファイルを lib/ 直下にコピーすることで、 browserify が require() を解決出来るようになっています。

React の src/ はかなり複雑な構造になっていますが、ファイル名がユニークであることで、モジュール名さえわかっていればエディタのfuzzy finderを使って簡単にファイルを開ける、といったメリットがあるようです。

Haste のコードが Facebook 内部でどうやって管理されているのかは分かりませんが、 React においては、この辺で require() しているスクリプトで同様の機能を実現しているようです。
https://github.com/facebook/react/blob/2be0d93c7782eb2dad62efcac9668152da715c25/gulpfile.js#L18-L20

Gulp と Grunt

gulpfile.jsGruntfile.js が両方存在するのに気づきます。
Issueにもある通り、 Grunt から Gulp へ移行途中ということみたいですね。
2013年時点のコード を見てみると、まだ Grunt だけ使っていることがわかります。

Gruntfile の初っ端から gulp を呼ぶための関数を定義しており、涙ぐましい……。
タスクごとに移行していくというのはスマートですね。
大規模なプロジェクトで Grunt から Gulp への移行を検討している方は真似してみても良いかもしれません。

コードリーディング

それでは、早速コードを読んで見ましょう!
今回は React アプリケーションの初期化まわりを探ることにします。
具体的には、 React.render() により React コンポーネントが初期化され、DOM 要素へマウントされるまでの処理を追うことを目標とします。

src/ 以下のファイルは、主に addons isomorphic そして renders の3つに分類されています。
React のコア部分が isomorphic に、 react-domreact-native 等環境に依存したコードが renderes/ に入っています。

react

まずは react パッケージのコア部分から攻めてみましょう。

react パッケージの本体は src/isomorphic/React.js です。
require('react'); した時に export される API が定義されています。

https://github.com/facebook/react/blob/2be0d93c7782eb2dad62efcac9668152da715c25/src/isomorphic/React.js#L54-L92

APIModern Classinc に分類されています。
Modern の方には Component や PureComponent といった ES2015 Class 前提のモジュールがあり、 Classic には createClass や mixin といった懐かしい単語が並んでいますね。

特に重要なのは ReactComponentReactElement でしょうか。

ReactComponent

ソース: src/isomorphic/modern/class/ReactComponent.js

ReactComponent は、我々通常の React ユーザーがコンポーネント作成時に extend するクラスです。
このファイルでは setState forceUpdate が定義されています。
いずれのメソッドでも、 this.state を直接操作したりせず、 this.updater に処理を enqueue するにとどまっています。

下の方で if (__DEV__) としているのは、開発環境で警告を出すためのコードです。
React は非常に丁寧にの警告やエラーを出してくれますが、コードを読んでいると至る所に warning invariant といった警告、エラーの為の処理が挟まれています。
これらの関数は facebook/fbjs 内で定義されています。
fbjs は Facebook 内製のツール群をまとめたレポジトリです。
https://github.com/facebook/fbjs/blob/e66ba20ad5be433eb54423f2b097d829324d9de6/packages/fbjs/src/forks/warning.js

話が逸れました。
さて、 updater はデフォルトでは ReactNoopUpdateQueue が渡されるようですが、これは名前の通り何もしない updater です。
実際に render するときは、適切な updater が渡されるのでしょうか?

git grep updater src でそれらしい箇所を探すと、 ReactCompositeComponent の mountComponent メソッドで updateQueue を渡しているのが見つかりました。

https://github.com/facebook/react/blob/2be0d93c7782eb2dad62efcac9668152da715c25/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js#L197

mountComponent はHTML文字列を作って返すメソッドです。
後述しますが、ReactCompositeComponent は ReactComponent に対応する内部表現オブジェクトのクラスであり、ReactComponent の子クラスとかでは ありません
mountComponent でコンポーネント毎の HTML 文字列を生成し、どこかで組み立てているようですが……?

react-dom

今度は react-dom のコードを見てみましょう。
react-dom の本体は src/renderers/dom/ReactDOM.js にあります。
React.js 同様、 require してAPIを公開してるだけですね。

https://github.com/facebook/react/blob/2be0d93c7782eb2dad62efcac9668152da715c25/src/renderers/dom/ReactDOM.js#L31-L41

(unmountComponentAtNode なんて出来たんですね……知らなかった)

ReactDOM.renderReactMount で定義されているようです。

ReactMount

ソース: src/renderers/dom/stack/client/ReactMount.js

ReactMount は Mounting に関する処理を行うモジュールです。
Mounting とは、 React コンポーネントを render し、 container 要素内に DOM ツリーを挿入する処理のことです。

// ReactMount.jsより抜粋
ReactMount.render(
  component,
  document.getElementById('container')
);
<div id="container">       <-- Supplied `container`.
  <div data-reactid=".3">  <-- Rendered reactRoot of React component.
    // ...                              
  </div>
</div>

render メソッドの定義は以下のようになっています。

  /**
   * @param {ReactElement} nextElement Component element to render.
   * @param {DOMElement} container DOM element to render into.
   * @param {?function} callback function triggered on completion
   * @return {ReactComponent} Component instance rendered in `container`.
   */
  render: function(nextElement, container, callback) {
    return ReactMount._renderSubtreeIntoContainer(null, nextElement, container, callback);
  },

ReactElement は、type props といったプロパティをもつ Plain なオブジェクトです。
多くの場合は JSX から生成し、 render() で return したりします。
JSX が苦手な方は手動で React.createElement() してるかもしれませんね。
(参考: https://facebook.github.io/react/docs/introducing-jsx.html)

_renderSubtreeIntoContainer は次のような処理をしています。

  • nextElement のラッパーを作成 (L464)
  • _renderNewRootComponent() で取得した ReactComponent のインスタンスを返す (L528)

_renderNewRootComponent の中身はこんな感じ:

  • instantiateReactComponent()nextElementインスタンス化 (L382)
  • ReactUpdates.batchedUpdates() を呼んで、取得した ReactComponent インスタンスを DOM ツリーにマウント (L394)。

「ReactUpdates って何やねん」という感じですが、今は気にしなくて良いです。

batchedUpdates() の引数を辿っていくと、mountComponentIntoNode に行き着きます (L94)。
この関数では、 ReactReconciler.mountComponent()markup を作って _mountImageIntoNode() に渡しています。
_mountImageIntoNode では setInnerHTML したりしてるので (L726)、 markup は HTML 文字列とみていいでしょう。

今度は ReactReconciler を読んで、 markup がどのように作られているか探って行きましょう。

ReactReconciler

ソース: src/renderers/shared/stack/reconciler/ReactReconciler.js

ReactReconciler は一言で言うと「ReactElement、ReactComponent、DOM要素、内部表現をまとめる者」です。

reconcile /rék(ə)nsàɪl/
調和させる, 調整する, 一致させる «with» ; 〈銀行明細など〉と帳尻を合わせる

先ほど「ReactCompositeComponent は ReactComponent に対応する内部表現」と説明しました。
React 内部では、 ReactComponent など render() を持つオブジェクトを public instance , ReactCompositeComponent などの内部表現を internal instance と呼んでいます。

さらに分類すると以下のようになります (ドキュメントを参照)。

internal instance は、対応する public instance や ReactElement への参照を持ちます。
また、 componentDidMount など public instance の持つライフサイクルを呼び出す役目を担っています。

以上を踏まえて ReactReconciler.mountComponent を眺めてみます。
第1引数は internalInstance という名前になっていて分かりやすいですね。 *1
internalInstance.mountComponent() で HTML 文字列を生成し、 refs などの処理をしてから返しています。

となると、今度は internal instance のクラスを読めばいいことがわかります。
composite component, host component の順番に読んでいきましょう。

ReactCompositeComponent

ソース: src/renderers/shared/stack/reconciler/ReactCompositeComponent.js

ファイルを開くといきなり良い図がでてきます。
この図では、public instance のライフサイクルメソッドや render が実行される順序が示されています。
以下転載 (ソース)。

/**
 * ------------------ The Life-Cycle of a Composite Component ------------------
 *
 * - constructor: Initialization of state. The instance is now retained.
 *   - componentWillMount
 *   - render
 *   - [children's constructors]
 *     - [children's componentWillMount and render]
 *     - [children's componentDidMount]
 *     - componentDidMount
 *
 *       Update Phases:
 *       - componentWillReceiveProps (only called if parent updated)
 *       - shouldComponentUpdate
 *         - componentWillUpdate
 *           - render
 *           - [children's constructors or receive props phases]
 *         - componentDidUpdate
 *
 *     - componentWillUnmount
 *     - [children's componentWillUnmount]
 *   - [children destroyed]
 * - (destroyed): The instance is now blank, released by React and ready for GC.
 *
 * -----------------------------------------------------------------------------
 */

public instance の render が再帰的に呼ばれることがわかりますね。

mountComponent の処理の流れは次のようになっています。

  • public instance を生成 (L201-L262)
  • componentWillMount を呼ぶ (L336-L351)
  • markup を生成 (L353-L370)
  • componentDidMount を呼ぶ (L372-L384)

markup の生成は performInitialMount で行なわれます。 こちらは次のような流れ。

  • renderedElement を作る (L516)
  • renderedElement から子供の internal instance を生成 (L521)、
  • ReactReconciler.mountComponent を呼ぶ

renderedElement には public instance の render() が返す ReactElement が入ります (L516, L1184, L1153 の順で呼ばれる)。
この ReactElement は this._currentElement の子要素にあたります。

すなわち、 ReactReconciler.mountComponent()render()再帰的に呼び続ける構造になっています。
React のツリーの末端は必ず DOM 要素などの host component となるので、そこで処理が止まるようです。

ReactDOMComponent

ソース: src/renderers/dom/stack/client/ReactDOMComponent.js

ブラウザ環境での host component 実装は ReactDOMComponent にあります。
ReactHostComponent というのもありますが、これらは継承関係ではありません。 *2

mountComponent を読んでいくと……ありました!
ReactElement の type から document.createElement() したり、 HTML 文字列を生成したりしています! (L569, L604)
前者の場合、 _createInitialChildren を呼ぶことで DOM 要素に子要素を挿入しています。

これでようやく ReactDOM.render() 時の DOM 生成が完了しました!

まとめ

初期化処理をなぞっただけでも、沢山の登場人物がでてきました。
今回でてきた主な登場人物は以下のとおり。

  • ReactElement
  • public instance (ReactComponent)
  • internal instance
    • composite component (ReactCompositeComponent)
    • host component (ReactDOMComponent)
  • reconciler (ReactReconciler)

他にも updater や transaction が出てきましたが今回は割愛します。

ReactDOM.render() した時の処理の流れは、ざっくりいうと以下のとおりです。

  • ReactMount.render() を呼び
  • ReactMount._renderSubtreeIntoContainer() で一番上の ReactComponent をインスタンス化し
  • ReactReconciler.mountComponent()render()再帰的に呼び
  • ReactDOMComponent.prototype.mountComponent で DOM 要素を生成する

おわりに

後半箇条書きばっかりになってしまった 😇
React ユーザーの方もそうでない方も、この記事で React の実装に興味を持っていただけたら幸いです。

よろしければ Kyoto.js Slack も覗いてみてください!

それでは!!


この記事は Reactアドベントカレンダー 2016 の7日目の記事です。
明日は chimame さんで「Webpack依存のReactコンポーネントをテストする」です。

*1:コメントでは ReactComponent となっていますが、多分間違いだと思います

*2:ドキュメントで解説されています https://facebook.github.io/react/contributing/codebase-overview.html#dynamic-injection

กระต่ายหมายจันทร์

タイで4日間過ごした。

知らない文字に囲まれて、本当に着くのかと不安になるバスに乗ったりしてた。 川と海と高層ビルの隙間に、水色と緑とピンクと金と、土色と、あと無数に吊るされた黒い電線の中を、フワフワさまようような旅だった。

経緯

旅行が決まったのは3ヶ月前。2,3日のうちにエクスペディアで航空券とホテルを予約して、あとへ引けない状況になった。 その後、GitHubのprivateレポジトリでissueを立てて、一日ごとのスケジュールとか、移動手段を検討していった。

f:id:amagitakayosi:20161201005800p:plain

SIMカードとかGrabとかの情報を恋人がどんどん収集してくれてメチャクチャ助かった……!

お役立ち情報

ホテル

Amari Watergate Bangkok というホテルに泊まった。

アマリウォーターゲートバンコク - プラトゥーナム地区にあるバンコクのホテル

エクスペディアで施設と場所を見て決めたんだけど、あとで調べると日本語サイトがあったり、日本人スタッフが居たりした。 日本人スタッフの人は調べ物やお店の予約などを手伝ってくれて凄く助かった。 部屋も綺麗で過ごしやすく、はじめてタイに行く人にはオススメできる。

SIMカード

SIMカードはtrueのものを利用。 ドンムアン空港のカウンターは6:00開店とのことだったけど、5:40ごろには開いていた。 店員のお姉さんはずっと髪を編み込みながら話してたが、無事LTEのSIMを入手。 4日間で7GB、意外と充分だった。

Grab

ドンムアン空港からバンコクまでは Grab を利用。 東南アジアで展開してるUberみたいなやつ。

https://www.grab.com/

都市部の移動は大体これか、メータータクシーを利用した。

タクシー

「METER TAXI」って書いてあるけど実際メーター使わないタクシーもあるので注意。 乗る前に確認しよう。

ぼったくり

タイ旅行で「気をつけろ」と言われるのがぼったくり。

今回はタクシー、バス、船で移動したけど、意外とボラれることは無かった。 タクシーでちょっと高めに取られることはあったけど、日本円で数十円程度だし、まあいいか、という感じ。 一番交渉が大変そうなトゥクトゥク、ソンテオには乗らなかったので、その辺は分からない。

地球の歩き方やブログでみた「ワットポーはは今日やってないよ!」おじさんは存在した。 ワットポーへ向かう王宮近くの道で遭遇。 その日は前国王の弔いイベントの日で、王宮を見るのは諦めていたので、王宮への道を塞がれても困りはしなかったが、強引に宝石店に連れて行かれることもなく、おじさんの目的は不明。

食事

うまい!!!!!!!!!!!! 屋台のパッタイが美味しすぎた

  • 英語通じなくてもジェスチャーと笑顔で頑張ろう
    • 都会のお店だと英語が通じる
  • 屋台が沢山あるので、ちょっとずつつまみ食いして歩くのもよさそう
  • 食費は店によって全然違うけど、だいたい日本の 1/3 〜 1/2程度
    • 瓶ビールが 80バーツ (240円) くらい
    • 屋台だとパッタイ一人前 40バーツ (120円)
    • アユタヤで名物のエビ食べたら 1600バーツ (4800円) だった

コンビニ

  • セブンイレブンがどこにでもある
    • まれにファミマもある
  • プリッツ ラーブ味」とかある
    • 空港のお土産屋さんとかにはないので、早めに買っておこう
  • レッドブル系のスタミナドリンク、なぜか炭酸ない

以下、写真


電車もバスもタクシーも乗り方がわからないので、拙い英語で話しかけまくったり、とかく行動力になって強くなった気がしたんだけど、帰ってきたらどこか遠い国の出来事なんだよなあ。

帰国から1週間たって、良かった記憶だけが残っている。