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からライブラリにアクセスする方法をドキュメントしていて:
- https://reactjs.org/docs/react-without-jsx.html
- JSX(JavaScript中にHTML要素を書けるようにする糖衣構文)なしでReactを使用する方法
- https://reactjs.org/docs/react-without-es6.html
- ES6のクラスシステムを使用せずにReactを使用する方法
これらを駆使すれば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)))))
もうちょっとクールに実装できるような気がする。。