Open10

React coreの実装を読みたい

ta1m1kamta1m1kam

Prerequisites
I strongly suggest that you are familiar with the following resources before continuing:

React Components, Elements, and Instances - "Component" is often an overloaded term. A firm grasp of these terms is crucial.
Reconciliation - A high-level description of React's reconciliation algorithm.
React Basic Theoretical Concepts - A description of the conceptual model of React without implementation burden. Some of this may not make sense on first reading. That's okay, it will make more sense with time.
React Design Principles - Pay special attention to the section on scheduling. It does a great job of explaining the why of React Fiber.

らしいので、読む

https://reactjs.org/blog/2015/12/18/react-components-elements-and-instances.html
→ スクラップにまとめた
https://reactjs.org/docs/reconciliation.html
→ スクラップにまとめた
https://github.com/reactjs/react-basic
https://reactjs.org/docs/design-principles.html

ta1m1kamta1m1kam

React Components, Elements, and Instances

Managing the Instances

traditionl UI model

  • 各コンポーネントのインスタンスはDOMノードと子コンポーネントのインスタンスへの参照を保持し、適切なタイミングで作成・更新・削除が必要
  • 親から子へインスタンスへの直接アクセスをするため切り離しが困難

Elements Describe the Tree

ElementはコンポーネントのインスタンスまたはDOMノード(buttonとか)とそのプロパティ(色とか)を記述したObject
→ 画面表示したいものをReactに伝える

DOM Elements


HTML

<button class='button button-blue'>
  <b>
    OK!
  </b>
</button>

{
  type: 'button',
  props: {
    className: 'button button-blue',
    children: {
      type: 'b',
      props: {
        children: 'OK!'
      }
    }
  }
}

type:string、タグとしてのDOMのノード
props:属性
Reactの要素はtraverseがかんたんで、実際のDOM要素より軽い → 単なるオブジェクトなので

note
Traverse: DOMツリーを上・下で様々な処理をすること

Component Elements

typeはfunctionやclassであってもよい

{
  type: Button,
  props: {
    color: 'blue',
    children: 'OK!'
  }
}

これが Reactのcore の考え

componentもelementも混在させることができる
これにより、componentは互いに切り離され、is-aとhas-a関係を表現できる

Components Encapsulate Element Trees

Reactはcompoenntになんのelementをrenderすればよいか尋ねて、それを底のDOMタグまで繰り返す。

React componentはpropsが入力でElementツリーが出力なだけ。

const Form = ({ isSubmitted, buttonText }) => {
  if (isSubmitted) {
    // Form submitted! Return a message element.
    return {
      type: Message,
      props: {
        text: 'Success!'
      }
    };
  }

  // Form is still visible! Return a button element.
  return {
    type: Button,
    props: {
      children: buttonText,
      color: 'blue'
    }
  };
};

Reactがインスタンスの生成・更新・破棄等のインスタンス管理をする。

Components Can Be Classes or Functions

以下の3つは全部ほぼ同じ。

// 1) As a function of props
const Button = ({ children, color }) => ({
  type: 'button',
  props: {
    className: 'button button-' + color,
    children: {
      type: 'b',
      props: {
        children: children
      }
    }
  }
});

// 2) Using the React.createClass() factory
const Button = React.createClass({
  render() {
    const { children, color } = this.props;
    return {
      type: 'button',
      props: {
        className: 'button button-' + color,
        children: {
          type: 'b',
          props: {
            children: children
          }
        }
      }
    };
  }
});

// 3) As an ES6 class descending from React.Component
class Button extends React.Component {
  render() {
    const { children, color } = this.props;
    return {
      type: 'button',
      props: {
        className: 'button button-' + color,
        children: {
          type: 'b',
          props: {
            children: children
          }
        }
      }
    };
  }
}

気になる記述
When a component is defined as a class, it is a little bit more powerful than a function component. It can store some local state and perform custom logic when the corresponding DOM node is created or destroyed.

functionがシンプルなので推奨

Top-Down Reconciliation

Form componentが以下で定義されるとして

const Form = ({ isSubmitted, buttonText }) => {
  if (isSubmitted) {
    // Form submitted! Return a message element.
    return {
      type: Message,
      props: {
        text: 'Success!'
      }
    };
  }

  // Form is still visible! Return a button element.
  return {
    type: Button,
    props: {
      children: buttonText,
      color: 'blue'
    }
  };
};

