この記事は「構造パスで作るシンプルなフロントエンドフレームワーク(Structive)」Advent Calendarの21日目です。
Structiveについて詳しくはこちらより
Week 3 を終えて
Week 3(Day 15〜20)では、コンポーネント化とスケーラビリティについて学びました。今日は、これまでの内容を振り返り、理解を深めます。
Week 3 で学んだこと
Day 15: SFC(シングルファイルコンポーネント)の設計思想
SFCの基本構造:
<template>
<!-- HTML: UIの構造 -->
</template>
<style>
/* CSS: スタイル */
</style>
<script type="module">
// JavaScript: ロジックと状態
export default class { }
</script>
なぜSFCなのか:
- 関連コードが一箇所に集約
- ファイル間の移動不要
- コンポーネント単位で管理
- 保守性と再利用性の向上
関心の分離:
- template: 構造の関心
- style: 見た目の関心
- script: ロジックの関心
Day 16: Web Componentsとの統合
コンポーネントの登録(3ステップ):
- SFCファイルを作成
- Import Mapsで登録
<script type="importmap">
{
"imports": {
"@components/user-card": "./user-card.st.html"
}
}
</script>
- Loaderを読み込み
<script type="module" src="EasyLoaders/components.js"></script>
ライフサイクル:
-
$connectedCallback: DOMに追加されたとき -
$disconnectedCallback: DOMから削除されたとき
使用パターン:
export default class {
timer = null;
$connectedCallback() {
// リソースの初期化
this.timer = setInterval(() => { ... }, 1000);
}
$disconnectedCallback() {
// リソースのクリーンアップ
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
}
}
}
Day 17: 親子コンポーネント間のデータ受け渡し
状態の部分委譲:
<child-component data-bind="state.子パス:親パス"></child-component>
パスの変換:
親: users.*.name → 子: user.name
親: users.*.age → 子: user.age
子から親の状態変更:
// 子コンポーネント
export default class {
product = { quantity: 1 };
increase() {
// 子で変更すると親の状態も変わる
this["product.quantity"] += 1;
}
}
複数プロパティの受け渡し:
<child
data-bind="
state.item:items.*;
state.index:$1;
state.total:items.length
"
></child>
Day 18: ビルトイン型WebComponentとShadow DOMモード
static $config:
export default class {
static $config = {
extends: "tr", // ビルトイン要素の拡張
shadowDomMode: "auto" // Shadow DOMモード
};
}
ビルトイン拡張(extends):
<!-- テーブル行を拡張 -->
<tr is="user-row" data-bind="state.user:users.*"></tr>
<!-- リストアイテムを拡張 -->
<li is="todo-item" data-bind="state.todo:todos.*"></li>
何がうれしいか:
- HTMLの構造的制約を守れる
- セマンティックなHTML
- ブラウザのレイアウトエンジンが正しく動作
Shadow DOMモード:
-
auto: 可能なら使う、不可能なら通常DOM(推奨) -
none: 常に通常DOM、グローバルCSSが適用される
Day 19: Import Mapsの詳細な使い方
3つのロードモード:
1. イージーロード(推奨)
<script type="importmap">
{
"imports": {
"@components/my-component": "./my-component.st.html"
}
}
</script>
<script type="module" src="./EasyLoaders/components.js"></script>
- Structiveコア不要
- 最小限の記述
2. 自動ロード
<script type="importmap">
{
"imports": {
"structive": "./structive/core.js",
"@components/my-component": "./my-component.st.html"
}
}
</script>
<script type="module" src="./AutoLoaders/components.js"></script>
- Structiveコア必要
- 互換性のため
3. マニュアルロード
import { config, defineComponents } from "structive";
config.enableMainWrapper = false;
config.enableRouter = false;
defineComponents({
"my-component": "my-component-module"
});
- 細かい制御
- 条件付きロード
Day 20: エラーコード体系とデバッグ
エラーコード形式:
PREFIX-NNN
主要な接頭辞:
- TMP: テンプレート
- BIND: バインディング
- COMP: コンポーネント
- STATE: 状態管理
- FLT: フィルター
- LIST: リスト管理
- CSO: 親子間の状態
番号帯の意味:
- 0xx: 初期化・設定
- 1xx: 未登録・未検出
- 2xx: 無効な値・フォーマット
- 3xx: 状態の不一致
- 4xx: ランタイム失敗
デバッグモード:
import { config } from "structive";
config.debug = true;
Week 3 の核心概念
1. SFCによるコンポーネント化
1ファイル = 1コンポーネント
├── template(構造)
├── style(見た目)
└── script(ロジック)
利点:
- コードの集約
- 理解しやすさ
- 保守性
- 再利用性
2. Web標準の活用
SFC → Web Components → カスタム要素
- Import Mapsで登録
- customElements APIで実装
- Shadow DOMでカプセル化
3. 状態の部分委譲パターン
親: users.*.name → state.user:users.* → 子: user.name
- 構造パスで結合
- 疎結合な設計
- 双方向の同期
4. ビルトイン拡張の活用
<tr is="custom-row">
<li is="custom-item">
<option is="custom-option">
- HTMLの制約を守る
- セマンティック
- ネイティブな動作
5. エラーコードによるデバッグ
BIND-101 → BINDの101番 → 未登録エラー
- 素早い問題特定
- 明確な修正方法
- チーム内の共通言語
復習クイズ
Q1: SFCの3つのセクションは?
答え
template(HTML)、style(CSS)、script(JavaScript)Q2: コンポーネントのライフサイクルメソッドは?
答え
`$connectedCallback`(DOMに追加)、`$disconnectedCallback`(DOMから削除)Q3: 状態の部分委譲の構文は?
答え
`data-bind="state.子パス:親パス"`Q4: ビルトイン要素を拡張するには?
答え
`static $config = { extends: "tr" }`を定義し、``で使用Q5: 推奨されるロードモードは?
答え
イージーロード(EasyLoader)。Structiveコア不要で最小限の記述。Q6: エラーコードBIND-101の意味は?
答え
BINDの未登録エラー。data-bindで参照した名前が登録されていない。Week 1 + Week 2 + Week 3 = 完全な知識体系
Week 1(基礎編)
- 構造パスの概念
- UIと状態の思想
- ボイラープレート排除
- Proxyによるリアクティビティ
Week 2(コア機能編)
- forブロックとループ
- ifブロックと条件分岐
- 属性バインディング
- 派生状態(getter)
- パイプフィルター
Week 3(応用編)
- SFCの設計思想
- Web Componentsとの統合
- 親子コンポーネント
- ビルトイン拡張
- Import Maps
- エラーコード体系
これで実用的なアプリケーションを構築できます!
実践的な開発フロー
1. プロジェクトのセットアップ
project/
├── index.html
├── components/
│ ├── app-header.st.html
│ ├── user-list.st.html
│ └── user-card.st.html
└── EasyLoaders/
└── components.js
2. index.htmlの設定
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>My App</title>
<script type="importmap">
{
"imports": {
"@components/app-header": "./components/app-header.st.html",
"@components/user-list": "./components/user-list.st.html",
"@components/user-card": "./components/user-card.st.html"
}
}
</script>
<script type="module" src="./EasyLoaders/components.js"></script>
</head>
<body>
<app-header></app-header>
<user-list></user-list>
</body>
</html>
3. コンポーネントの実装
親コンポーネント(user-list.st.html):
<template>
<div class="user-list">
{{ for:users }}
<user-card data-bind="state.user:users.*"></user-card>
{{ endfor: }}
</div>
</template>
<script type="module">
export default class {
users = [
{ name: "Alice", email: "alice@example.com" },
{ name: "Bob", email: "bob@example.com" }
];
}
</script>
子コンポーネント(user-card.st.html):
<template>
<div class="user-card">
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
</div>
</template>
<style>
.user-card {
border: 1px solid #ccc;
padding: 1em;
margin-bottom: 1em;
}
</style>
<script type="module">
export default class {
user = { name: "", email: "" };
}
</script>
4. デバッグの有効化
// 開発環境
import { config } from "structive";
config.debug = true;
ベストプラクティスのまとめ
コンポーネント設計
✅ DO(推奨):
- 1コンポーネント200行以下を目指す
- 単一責任の原則を守る
- 意味のある命名(
user-card,product-list) - デフォルト値を提供
❌ DON'T(非推奨):
- 複数の責任を持つコンポーネント
- 曖昧な命名(
component1,thing) - nullやundefinedを初期値にする
状態管理
✅ DO(推奨):
- 基本状態と派生状態を明確に分ける
- getterで計算ロジックを集約
- 配列の更新は
concatやtoSpliced
❌ DON'T(非推奨):
- 状態を複数箇所に分散
- UIに複雑なロジックを書く
-
pushやspliceで直接変更
親子関係
✅ DO(推奨):
- 構造パスの契約を明確に
- 子コンポーネントは独立させる
- 双方向バインディングを活用
❌ DON'T(非推奨):
- 親の内部構造に依存
- グローバル状態に頼る
- 複雑な親子通信
エラー対応
✅ DO(推奨):
- エラーコードで素早く特定
-
config.debug = trueで詳細表示 - ライフサイクルでエラーハンドリング
❌ DON'T(非推奨):
- エラーを無視
- try-catchなしの非同期処理
- 不明瞭なエラーメッセージ
Week 4 への準備
Week 3までで、このフレームワークの主要機能はすべて学びました。Week 4では、実践的なトピックを扱います。
Week 4 の予定(Day 22〜24)
Day 22: 既存フレームワーク(React)との比較
- コード量の比較
- 開発体験の違い
- パフォーマンス特性
Day 23: 既存フレームワーク(Vue)との比較
- 設計思想の違い
- テンプレート記法の比較
- 学習曲線
Day 24: まとめ - 構造パスがもたらす開発体験の革新
- 全体の振り返り
- このフレームワークの特徴
- 今後の展望
Week 3 の成果
習得した技術:
- SFCによるコンポーネント化
- Web標準の活用
- 親子間のデータフロー
- ビルトイン拡張
- Import Mapsの使い方
- エラーコード体系
実装できるようになったこと:
- 再利用可能なコンポーネント
- 階層化されたコンポーネントツリー
- テーブル行などの特殊要素のコンポーネント化
- 効率的なデバッグ
開発体験の向上:
- コードの集約による保守性
- Web標準による互換性
- 構造パスによる疎結合
- エラーコードによる素早いデバッグ
まとめ
Week 3では、コンポーネント化とスケーラビリティを学びました:
SFC(Day 15):
- 1ファイル = template + style + script
- 関心の分離とカプセル化
Web Components(Day 16):
- Import Mapsで登録
- EasyLoader/AutoLoader
- ライフサイクル
親子コンポーネント(Day 17):
- 状態の部分委譲
- 構造パスで疎結合
- 双方向の同期
ビルトイン拡張(Day 18):
- static $config
- HTMLの制約に対応
- Shadow DOMモード
Import Maps(Day 19):
- 3つのロードモード
- 明示的な管理
エラーコード(Day 20):
- PREFIX-NNN形式
- 素早い問題特定
- デバッグモード
これで大規模アプリケーションの開発準備が整いました!
Week 4では、既存フレームワークとの比較を通じて、このフレームワークの特徴をより深く理解していきます。
次回: Day 22「既存フレームワーク(React)との比較」
Week 3 お疲れさまでした!分からないことがあればコメントで質問してください。
Week 4 で既存フレームワークとの比較を学んでいきましょう!