Topics

・関数型プログラミングとオブジェクト指向のパラダイムとしての対立 国内の【自称】関数型コミュニティと海外の論調の違い

ガラパゴス・ネットスラング=「関数型ポエム」という呪詛、先入観的読書と、フェアなレビューの登場

Qiitaの騒動から派生して、東北大学の住井 英二郎@esumii氏、らくだの卯之助@camloeba氏その他が、犯罪者集団に合流して2ちゃんねるでの誹謗中傷チームの「8賢者」「ウィザード組」とかアホなことをやってる件について

JavaScriptではES6+とReact-JSXからES5へのトランスパイルが標準に / ATOMエディタで最速環境構築 厳選パッケージ 3 + 3 2015年夏バージョン

2016年のnode、ES6、React、Babel、Atomエディタ界隈の方向性

Dockerじゃないsystemd-nspawn+machinectlが非常に良い

99%のプログラマがIQ145のJKに「ダサい」と言われてしまう理由とは?【その1】「計算機科学のほんとうの基礎」を理解していない。IQ145のJKと同じ事を語るMITの権威とSICPという聖典の権威を借りてマインドコントロールを解いてみよう

99%のプログラマがIQ145のJKに「ダサい」と言われてしまう理由とは?【その2】関数型プログラミングのイミュータブルな世界観とイミュータブルな実世界を完全に統合

10年先を行く斬新な関数型(FRP)データベースについて説明する 99%のプログラマがIQ145のJKに「ダサい」と言われてしまう理由とは?【その3】