FormのReactDOM.renderが呼ばれると

ReactDOM.render({
  type: Form,
  props: {
    isSubmitted: false,
    buttonText: 'OK!'
  }
}, document.getElementById('root'));

propsのisSubmittedがfalseなので、
→ type: Button が呼ばれる → Buttonはtype: 'button'のDOMタグObjectを返却する。
これが ReactDOM.rendersetState() が呼ばれた時に 実行される Reconciliation のプロセス
Reconciliationが終わると ReactはDOMツリーの結果を知り、react-domがDOMノードのアップデートを行う。

これがReactアプリが最適化(optimize)しやすい理由の一部
コンポーネントツリーが大きくなっても、関連するpropが変更されてなければ差分をスキップする。
→ Reactとimmutabilityの相性はバツグン

インスタンスの説明が少ないが、実はReactはインスタンスの重要性は他のオブジェクト指向UIフレームワークよりはるかに低い

親コンポーネントのインスタンスが子コンポーネントのインスタンスにアクセスするメカニズムはあるが、一般的には避けるべき

ta1m1kamta1m1kam

Reconciliation

React’s “diffing” algorithm

Motivation

render() functionがReact elementsのツリーを作成する。
stateやpropsの更新時はrender()が異なるツリーを返す。
その時にUIを効率的に更新する方法が必要。

state of the art algorithms
order: O(n3)
→ これをReactで使うと 1000個の要素に対して、10億回単位の比較が必要

Reactは2つの仮定に基づくヒューリスティックなO(n) algorithmを実装している

2つの仮定
Two elements of different types will produce different trees.
The developer can hint at which child elements may be stable across different renders with a key prop.

The Diffing Algorithm

Elements Of Different Types

例:divとspanでルートDOMが違う時

<div>
  <Counter />
</div>

<span>
  <Counter />
</span>

古いツリーを解体し、新しいツリーを1から構築する
古いCounterは消されて、新しいCounterで再マウントされる

DOM Elements Of The Same Type

例:divとdivで同じElementの時

<div className="before" title="stuff" />

<div className="after" title="stuff" />

attributesだけ更新する(例ではclassNameのみ更新する)

Component Elements Of The Same Type

コンポーネントが更新されると、インスタンスは同じままなのでレンダリング間で状態が維持される。
Reactは新しい要素に一致するように基礎となるコンポーネントインスタンスのpropsを更新し、基礎となるインスタンスでUNSAFE_componentWillReceiveProps()、UNSAFE_componentWillUpdate()、および componentDidUpdate() を呼び出す。
次にrender()が呼び出され、diffアルゴリズムが再帰的に処理される。

Recursing On Children

デフォルトでは、DOMノードの子を再帰的に処理する場合、Reactは両方の子リストを同時に反復処理し、違いがあるたびに変異を発生させます。

<ul>
  <li>first</li>
  <li>second</li>
</ul>

<ul>
  <li>first</li>
  <li>second</li>
  <li>third</li>
</ul>

この例だとthirdを入れるだけ

<ul>
  <li>Duke</li>
  <li>Villanova</li>
</ul>

<ul>
  <li>Connecticut</li>
  <li>Duke</li>
  <li>Villanova</li>
</ul>

でも、この例だと全部置き換えないといけない...

Keys

上記の問題を解決するためにKey属性がある

<ul>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>

<ul>
  <li key="2014">Connecticut</li>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>

keyがあれば、何が新しいかわかるので最初にinsertされてもReactは検知できる
keyにはuniqueIDが好ましい
最終手段として配列のインデックスを使うことが出来るが、項目の順番を変えると非効率になる

Tradeoffs

  1. The algorithm will not try to match subtrees of different component types. If you see yourself alternating between two component types with very similar output, you may want to make it the same type. In practice, we haven’t found this to be an issue.
  2. Keys should be stable, predictable, and unique. Unstable keys (like those produced by Math.random()) will cause many component instances and DOM nodes to be unnecessarily recreated, which can cause performance degradation and lost state in child components.
ta1m1kamta1m1kam

React - Basic Theoretical Concepts

Transformation

Reactの大前提は、UIはデータを別の形に投影したものに過ぎない
同じ入力は同じ出力をする。

Abstraction

再利用可能なピースに抽象化することが重要
ある関数から別の関数を呼ぶような場合

Composition

他の抽象化を組み合わせて新しいものを作る

State