コンポーネント
コンポーネントは Zylix の再利用可能な UI 構成要素です。構造、スタイル、動作をカプセル化し、任意のプラットフォームにレンダリングできる合成可能なユニットです。
コンポーネントタイプ
Zylix は一般的な UI 要素用の組み込みコンポーネントタイプを提供します。
pub const ComponentType = enum(u8) {
container = 0, // div 相当のコンテナ
text = 1, // text/span 要素
button = 2, // クリック可能なボタン
input = 3, // テキスト入力フィールド
image = 4, // 画像要素
link = 5, // アンカーリンク
list = 6, // ul/ol リスト
list_item = 7, // li アイテム
heading = 8, // h1-h6
paragraph = 9, // p 要素
custom = 255, // カスタムコンポーネント
};コンポーネント状態
各コンポーネントはインタラクティブな状態を追跡します。
pub const ComponentState = packed struct {
hover: bool = false, // マウスがコンポーネント上にある
focus: bool = false, // コンポーネントがフォーカスを持つ
active: bool = false, // 押下/クリック中
disabled: bool = false, // インタラクション不可
checked: bool = false, // チェックボックス/ラジオ用
expanded: bool = false, // 展開可能なコンポーネント用
loading: bool = false, // ローディング表示
error_state: bool = false, // エラー表示
};コンポーネントプロパティ
共通プロパティ
pub const ComponentProps = extern struct {
// ID
id: u32 = 0, // ユニークなコンポーネント ID
// スタイル用クラス名
class_name: TextBuffer = std.mem.zeroes(TextBuffer),
class_name_len: u16 = 0,
// テキストコンテンツ
text: TextBuffer = std.mem.zeroes(TextBuffer),
text_len: u16 = 0,
// スタイル参照
style_id: u32 = 0, // デフォルトスタイル
hover_style_id: u32 = 0, // ホバー時
focus_style_id: u32 = 0, // フォーカス時
active_style_id: u32 = 0, // アクティブ時
disabled_style_id: u32 = 0, // 無効時
// レイアウト参照
layout_id: u32 = 0,
};入力プロパティ
pub const ComponentProps = extern struct {
// ... 共通プロパティ ...
// 入力固有
input_type: InputType = .text,
placeholder: TextBuffer = std.mem.zeroes(TextBuffer),
placeholder_len: u16 = 0,
value: TextBuffer = std.mem.zeroes(TextBuffer),
value_len: u16 = 0,
max_length: u16 = 0,
};
pub const InputType = enum(u8) {
text = 0,
password = 1,
email = 2,
number = 3,
search = 4,
tel = 5,
url = 6,
checkbox = 7,
radio = 8,
};見出しプロパティ
pub const ComponentProps = extern struct {
// ... 共通プロパティ ...
// 見出し固有
heading_level: HeadingLevel = .h1,
};
pub const HeadingLevel = enum(u8) {
h1 = 1, h2 = 2, h3 = 3,
h4 = 4, h5 = 5, h6 = 6,
};イベントハンドラ
コンポーネントは複数のイベントハンドラを持つことができます。
pub const EventHandler = struct {
event_type: EventType = .none,
callback_id: u32 = 0, // コールバック検索用 ID
prevent_default: bool = false,
stop_propagation: bool = false,
};
pub const MAX_EVENT_HANDLERS = 8;イベントタイプ
pub const EventType = enum(u8) {
none = 0,
click = 1,
double_click = 2,
mouse_enter = 3,
mouse_leave = 4,
mouse_down = 5,
mouse_up = 6,
focus = 7,
blur = 8,
input = 9,
change = 10,
submit = 11,
key_down = 12,
key_up = 13,
key_press = 14,
};コンポーネントツリー
コンポーネントは階層的なツリー構造を形成します。
pub const Component = struct {
id: u32 = 0,
component_type: ComponentType = .container,
props: ComponentProps = .{},
state: ComponentState = .{},
handlers: [MAX_EVENT_HANDLERS]EventHandler = undefined,
handler_count: u8 = 0,
// ツリー構造
parent_id: u32 = 0,
children: [MAX_CHILDREN]u32 = undefined,
child_count: u16 = 0,
};コンポーネントの作成
コンテナ
fn createContainer(tree: *VTree, class: []const u8) u32 {
var node = VNode.element(.div);
node.props.setClass(class);
return tree.create(node);
}
// 使用例
const container = createContainer(tree, "main-container");ボタン
fn createButton(
tree: *VTree,
text: []const u8,
onclick_id: u32
) u32 {
var node = VNode.element(.button);
node.setText(text);
node.props.on_click = onclick_id;
node.props.setClass("btn");
return tree.create(node);
}
// 使用例
const button = createButton(tree, "クリック", CALLBACK_INCREMENT);入力フィールド
fn createInput(
tree: *VTree,
placeholder: []const u8,
oninput_id: u32
) u32 {
var node = VNode.element(.input);
node.props.setPlaceholder(placeholder);
node.props.on_input = oninput_id;
node.props.setClass("text-input");
return tree.create(node);
}
// 使用例
const input = createInput(tree, "タスクを入力...", CALLBACK_TEXT_INPUT);リスト
fn createList(tree: *VTree, items: []const Item) u32 {
const list_id = tree.create(VNode.element(.ul));
for (items) |item| {
const item_id = createListItem(tree, item);
_ = tree.addChild(list_id, item_id);
}
return list_id;
}
fn createListItem(tree: *VTree, item: Item) u32 {
var node = VNode.element(.li);
// 効率的な更新のためにキーを設定
var key_buf: [32]u8 = undefined;
const key = std.fmt.bufPrint(&key_buf, "item-{d}", .{item.id}) catch "item";
node.setKey(key);
// テキストを追加
const text_id = tree.create(VNode.textNode(item.text));
const item_id = tree.create(node);
_ = tree.addChild(item_id, text_id);
return item_id;
}コンポーネントパターン
複合コンポーネント
シンプルなコンポーネントから複雑なコンポーネントを構築します。
fn createTodoItem(tree: *VTree, todo: Todo) u32 {
// コンテナ
var container = VNode.element(.li);
container.setKey(todo.id_str);
if (todo.completed) {
container.props.setClass("todo-item completed");
} else {
container.props.setClass("todo-item");
}
const container_id = tree.create(container);
// チェックボックス
var checkbox = VNode.element(.input);
checkbox.props.input_type = @intFromEnum(InputType.checkbox);
checkbox.props.on_change = CALLBACK_TOGGLE;
const checkbox_id = tree.create(checkbox);
// テキスト
const text_id = tree.create(VNode.textNode(todo.text));
// 削除ボタン
var delete_btn = VNode.element(.button);
delete_btn.setText("×");
delete_btn.props.on_click = CALLBACK_DELETE;
delete_btn.props.setClass("delete-btn");
const delete_id = tree.create(delete_btn);
// 組み立て
_ = tree.addChild(container_id, checkbox_id);
_ = tree.addChild(container_id, text_id);
_ = tree.addChild(container_id, delete_id);
return container_id;
}条件付きレンダリング
fn createLoadingOrContent(tree: *VTree, loading: bool, content: []const u8) u32 {
if (loading) {
var spinner = VNode.element(.div);
spinner.props.setClass("spinner");
spinner.setText("読み込み中...");
return tree.create(spinner);
} else {
return tree.create(VNode.textNode(content));
}
}リストレンダリング
fn renderTodoList(tree: *VTree, todos: []const Todo, filter: Filter) u32 {
const list_id = tree.create(VNode.element(.ul));
for (todos) |todo| {
// フィルター適用
const show = switch (filter) {
.all => true,
.active => !todo.completed,
.completed => todo.completed,
};
if (show) {
const item_id = createTodoItem(tree, todo);
_ = tree.addChild(list_id, item_id);
}
}
return list_id;
}レンダーコマンド
コンポーネントはプラットフォーム実行用のレンダーコマンドを生成します。
pub const RenderCommandType = enum(u8) {
none = 0,
create_element = 1,
create_text = 2,
update_text = 3,
set_attribute = 4,
remove_attribute = 5,
add_class = 6,
remove_class = 7,
set_style = 8,
append_child = 9,
insert_before = 10,
remove_child = 11,
add_event_listener = 12,
remove_event_listener = 13,
set_property = 14,
};プラットフォームレンダリング
各プラットフォームはコンポーネントを異なる方法で解釈します。
Web (JavaScript)
function applyCommand(cmd) {
switch (cmd.type) {
case 'create_element':
return document.createElement(cmd.tag);
case 'create_text':
return document.createTextNode(cmd.text);
case 'set_attribute':
element.setAttribute(cmd.name, cmd.value);
break;
case 'add_event_listener':
element.addEventListener(cmd.event, callback);
break;
}
}iOS (SwiftUI)
struct ZylixComponent: View {
let component: ComponentData
var body: some View {
switch component.type {
case .container:
VStack { renderChildren() }
case .button:
Button(component.text) { handleClick() }
case .input:
TextField(component.placeholder, text: $text)
case .text:
Text(component.text)
}
}
}Android (Compose)
@Composable
fun ZylixComponent(component: ComponentData) {
when (component.type) {
ComponentType.Container -> Column { renderChildren() }
ComponentType.Button -> Button(onClick = { handleClick() }) {
Text(component.text)
}
ComponentType.Input -> TextField(
value = text,
onValueChange = { handleInput(it) }
)
ComponentType.Text -> Text(component.text)
}
}ベストプラクティス
1. コンポーネントを単一責任に保つ
// 良い例: 単一責任
fn createHeader(tree: *VTree, title: []const u8) u32 { ... }
fn createNavigation(tree: *VTree, items: []NavItem) u32 { ... }
fn createFooter(tree: *VTree, year: u32) u32 { ... }
// 悪い例: モノリシックなコンポーネント
fn createEntirePage(tree: *VTree, everything: Everything) u32 { ... }2. 意味のあるキーを使用
// 良い例: 安定したユニークなキー
node.setKey(item.uuid);
// 悪い例: インデックスベースのキー
node.setKey(std.fmt.bufPrint(&buf, "{d}", .{index}));3. 再利用可能なパターンを抽出
// 良い例: 再利用可能なボタンファクトリ
fn createIconButton(
tree: *VTree,
icon: []const u8,
label: []const u8,
callback: u32
) u32 {
var btn = VNode.element(.button);
btn.props.setClass("icon-btn");
btn.props.on_click = callback;
// ... アイコンとラベルのセットアップ
return tree.create(btn);
}