[React (.js Facebook)解説 関数型プログラミングに目覚めた! IQ145の女子高生の先輩から受けた特訓5日間 サポート記事【静的HTML編】React 解説【動的HTML-FRP編】

量子コンピュータが超高速である原理と量子論とそれに至るまでの科学哲学史をゼロからわかりやすく解説01量子コンピュータが超高速である原理と量子論とそれに至るまでの科学哲学史をゼロからわかりやすく解説02

『関数型プログラミングに目覚めた!』のレビュー(Day-1)について

LISPデータ構造の問題点の指摘と抜本的解法としての新プログラミング言語の策定/純粋関数型言語「SPINOZA」

著書『関数型プログラミングに目覚めた! IQ145の女子高生の先輩から受けた特訓5日間』 [Day1]たち読み記事 無料公開中

『関数型プログラミングに目覚めた! IQ145の女子高生の先輩から受けた特訓5日間』を大変多くの方々にお買い求めいただき、感謝します。本書の全目次を公開し、質問を受け付けます。

2015年5月2日土曜日

React 解説【動的HTML-FRP編】

React (.js Facebook)解説 関数型プログラミングに目覚めた! IQ145の女子高生の先輩から受けた特訓5日間 サポート記事 静的HTML編

の続き、【動的HTML-FRP編】となります。

前回、【静的HTML編】では、

原始のWebページ、
静的ドキュメントを記述するための
HTML(宣言型コード)

enter image description here

を、

enter image description here

へと移行させました。

今回の【動的HTML-FRP編】では、

原始のWebページの進化系の、
動的なWebアプリケーション=ユーザ・インタフェース
を記述するための
HTML+JavaScript (命令形コード)

enter image description here

を、また、

enter image description here

に移行させます。
 

React-JSXでは、Elementがファーストクラス・オブジェクト

Reactでは、
物理的なUIに相当するDOM要素を仮想化し、
VirtualDOM(仮想DOM)という論理的存在になったElementが、
JavaScript(JSX)上で、ファーストクラス・オブジェクトとして、
縦横無尽、自由自在に論理操作して、合成もできてしまいます。

Elementを返り値として返す、関数型プログラミングの関数に相当するComponent

Elementは、別のElementを返り値として返す
関数型プログラミングの関数に相当するComponentとして設計されます。

設計定義された、Componentは、HTMLの従来のタグ表記とすることで、
Elementとして、「インスタンス化」されます。

関数型プログラミングのコンポーネント指向

Elementは、別のElementを返り値として返す
関数型プログラミングの関数に相当するComponentとして設計されます。

ということでも、すぐにわかるとおり、
これは延々と入れ子の論理構造となっており、
Reactではこの論理操作を繰り返して宣言していきます。

このように、
あるファーストクラス・オブジェクトを(必ず)返すような、
自身ファーストクラス・オブジェクトになるコンポーネントを設計する、
というコンポーネント単位の設計、
コンポーネントの多重構造の設計で、コーディングすることを、コンポーネント指向と言います。

コンポーネント単位の設計のコンポーネント指向は、原理的に宣言型のコードになります。

前回、その様子を実際に、簡単なHTMLをJSXに移行させることで、具体的にデモンストレーションしたわけですが、このコンポーネント指向の設計で、
さらに、時間変化まで取り扱う、というのは、また別の話になってきます。

Reactコンポーネントは、時間変化、ユーザの入出力にたいしてどうあるべきか?

Reactはコンポーネント指向であり、
すべての物理的なUIは、
コンポーネントとして、論理的に延々と入れ子構造で合成されて設計されています。

繰り返しますが、コンポーネント指向は宣言型のコーディングです。

ここを土台として、さて、これからどうしていくか?
各コンポーネントは、時間変化、ユーザの入出力にたいしてどうあるべきか?

いくつかのアプローチが考えられるでしょう。

【解法1】従来の命令型、あるいはオブジェクト指向のアプローチ

実際この解法は可能です。
Facebookは、Reactを全体の設計を規定するフレームワークとしてではなく、ミニマルなライブラリという位置づけとして提供しているので、このアプローチは可能です。
もちろん、Reactを設計、開発したFacebookの技術者の意図、設計思想の延長にはないでしょうし、推奨もされていないでしょうが、別に制限はかけられてはいないので、可能は可能です。

例題として、
毎秒、数字を0から1ずつカウントアップしていくUIを実装します。

var Timer = React.createClass(
{
  render: function()
  {
    return (
      <div>Seconds Elapsed: {time}</div>
    );
  }
});

var time = 0;

var f = function()
{
  time++;
  React.render(<Timer/>, document.body);
};
setInterval(f, 1000);

React.renderを、マウントする宣言、とするのではなく、
毎秒、文字通り「レンダーし直せ」という命令にします。

time をグローバル変数とし、グローバルで変更、管理し、
Timerコンポーネント内部から、その値を直接読みだしてエレメントに反映させています。

非常に直感的で、わかりやすいコードですが、
これは、もはやコンポーネント指向の設計思想ではありません。
これは、命令型のコードです。
この調子で進めていくと、命令型のパラダイムで全体が構築されるので、もちろん、あとあと宜しくないです。なぜ、命令型だと後々よろしくないのか?よくわからない人は、このブログのヘッダー部分にある著書の立ち読み記事を読んでください。

そこで、せめて、
React.renderは、あくまでマウントする宣言であり、
Timerコンポーネントのプロパティ(関数型プログラミングの引数に該当する)のみが変更されて、再描画させる、という思想でやります。

var TopComponent = React.render(<Timer secondsElapsed={time} />, document.body);
と宣言した、TopComponentについて、毎秒、
TopComponent.setProps({ secondsElapsed: time++ });
setPropsとプロパティ変更を通知して、再描画させます。

var Timer = React.createClass(
{
  render: function()
  {
    return (
      <div>Seconds Elapsed: {this.props.secondsElapsed}</div>
    );
  }
});

var TopComponent = React.render(<Timer secondsElapsed={time} />, document.body);

var time = 0;

var f = function()
{
  TopComponent.setProps({ secondsElapsed: time++ });
};
setInterval(f, 1000);

でも、まあこれも、
「ある対象に向けて」「通知する」という、命令型パラダイムなんですね。
宣言型のコンポーネント指向ではない。

【解法2】宣言型、Reactの純粋コンポーネント指向のアプローチ

そこで、宣言型、Reactの純粋コンポーネント指向のアプローチです。
Reactの公式サイトのサンプルコードがこれです。

https://facebook.github.io/react/index.html

A Stateful Component

var Timer = React.createClass({
  getInitialState: function() {
    return {secondsElapsed: 0};
  },
  tick: function() {
    this.setState({secondsElapsed: this.state.secondsElapsed + 1});
  },
  componentDidMount: function() {
    this.interval = setInterval(this.tick, 1000);
  },
  componentWillUnmount: function() {
    clearInterval(this.interval);
  },
  render: function() {
    return (
      <div>Seconds Elapsed: {this.state.secondsElapsed}</div>
    );
  }
});

var TopComponent = React.render(<Timer/>, document.body);

繰り返します。
Reactの純粋コンポーネント指向のアプローチです。

var Timer = React.createClass({....});
var TopComponent = React.render(<Timer/>, document.body);

と2つのReactの宣言文しか存在しません。

基本、Timerコンポーネントが内部で、すべてを一手に引き受けており、
時間変化について自発的に自らの返り値としてのエレメントを更新し、
それは自動的に、全体、つまりマウントされた宣言部分を再描画します。

これは完全かつ純粋なコンポーネント指向となっています。

ただし!ですね。そうは問屋がおろさない。世の中はそう単純でもないのです。

Reactのコンポーネントとは、あくまで、物理的なUIの部品単位を論理化したものです。
これは、あえてオブジェクト指向のMVCアーキテクチャでいうならば、
VのViewで、物理要素に相当します。
そして、MVCのM、Modelの部分は、「ビジネスロジック」などと言われる、
物理要素のViewと分離された正味の論理部分ですが、
そんな単純に、ModelとViewが1:1に結びついているわけもありません。

上のReact公式のサンプルコードでは、MとVが1:1に結びついた、
極めて単純化されたケースとして、
Timerコンポーネントが内部で、すべてを一手に引き受けており、
Reactの純粋コンポーネント指向のアプローチとなっているのですが、
実際、複数のコンポーネント=View、
そして、複数の「ビジネスロジック」=Modelがある場合、
このアプローチは脆くも破綻してしまいます。
つまり、このやり方を単純に模倣していっても、使い物にはならない。

あと、
this.setState({secondsElapsed: this.state.secondsElapsed + 1});
とか、回りくどくて、長くて、面倒くさい、ということもあります。

【解法3】”BestPractice”= ReactのStateは一箇所(トップ・ルート)に集中させ、トップ・ルートから再レンダリングさせるアプローチ

これは、Reactコミュニティでも、自サイトをReactで再構築しようとした技術者が、
試行錯誤を重ねていますが、一定のコンセンサスには到達しているようです。

その結果、ReactのBestPracticeを導出した、という記事が複数、公開されています。

REACT TIPS AND BEST PRACTICES
では、

CENTRALIZE STATE

enter image description here

ReactのStateは一箇所、具体的には、トップ・ルートコンポーネントか、あるいはグローバルに集中させて、再度、render()をトリガーする、という方法が推奨されています。
このアプローチがもっともシンプルであると。

しかし、この”BestPractice”はいくつか問題があります。
少なくとも2つ問題がある。

  1. これは根本的に、【解法1】の2つ目と同じアプローチであり、命令型である。
  2. コンポーネントの階層(Component Hierarchy)において、トップ・ルートコンポーネントから、末端のコンポーネントに至るまで、すべてのコンポーネントで、逐一、もれなくpropsを流さなければならない。つまり、各部品たるコンポーネントが、常に全体の情報を扱っていることになり、これは関心が分離されているべき部品、コンポーネントのあり方として本末転倒である。

実際、このアプローチでやると、コードはかなり面倒なことになります。

では、なんでこんな問題が起こっているのか?
根本の問題とはいったい何なのか?

その答えは、すでに書いたとおり。
ModelとViewが1:1に結びついているわけがない、からです。

Reactのコンポーネントとは、あくまでViewを「仮想DOM」として論理化した部品であり、
「ビジネスロジック」たるModelとは対応させるべきものでは本来ありません。

Reactのコンポーネントモデルは、あくまでViewという末端を取り回すことに特化した設計であり、それだけで、すべてを構築するのは無理があるんですね。

【解法4】関数型・リアクティブ・プログラミング(Functional Reactive Programming/FRP)のコンポーネントをReactに別途適用する、宣言型

真の”BestPractice”へのヒントは、実は公式サイトに書かれていたりします。

Communicate Between Components
https://facebook.github.io/react/tips/communicate-between-components.html

For communication between two components that don’t have a parent-child relationship, you can set up your own global event system. Subscribe to events in componentDidMount(), unsubscribe in componentWillUnmount(), and call setState() when you receive an event.

親子関係にない、2つのコンポーネントを連携させる場合は、
自前のグローバルのイベントシステムをセットアップしたら良い。
各イベントをcomponentDidMount()で、Subscribeし、
componentWillUnmount()で、unsubscribeし、
イベント通知があれば、 setState()を呼び出すようにする。

これ何を書いているか?っていうと、もちろん、
関数型・リアクティブ・プログラミング(Functional Reactive Programming/FRP)
を自前で実装して、ReactのFRP機構と接続させろ、ってことです。

多くの技術者が”BestPractice”として【解法3】を示唆している中、
なぜ私が、この【解法4】のほうが真の”BestPractice”であると「気がつく」「わかる」のか?というと、それは、ソフトウェア開発における「パラダイム」の違い、もっと言えば、プログラミングの「設計思想」「考え方」「哲学」を常に重視しているからです。著書では、そこが長期的には必ずそこが重要になってくる、と信じるのでそう強調しながら解説しましたが、残念ながら多くの人たちは全く興味も理解も示さなかったようです。
また、おそらく、流れから私の著書が世に広く認められることになってしまうと、批判した自分たちが相対的に間違っていたと評価が下ることと等価であるので、それだけは絶対に阻止したかった思惑もあったのでしょう。これは「自分の立場による手前勝手な都合」であり、「他人の都合のため」の行動ではありません。もちろん、その他人とは、広い入門者のことですが、実際、先入観のない入門者のほうが、著書を受け入れやすいようです。私はメールでの直接のやりとりを通じてそれは実感できたので、著書は成功した、と考えています。「この世代」による反発という短期的な現象(ブーム)は予想通り起こったが、「次の世代」では、その現象は過去のものとなる。単なる短期的な現象(ブーム)ですからね。短期的な現象(ブーム)とは、中長期の評価ではありません。

閑話休題。

your own global event system、つまり自前のFRPで実装しろ、という公式サイトのTIPSによる示唆ですし、実際、私はReactとはFRPの末端部分に過ぎない、という考えとともに、実用に耐えるFRPライブラリを自前で実装する作業をしていたので、それを活用することにします。

worldcomponent The tiny FRPです。

The tiny FRP、このライブラリ(コンポーネント)の実装はかなり単純でコードは短いです。

ソースコード

var worldcomponent = function(initialVal)
{
  var computingF = [];
  var value = {};
  var state;
  Object.defineProperties(value,
  {
    val: //value.val
    {
      get: function()
      {
        return state;
      },
      set: function(x)
      {
        state = x;
        computingF.map(
          function(f)
          {
            f(x);
          });
        return;
      }
    }
  });
  var o = {
    compute: function(f)
    {
      var f1 = function()
      {
        computingF[computingF.length] = f; //push  f
        value.val = initialVal;
      };
      return f1;
    },
    appear: function(a)
    {
      var f1 = function(){value.val = a;};
      return f1;
    },
    now: function()
    {
      return value.val;
    }
  };

  return o;
};

Object.defineProperties(worldcomponent,
{
  world: //our physical world
  {
    set: function(f)
    {
      return f();
    }
  }
});

worldcomponent.log = function(a)
{
  var f = console.log.bind(console, a);
  return f;
};

module.exports = worldcomponent;

著書のメインキャラであるsakurafunctionalの名前で、npmライブラリとして公開
https://www.npmjs.com/package/worldcomponent/
していますが、特にReactを実装するWebアプリから、npmライブラリを利用するには、
browserifyや、webpackで、コードを再パッケージした上でブラウザ上で動作させる必要があります。
CDNで公開してもいいのですが、コードが短いので、上記のコードをコピペして使えば良い、と思っていて、今回もその方針でいきます。Simple is bestです。

npm testにあるとおり、nodeから使う際は、requireもつけて、

テストコード

var ___ = require('./worldcomponent');

var ___a = ___(0);
var ___b = ___(0);

___.world = ___a.compute(___.log('a:'));
___.world = ___b.compute(___.log('b:'));

___.world = ___a.compute(function(x)
{
  ___.world = ___b.appear(x * 5);
});

var f = function()
{
  ___.world = ___a.appear(___a.now() + 1);
};
var timer = setInterval(f, 1000);

実行結果

a: 0
b: 0
a: 0
b: 0
a: 1
b: 5
a: 2
b: 10
a: 3
b: 15
a: 4
b: 20
a: 5
b: 25
a: 6
b: 30
a: 7
b: 35

というように使うのですが、この、
___.world = ___a.compute(___.log('a:'));
とか、どういう意味なのか?
これは、命令型パラダイムを排除し、純粋に宣言型のコードにする超絶工夫なのですが、
この表記の思想背景については、
Lispデータ構造の問題点の指摘と抜本的解法としての新プログラミング言語の策定/純粋関数型言語「Spinoza」に書いており、そのまま援用しています。

また、このappearとかいう用語については、著書でこの前段になる概念実証のためのライブラリの解説としても書いています。
FRPとは、時間軸上のイベントを数学的な集合として取り扱うので、必然的にこういう言葉遣いになるわけですが、本稿では長くなるので余裕で省略。著書を読み返してみてください。

___.world = ___a.compute(function(x)
{
//.............
});

という部分が、ReactのTIPSで示唆されていた、

各イベントをSubscribeし、
イベント通知があれば、
setState()を呼び出すようにする。

に該当する要点となります。

さて、コードまるまるコピペにせよ、webpacknpmパッケージングにせよ、
いずれにせよ、
worldcomponentを適用してやり、
すでに、すっかり忘れかかっていた例題

毎秒、数字を0から1ずつカウントアップしていくUI実装

Reactの公式サイトのサンプルコード
https://facebook.github.io/react/index.html
A Stateful Component
を置き換える、代替アプローチのコードは以下です。

//worldcomponent the tiny FRP
//copy-pasted here, available in npm-------------
//-----------------------------------------

の箇所に実際は、上記worldcomponentのソースコードがコピペされています。

//worldcomponent the tiny FRP
//copy-pasted here, available in npm-------------
//-----------------------------------------

var ___ = worldcomponent;
___.world = ___.log('start!'); //debug log

var ___time = ___(0);

var f = function()
{
  ___.world = ___time.appear(___time.now() + 1);
};
var timer = setInterval(f, 1000);

var Timer = React.createClass(
{
  getInitialState: function()
  {
    return {secondsElapsed: null};
  },
  componentDidMount: function()
  {
    var com = this;
    ___.world = ___time.compute(function(x)
    {
      com.setState({secondsElapsed: x});
    });
  },
  render: function()
  {
    var el = (<div>Seconds Elapsed: {this.state.secondsElapsed}</div>);
    return el;
  }
});

var TopComponent = React.render(<Timer/>, document.body);

Communicate Between Components
https://facebook.github.io/react/tips/communicate-between-components.html

For communication between two components that don’t have a parent-child relationship, you can set up your own global event system. Subscribe to events in componentDidMount(), unsubscribe in componentWillUnmount(), and call setState() when you receive an event.

親子関係にない、2つのコンポーネントを連携させる場合は、
自前のグローバルのイベントシステムをセットアップしたら良い。
各イベントをcomponentDidMount()で、Subscribeし、
componentWillUnmount()で、unsubscribeし、
イベント通知があれば、 setState()を呼び出すようにする。

という、TIPSをその通りに(結果的に)やっています。
別に、このTIPSの指示どおりなぞった、ということではなく、論理的にこうなります。
componentDidMount(), unsubscribeについては別に必須ではないので省略しています。

このコードは、Timerコンポーネントがひとつしかないので、
このFRPアプローチのメリットが今ひとつ明白ではないかもしれませんが、

親子関係にない、2つのコンポーネントを連携させる場合

あるいは、それ以上の複数のコンポーネントが複雑に絡み合う場合であっても、
極めて見通しのよい宣言型のコードがかけます。

【解法3】が抱える問題、

  1. これは根本的に、【解法1】の2つ目と同じアプローチであり、命令型である。
  2. コンポーネントの階層(Component Hierarchy)において、トップ・ルートコンポーネントから、末端のコンポーネントに至るまで、すべてのコンポーネントで、逐一、もれなくpropsを流さなければならない。つまり、各部品たるコンポーネントが、常に全体の情報を扱っていることになり、これは関心が分離されているべき部品、コンポーネントのあり方として本末転倒である。

が解決されています。

【解法5】関数型・リアクティブ・プログラミング(Functional Reactive Programming/FRP)のコンポーネントをReactに別途適用する、宣言型 setState不要メソッド

ここまで,
Communicate Between Components
https://facebook.github.io/react/tips/communicate-between-components.html

For communication between two components that don’t have a parent-child relationship, you can set up your own global event system. Subscribe to events in componentDidMount(), unsubscribe in componentWillUnmount(), and call setState() when you receive an event.

親子関係にない、2つのコンポーネントを連携させる場合は、
自前のグローバルのイベントシステムをセットアップしたら良い。
各イベントをcomponentDidMount()で、Subscribeし、
componentWillUnmount()で、unsubscribeし、
イベント通知があれば、 setState()を呼び出すようにする。

という、TIPSをその通りに(結果的に)論理的にやった、わけですが、
なんとも、気持ちが悪い、納得がいかないのが、

 getInitialState: function()
  {
    return {secondsElapsed: null};
  },

と初期化されて用意されている、Reactコンポーネントのstate変数の存在です。
これは、worldcomponentのFRPの値とは、別途存在しています。

これは本当に必要なのか?

そもそも、Reactコンポーネントのstate
というより、FRPの理論的には変化しないイミュータブルな定数なのですが、
これ自体がReactのイベントシステムであって。
Reactコンポーネントの再描画renderをトリガーする仕組みです。

しかし、今や、自前のグローバルのイベントシステムである、
worldcomponentがあるので、React内部で、イベント通知の仕事をまたやってもらう必要はありません。
二度手間ですし、コードもその分無駄に書かなければいけません。
自明的に、再描画renderをトリガーすればよいです。

実際そのためのforceUpdateという、APIは用意されています。
https://facebook.github.io/react/docs/component-api.html

forceUpdate

forceUpdate([function callback])

If your render() method reads from something other than this.props or this.state, you’ll need to tell React when it needs to re-run render() by calling forceUpdate(). You’ll also need to call forceUpdate() if you mutate this.state directly.

Calling forceUpdate() will cause render() to be called on the component, skipping shouldComponentUpdate(). This will trigger the normal lifecycle methods for child components, including the shouldComponentUpdate() method of each child. React will still only update the DOM if the markup changes.

Normally you should try to avoid all uses of forceUpdate() and only read from this.props and this.state in render(). This makes your application much simpler and more efficient.

自前のこのようなFRPを適用しない場合は、
ReactコンポーネントのstateのFRP機構に依存しており、それで事足りるわけで、
その場合、このAPIは使う必要性はまったくありませんし、使うべきではないでしょう。

しかし、上述のとおり、自前のFRPを適用した場合、
ReactコンポーネントのstateのFRP機構はもはやまったく必要なくなるので、
forceUpdateを使ったほうがコンポーネントの設計がかなり簡潔になります。

これは、要するにどういうことか?
もうReactコンポーネントのstateの出る幕はなくなった、ということです。

上記、APIドキュメントでも、normal lifecycleがトリガーされる、ということが説明されており、パフォーマンスもなんでも一緒です。
Reactのメリットは一切失われることなく、ただ設計の重複した要素の冗長さが排除され、
コードが簡潔に見通しが良くなります。

もちろん、propsstateの「使い分け」ということを考える必要もなくなりました。

worldcomponentが司るFRP機構によってModelをイミュータブルに管理し、
reactでは、ただpropsだけを使って、物理的なUI=Viewをイミュータブルに構成する。

それが筆者のReactの”Best Practice”です。

Reactの公式サイトのサンプルコード
https://facebook.github.io/react/index.html
A Stateful Component
を筆者のReactの”Best Practice”で置き換えるコードは以下です。

//worldcomponent the tiny FRP
//copy-pasted here, available in npm-------------
//-----------------------------------------
var ___ = worldcomponent;
___.world = ___.log('start!'); //debug log

var ___time = ___(0);

var f = function()
{
  ___.world = ___time.appear(___time.now() + 1);
};
var timer = setInterval(f, 1000);

var Timer = React.createClass(
{
  componentDidMount: function()
  {
    var com = this;
    ___.world = ___time.compute(function(x)
    {
        com.forceUpdate();
    });
  },
  render: function()
  {
    var el = (<div>Seconds Elapsed: {___time.now()}</div>);
    return el;
  }
});

var TopComponent = React.render(<Timer/>, document.body);

Click Counter

Click Counterなる、
「ああこれが出来るなら、多分どんなWebアプリも書けるだろうな!」
というシンプルかつ要点をおさえているWebアプリを
React+worldcomponentで構築してみます。

ライブデモ

 ↑ このブログ上で実際に動作します。

各グレーの「カード」をクリックすると、
Localのクリック数、Totalしたクリック数がカウントアップされます。

そして、前回の
【静的HTML編】の一番最後のコードが、ベースになっています。
ChildrenComponentなど、よく見比べてみてください。

コンポーネントの意味として、ChildrenComponentは、
前回、【静的HTML編】の一番最後のコードと同様に、
単に、物理的なUIのコンテナの意味しかありません。

【解法3】のアプローチでやると、
最上層から最下層(末端のカードのコンポーネント)に至るまで、
すべての階層のコンポーネントで、
すべてのprop引数をリレーしてやらなければ、
グローバルで管理しているデータが届かないので、
単に、物理的なUIのコンテナの意味しかない、ChildrenComponentにも、
すべてのprop引数を渡して、その子コンポーネントである、
ChildComponentにさらに、prop引数を渡す、という設計とせねばなりません。

しかし、この【解法5】(【解法4】もそう)では、直接グローバルで管理している、
worldcomponentの値にFRPでSubscribe=computeしているので、その必要はありません。

各UIの物理的な階層関係と、各UIデータの結びつきの構造は、別構造なので、
FRPをもってして、このように分離して設計すべきです。

var ___ = worldcomponent;
___.world = ___.log('start!'); //debug log

var CARDS = 4;
var ___totalClicks = ___(0);

var TopComponent = React.createClass(
{
  render: function()
  {
    var divStyle = {background:"#aaaaaa"};
    var el = (
      <div style={divStyle}>
        <h1>Click Counter</h1>
        <ChildrenComponent/>
      </div>
    );
    return el;
  }
});

var ChildrenComponent = React.createClass(
{
  render: function()
  {
    var createChild = function(n)
    {
      return (<ChildComponent id={n} ___clicks={___(0)}/>);
    };

    var array = Immutable.Range(0, CARDS).toArray();
    var elArray = array.map(createChild);

    var el = (<div>{elArray}</div>);
    return el;
  }
});

var ChildComponent = React.createClass(
{
  componentDidMount: function()
  {
    var com = this;

    ___.world = com.props.___clicks
                .compute(function(x){com.forceUpdate();});
    ___.world = ___totalClicks
                .compute(function(x){com.forceUpdate();});
  },
  handleClick: function()
  {
    var com = this;

    ___.world = com.props.___clicks
                  .appear(com.props.___clicks.now() + 1);
    ___.world = ___totalClicks
                  .appear(___totalClicks.now() + 1);
  },
  render: function()
  {
    var com = this;

    var divStyle = {background:"#dddddd",
                    width:"150px",height:"150px",
                    margin:"5px",float:"left",
                    "user-select": "none",
                    "-moz-user-select": "none",
                    "-webkit-user-select": "none",
                    "-ms-user-select": "none"};

    var el = (<div style={divStyle} onClick={com.handleClick}>
                <h2>id : {com.props.id}</h2>
                <h4>Local Clicks : {com.props.___clicks.now()}</h4>
                <h4>Total Clicks : {___totalClicks.now()}</h4>
              </div>);
    return el;
  }
});

var mount = React.render(<TopComponent/>, document.body);

GitHubレポジトリ

【静的HTML編】から通して、
すべてのソースコードは以下にあります。

https://github.com/kenokabe/react-abc

0 コメント:

コメントを投稿

Popular Posts