Create a gist now

Instantly share code, notes, and snippets.

ReactのCodebase Overview, Implementation Notes要約

Codebase Overview

https://facebook.github.io/react/contributing/codebase-overview.html

Custom Module System

  • FacebookではHasteというモジュールシステムを使っている
    • all filenames are globally unique
    • require('../foo/bar') の代わりに require('bar') とする
    • ファイル移動しても壊れない
    • Fuzzy Finder使いやすい
  • ビルド時に、全てのファイルを /lib 下にフラットに配置する
    • require の引数に ./ を追加する
    • 結果、CommonJSで解釈できるようになる

External Dependencies

  • fbjs に依存してるよ

Top-Level Folders

  • src: 本体
  • docs: https://facebook.github.io/react のソースコード
  • examples
  • packages : npmパッケージのメタデータ置き場 (package.json 等)。ソースコードは src にある
  • build: ビルド済みファイル置き場

Colocated Tests

  • テストファイルはテストしたいファイルのすぐ近くにある
    • 例) src/addons/update.js のテストは src/addons/__tests__/update-test.js

Shared Code

  • 基本的に、同じディレクトリ or 子ディレクトリのファイルのみimportする
    • 循環参照を避けるための規約
  • 2つのディレクトリで共有したいコードがある場合、共通の親ディレクトリに shared を作って、そこに置く

Warnings and Invariants

  • fbjsのwarning, invariantをよく使う
  • warning: 第一引数がfalseの時、警告する
    • developmentでのみ有効
    • 警告の嵐をさけるため、警告と同時にフラグを立てることがよくある
  • invariant: 第一引数がfalseの時、エラーを吐いて死ぬ
    • productionでも有効
    • productionのときはエラーメッセージじゃなくてエラーコードが出る(ファイルサイズ削減のため)

Development and Production

global.__DEV__process.env.NODE_ENV !== 'production' の代わりに使える

JSDoc

  • 昔のコードで使ってた。型チェックはしてない

Flow

  • 新しいコードではflowtypeを使ってる
  • 型つけるPullReqは絶賛受付中!

Classes and Mixins

殆どのコードはまだES5で書かれてる

Mixin

// Constructor
function ReactDOMComponent(element) {
  this._currentElement = element;
}

// Methods
ReactDOMComponent.Mixin = {
  mountComponent: function() {
    // ...
  }
};

// Put methods on the prototype
Object.assign(
  ReactDOMComponent.prototype,
  ReactDOMComponent.Mixin
);

module.exports = ReactDOMComponent;
  • ここでいう Mixin は React の mixins とは無関係
    • 他のクラスにMixinするのに便利で使ってた
  • ES6 Classに書き直したりもするけど、優先度は低い
    • React Fiber で、あまり OOP っぽくないコードに置き換わる見込み

Dynamic Injection

  • DOMやReactNativeなど、環境による違いを吸収するため、実行時に依存クラスをinjectしてるコードがある
// Dynamically injected
var textComponentClass = null;

// Relies on dynamically injected value
function createInstanceForText(text) {
  return new textComponentClass(text);
}

var ReactHostComponent = {
  createInstanceForText,

  // Provides an opportunity for dynamic injection
  injection: {
    injectTextComponentClass: function(componentClass) {
      textComponentClass = componentClass;
    },
  },
};

module.exports = ReactHostComponent;

この例だと、次のようにinjectされる

  • DOM: ReactHostComponent.injection.injectTextComponentClass(ReactDOMTextComponent);
  • ReactNative: ReactHostComponent.injection.injectTextComponentClass(ReactNativeTextComponent);

将来Dynamic Injectionやめてビルド時に良い感じにしたいけど、まだできてない

Multiple Packages

  • Reactはmonorepo
    • 複数のnpmパッケージを1つのgitレポジトリで管理
    • ref. lerna

React Core (src/isomorphic)

  • 以下のAPIだけ提供する
    • React.createElement()
    • React.createClass()
    • React.Component
    • React.Children
    • React.PropTypes
  • reconciliation や環境依存のコードはない
    • v15.4.0 で react.js から react-dom のコードが削除された

Renderers (src/renderers)

react-artというのも一応公式rendererなので、reactのリポジトリに組み込まれている

Reconcilers

  • 環境に依存しない reconciliation 処理をおこなう
    • render を呼んだり、lifecycle methodsを呼んだり、refsを管理したり
  • no pubic APIs
    • rendererから呼ばれる

Stack Reconciler (src/renderers/shared/stack/reconciler)

  • "internal tree" とReactコンポーネントを管理する
  • internal instance は composite と host の2種類がある
  • internal instance は外部からは触れない

Host Components

  • プラットフォーム毎に提供されるもの
    • <div><View> など
  • ReactDOMComponent, ``
  • mounting, updates, unmounting を行う

Composite Components

  • ユーザー定義のもの
  • ReactCompositeComponent
  • mounting, updates, unmounting を行う
  • update時の処理
    • 新しい render() 結果の type, key を調べる
    • 変化がない場合: 子供の render() を呼ぶ
    • 変化がある場合: 子供を unmount して新しいのを mount

