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