Open2

Mehログ 2024-11

hirospherehirosphere

仮想DOMじゃないHTMLコンポーネントライブラリ

仮想DOMじゃない HTMLコンポーネントのJS/TSライブラリ「Meh」の、きわめてマイペースな開発・河原の石積んでくずし日記。

無職になり1年ぶりの投稿です。

値モデルの課題

Vue でいう ref のようなオブジェクト、末端・枝葉の葉のやくわりを担うので「Leaf」と名付け、それと参照し合いチェンジイベントをうけとる側を「Ref」と名付けているので、Vue の ref とまぎらわしくなったかもしれません。

オブジェクトの生成は new Leaf < string > ( "い・ろ・は" ) のように、クラスを new していましたが、値実体を持たないインターフェースの需要があったので、実体は Leaf.Entity とし、生成は leaf < V > という関数で簡潔に作るようにしました。

また、Leaf は TSインターフェースとはせず abstract class にして、実行時に instanceof で判別できるようにしました。

タブやページなどの「切り替え」の宣言的表現

switch という名前の関数がつくれればうれしいですが、JSのそれと被ってしますので、抽象クラスで place という疑似名前空間を作り、そこに static メンバとして閉じ込めてみました。

「sw」のように略してもいいでしょう。

引数順は、

place.switch < K >
(
    選択キー : K | Leafr < K > ,
    Mehエレメント作成関数 | [ キー, Mehエレメント実体 ][] ,
    初期化オプション
)

のようにしました。

「キー」には真偽型・数値型・文字列型のほかにオブジェクト型もつかえます。

「SPA=ブラウザ内ブラウザ」ナビゲーションの課題

「サイト構造」を模した「Index」オブジェクトのツリー。

やっと気に入る「ページ切り替え機構の表現」ができたので、路線・駅APIを使った簡単なツリーナビでも、と思いましたが、「パス path」を表す動的アレイも作らなければ.. 、いうことになり、ちょっと一休み。

リポジトリ Meh-24-9/ts-src/meh/model

ページ切り替えサンプル
HTMLページ
コード

hirospherehirosphere

リファクト!!

「leaf , Leaf , leafr , Leafr」と、値生成・型・クラス・アクセス性などで、4つに分けていた値モデルのシンボルを「leaf」ひとつに統一。

const l : leaf.str = leaf.str ( "value で読み書き可" ) ;
const lr : leaf.r = leaf.r.str ( "value は読み専用" ) ;
l.value = "書き換えるよ。" ;
lr.value = "いいじゃないか" ; // TSエラー

const ll : leaf.ll.str = "動静どちらかの値" ;
console.log ( ll instanceof leaf.Base ? "動的な、リアクティブな、値。" :  "静的な値。" ) ;

読み専用 R を基底クラスとして、読み書き可 W/R の方もクラスとして定義するやり方に矛盾が生じ、

基底側を W/R とし、値実体や返還などの具象クラスの実装は W/R のみとし、インターフェイスと2種の生成関数で、TS文法上の書きアクセスを閉じるやり方へ。

JS上ではエラーにならないでしょうが..

leaf.ts
import { log } from "../common.js" ;


/* W/R 実体生成 */

export function leaf < V > ( value : V , branch ? : leaf.branch < V > ) : leaf < V >
{
	return new leaf.Entity ( value , branch ) ;
}

export namespace leaf
{
	export const str = leaf < string > ;
	export const num = leaf < number > ;
	export const bool = leaf < boolean > ;

	export const get = < V > ( lol : ll < V > ) =>
	{
		return lol instanceof Source ? lol.value : lol ;
	}
}

/* W/R 型定義 */

export const set_value = Symbol() ;

export interface leaf < V >
{
	add_ref
	(
		ref : leaf.ref < V > ,
		old_v ? : V
	
	) : void ;

	remove_ref ( ref : leaf.ref < V > ) : void ;

	get value () : V ;
	set value ( value : V ) ;

	set ( value : V , changer ? : object ) : void ;

	[ set_value ] ( value : V , changer ? : object ) : void ;
}

export namespace leaf
{
	export type str = leaf < string > ;
	export type num = leaf < number > ;
	export type bool = leaf < boolean > ;
}

export namespace leaf
{
	export type ll < V > = leaf < V > | V ;

	export namespace ll
	{
		export type str = ll < string > ;
		export type num = ll < number > ;
		export type bool = ll < boolean > ;
	}
}

/* readonly */

export namespace leaf
{
	/* R 実体生成 */

	export function r < V > ( value : V , branch ? : leaf.branch < V > ) : r < V >
	{
		return new leaf.Entity ( value , branch ) ;
	}

	export namespace r
	{
		export const str = r < string > ;
		export const num = r < number > ;
		export const bool = r < boolean > ;
	}
	
	/* R 型定義 */

	export interface r < V > extends leaf < V >
	{
		get value () : V ;
	}

	export namespace r
	{
		export type str = r < string > ;
		export type num = r < number > ;
		export type bool = r < boolean > ;
	}

	export namespace r
	{
		export type ll < V > = r < V > | V ;

		export namespace ll
		{
			export type str = ll < string > ;
			export type num = ll < number > ;
			export type bool = ll < boolean > ;
		}	
	}
}

/* */

export namespace leaf
{
	/* 基底抽象クラス */

	export abstract class Source < V > implements leaf < V >
	{
		protected refs = new Set < ref < V > > ;

		public add_ref ( ref : ref < V > , old_v ? : V ) : void
		{
			this.refs.add ( ref ) ;
			ref.src_value_change ( this.value , old_v ) ;
		}

		public remove_ref ( ref : ref < V > ) : void
		{
			this.refs.delete ( ref );
		}

		public abstract get value () : V ;
		public abstract set value ( value : V ) ;

		public abstract set ( value : V , changer ? : object ) : void ;

		public [ set_value ] ( new_v: V , changer ? : object ) : void
		{
			this.set ( new_v , changer ) ;
		}
	}

	/* 値実体クラス */

	export class Entity < V > extends Source < V >
	{
		constructor
		(
			protected p_value : V ,
			protected p_branch ? : branch < V >
		)
		{
			super () ;
		}

		public override get value ()
		{
			return this.p_value ;
		}

		public override set value ( new_v : V ) { this.set ( new_v ) }

		public override set( new_v: V , changer ? : object ) : void
		{
			if( new_v === this.p_value )  return ;

			const old_v = this.p_value ;
			this.p_value = new_v ;

			if( changer != this.p_branch )
			{
				this.p_branch?.update ( new_v , old_v ) ;
			}

			this.refs.forEach
			(
				ref => ref != changer && 
				(
					ref.src_value_change ( new_v , old_v )
				)
			);
		}
	}

	/* 参照・変換クラス */

	export class Converter < S , R = S > extends Source < R >
	{
		constructor
		(
			protected src : Source < S > ,
			protected to_ref : conv < S , R > ,
			x ? : [ conv < S , R > , conv < R , S > ]
		)
		{
			super () ;
			src.add_ref( this ) ;
		}

		public override get value () : R
		{
			return this.to_ref ( this.src.value ) ;
		}

		public override set value ( new_v : R )
		{ /* 未実装 */ }

		public override set ( value : R, changer ? : object ) : void
		{
			/* 未実装 */ ;
		}

		/* */

		public src_value_change ( new_v : S , old_v ? : S )
		{
			const new_rv = this.to_ref ( new_v ) ;
			const old_rv = old_v === undefined ? undefined : this.to_ref ( old_v ) ;

			this.refs.forEach
			(
				ref => ref.src_value_change ( new_rv , old_rv )
			);
		}
	}

	type conv < S ,R > = ( s : S ) => R ;

	/* */

	export const ref = < V >
	(
		src : leaf < V > ,
		src_value_change : update < V >
	
	) : ref < V > => new Ref ( src , src_value_change ) ;

	export interface ref < V >
	{
		src_value_change : update < V > ;
		src_term ? () : void ;
		term ? () : void ;
	}

	export class Ref < V > implements ref < V >
	{
		constructor
		(
			protected src : leaf < V > ,
			public src_value_change : update < V > ,
		)
		{
			src ?.add_ref ( this ) ;
		}

		public term ? () : void
		{
			this.src ?.remove_ref ( this ) ;
		}
	}

	/* */

	export type update < V > = ( new_v : V , old_v ? : V ) => void ;
	export type update_ref < V > = ( new_v ? : V , old_v ? : V ) => void ;

	export interface branch < V >
	{
		update : update < V > ;
	}

}