BiwaSchemeでReactする


諸般の事情でMithrilではなくReactに乗り換えた。 ...単にMaterial-UI( https://material-ui.com/ )が使いたかっただけだけど、最近のReactNativeにはreact-native-domというかなりアツいプロジェクトが有り( https://github.com/vincentriemer/react-native-dom )、試してみたい気持ちがある。

ビルド済のサイトをNetlifyに置いてみた。実装内容は前回( http://d.hatena.ne.jp/mjt/20180802/p1 )と同じく、ボタンを押すとカウントアップするだけ。F12で開くコンソールにSchemeからデバッグ出力もしている。
NetlifyはGitHub pagesのようにgitリポジトリにpushすることでサイトをビルドし公開することができる。GitHub pagesより強力なのはビルドにnpmやyarnがそのまま使用できる点で、yuniのビルドも含めて(ビルド成果の中間リポジトリを用意することなく、)無料で配置できている。

そもそもBiwaSchemeでReactにインターフェースできるのか問題

BiwaSchemeのようなJavaScript言語にとって、Reactのようなここ数年のJavaScriptインフラストラクチャを前提としたプラットフォームとのアクセスは難しい問題になる。ただ、Reactはちゃんと素のJavaScriptからライブラリにアクセスする方法をドキュメントしていて:

これらを駆使すればBiwaSchemeからReactコンポーネントを定義して使うことが一応できる。
JSXが無い場合は、MithrilのようにHyperScript(HTML要素を挿入するための手続きインターフェース)を使用してコンポーネントを記述することになる。ES6クラス構文が使用できない場合は、オプションライブラリである create-react-class を使用して通常のJavaScriptオブジェクトからクラスを生成できる。

this問題

Reactのコンポーネントを記述する上で問題になったのは、BiwaSchemeはスクリプト内からthisにアクセスできないという点で、エレガントな解法が全然浮かばない。
典型的なReactのコンポーネントは、イベントハンドラ中でthis.setState()を呼び出して自身の状態を更新し、再描画をトリガする:

  handleClick() {
    this.setState(prevState => ({
      isToggleOn: !prevState.isToggleOn
    }));
  }

しかし、BiwaSchemeではScheme側のクロージャからJavaScript文脈のthisにアクセスすることができないため、何らかの方法でthisを参照するメカニズムを実装する必要がある。
今回はヘルパ手続き thiswrap をJavaScript側に実装し、クロージャにthisを引数として渡す方式を取ってみた。

var thiswrap = function(cb){
    return function(){
        return cb(this);
    };
};

Scheme側はsyntax-rules(yuniが実装している擬似的なもの http://d.hatena.ne.jp/mjt/20180521/p1)で適当にラップしてrenderやhandleClickといった手続きに設定する:

(define %thiswrap (yuni/js-import "thiswrap"))
(define-syntax wrap-this   ;; Yuni generic-runtime requires 
  (syntax-rules ()         ;; EVERY syntax-rules as top-level form
    ((_ this form ...)
     (js-call %thiswrap (js-closure (lambda (this) form ...))))))
- snip -
      (define (counter-object)
        (js-call 
          createReactClass
          (js-obj
            "render" (wrap-this this
                                (js-call e Button
                                         (js-obj "color" "primary"
                                                 "onClick" 
                                                 (js-ref this "handleClick"))
                                         (number->string
                                           (js-ref (js-ref this "state")
                                                   "count"))))

            "handleClick" (wrap-this this
                                     (count++)
                                     (PCK 'COUNT count)
                                     (js-invoke this "setState" theCounter))
            "getInitialState" (js-closure (lambda () theCounter)))))

もうちょっとクールに実装できるような気がする。。