Recursion

  • renderは再帰的に行われる
    • シングルパス、同期的

Fiber Reconciler (src/renderers/shared/fiber)

  • reconciler書き直し計画
  • 目的 ー 処理をチャンクに分割して割り込みできるようにする ー チャンクに優先度つけて、一時停止とかできるように ー 親子間を行ったり来たりして処理できるようにする、レイアウトを考慮して処理する ー render() で複数の要素を返せるようにする ー エラー境界?のより良いサポート

詳細: https://github.com/acdlite/react-fiber-architecture

Event System (src/renderers/shared/shared/event)

  • React は生のDOMイベントではなく、自前で実装した synthetic event を利用する
    • renderer 非依存

Add-ons (src/addons)

  • TransitionGroup, CSSTransitionGroup: アニメーションとか
    • src/addons/transitions
  • createFragment: 子コンポーネントの配列にkeyを設定しづらいときに使う
    • src/addons/ReactFragment.js
  • Perf: パフォーマンス計測用
    • src/renderers/shared/ReactPerf.js
  • ReactTestUtils: テスト用
    • src/test/ReactTestUtils.js

その他一覧: https://facebook.github.io/react/docs/addons.html

Implementation Notes

https://facebook.github.io/react/contributing/implementation-notes.html Stack Reconciler (src/renderers/shared/stack/reconciler) についての解説

動画: https://www.youtube.com/watch?v=_MAD4Oly9yg

用語

  • React element: Plain Object。typeとpropsを持つ
  • User-defined components は render すると React element になる
  • "Mounting" = トップレベルのReact elementを受け取り DOM or Native要素を生成する再帰的なプロセス

Mounting Host Elements

  • composite element も、render していくと host element になる
  • reconciler は、ツリーのどこかで host element が返って来ると、その要素の mounting 処理を renderer に任せる
  • 子要素があれば mounting をつづける
  • "mount image" = mounting で生成された結果

Introducing Internal Instances

update / rerender するためには、 tree の情報や render 結果への参照などを保持する必要がある

  • composite internal instances のプロパティ
    • currentElement
    • publicInstance (element が class の場合)
    • renderedComponent: DOMComponent or CompositeComponent
  • host internal instances のプロパティ
    • currentElement.
    • node.
    • renderedChildren: DOMComponent[] or CompositeComponent[]

Unmounting

  • ReactDOM.unmountComponentAtNode(containerNode)するには
    • containerNode.firstChild._internalInstance.unmount() して
    • containerNode.innerHTML = ''; する
  • 最初の mountTree 時に node._internalInstance = rootComponent; する

Updating

  • DOMComponent, CompositeComponent に receive(nextElement) を実装する
    • internal instanceに更新があればDOMを新しくする
    • nextElement: 新しくpublicInstanceの render() で帰ってきた React element ?

Updating Composite Components

  • nextElementをrenderする
  • renderした結果 (子) のtypeと、以前の子のtypeを比較
    • 同じ: 以前の子internal instanceのreceivedを呼ぶ
    • 違う: 子をunmountしてmountし直す
      • var prevNode = prevRenderedComponent.getHostNode(); して
      • unmount, rerenderして
      • prevNode.parentNode.replaceChild(nextNode, prevNode); する
  • getHostNode : 一番近いhost elementのDOMNodeを得るメソッド

Updating Host Components

  • propsのうち、古いattributeを削除し、新しいattributeをDOM Nodeに追加
  • childrenをループして、ADD, REPLACE, REMOVEが必要な要素をキューにいれる
  • キューを順番に実行

Top-Level Updates

containerNode で if (prevElement.type === element.type) { prevRootComponent.receive(element); } とすれば、React.renderの結果のDOMを再利用できるようになる

What We Left Out

以上の解説はあくまで擬似的なもの。本当は以下を考慮する

  • render() が null を返すとき
  • array 中の null
  • 要素の key
  • composite, host に加え text, empty が存在する
  • host 要素は、rendererからreconcilerに inject される
  • ReactMultiChild
  • setState()
    • イベントハンドラ内で複数回setStateしたら、まとめてupdateされる
  • refs
  • lifecycle hooks
    • "callback queues" でまとめて実行される
  • transaction

Jumping into the Code

  • ReactMount: トップレベルコンポーネントのmount, unmountを行う
  • ReactDOMComponent DOM renderer における host component 実装
  • ReactCompositeComponent: composite component 実装
  • instantiateReactComponent: host, component等のうち適切なコンポーネントをinstantiateする
  • ReactReconciler: mountComponent, receiveComponent, unmountComponentメソッドを持つ
  • ReactChildReconciler: children の key を考慮して mounting, updating, unmounting を行う
  • ReactMultiChild: children の操作をキューを用いて行う
  • mountComponent(), receiveComponent(), unmountComponent() の引数はホントは React element
    • "component" って言ってるのは歴史的経緯
  • internal instanceのプロパティは _ から始まる (例: _currentElement)
    • "read-only public fields"

Future Directions

  • 現在のStack reconcilerの処理は同期的で割り込み出来なくてこまる
  • Fiber reconcilerが開発中
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment