Web Components用フレームワークhybrids
結構面白そうで、ドキュメントを読んだら触ってみたい。
JavaScriptオブジェクトの「モデル」があり、コンポーネントはそれを表現したり、ユーザーインタラクションによってモデルに変更を加えたりする。
そのためにstoreという物が使われている。これが中々面白い。と同時に独特だから可搬性は低そう。
でも今は、可搬性とかいいからいい感じに使えるフレームワークがあった方がいい時代だと思う。
モデルはオブジェクトだが、なるべく関数型で扱おうとしている。
例えばこんなコードがある:
import { User } from "./models.js";
function submit(host, event) {
event.preventDefault();
// Creates a real `User` model instance
store.submit(host.user).then(() => {
// Clears values in the form
host.user = null;
});
}
const CreateUserForm = {
user: store(User, { draft: true }),
render: ({ user }) => html`
<form onsubmit="${submit}">
<div class="${ error: store.error(user, "firstName") }">
<input defaultValue="${user.firstName}" oninput="${html.set(user, "firstName")}">
</div>
<div class="${ error: store.error(user, "lastName") }">
<input defaultValue="${user.lastName}" oninput="${html.set(user, "lastName")}">
</div>
</form>
`,
}
store.error(user, "firstName")
はuser
オブジェクトのfirstName
プロパティがエラーかどうか、エラーならそのエラーを返す関数。エラーかどうかを調べるのにuser
にエラーオブジェクトなどを持たせるのではなく、関数で取得しているのが特徴的というか、オブジェクト指向脳のうちは慣れない。
こんなふうにモデル定義をする:
const User = {
id: true,
firstName: "",
lastName: "",
...,
};
const Profile = {
user: User,
isAdmin: false,
...,
};
TypeScriptの型やインターフェイスを使うのではなくJavaScriptで定義している。
TypeScriptでモデルのインターフェイスを定義することもできるけど、結局JavaScript(ランタイム)でも定義はしないといけない:
import { store, Model } from "hybrids";
interface User {
id: string;
name: string;
};
const User: Model<User> = {
id: true,
name: "",
[store.connect]: (id) => fetch(`/users/${id}`).then(res => res.json()),
};
export default User;
独特すぎてこれ、他のウェブコンポーネントと相互作用できるんだろうか。我々は普段HTMLXxxElement
インターフェイス相手にプログラミングするわけだけど、そういうふうになるの?
こういうコンポーネントを作ったとする。
export default define<AppLayout>({
tag: 'app-layout',
sidebar: store(Sidebar),
render: ({sidebar}) => html`
<div class="header">
<slot name="header"></slot>
</div>
<div class="body">
<div class="main">
<slot name="main"></slot>
</div>
<div class="sidebar" aria-hidden="${sidebar.open ? 'false' : 'true'}">
<slot name="sidebar"></slot>
</div>
</div>
`
});
hybridsだとこのグローバルステートであるsidebar: store(Sidebar)
を使って.sidebar
の開閉を制御するしかないように思うのだけど、本当は<app-layout aria-expanded="true">
とかやって開閉したい。JavaScriptのレベルでは
appLayout.ariaExpanded = true;
とか。
つまり
- 親コンポーネントから
<app-layout>
の属性を設定することで開閉させたい - その属性の値を元に
Sidebar
ステートも更新したい
ということなのだけど、レンダリングが純粋関数でしてしまっているので属性の変更 -> ステートの更新という順番で処理できない気がする。
モデルのid
は自分で設定することもできるが、しなければ自動でUUIDv4が付与される。