一个专为 MetaID 协议设计的轻量级、去中心化 SPA 框架
基于 Alpine.js 响应式系统和 Command Pattern 数据驱动架构
IDFramework 是一个专为 MetaWeb(基于 MetaID 协议的全链互联网)应用设计的微框架。它遵循 Cairngorm MVC 架构思想,采用 Command Pattern 实现业务逻辑的原子化,通过 Alpine.js 实现响应式数据绑定。
- ✅ No-Build 架构:纯原生 ES 模块,无需编译步骤
- ✅ 单数据源(Single Source of Truth):所有应用状态集中在 Model 层
- ✅ 响应式系统:基于 Alpine.js 的自动数据绑定
- ✅ 命令模式:业务逻辑原子化,易于复用和测试
- ✅ 组件化:基于 Web Components 的视图层
- ✅ 主题系统:CSS Variables 实现结构与皮肤分离
- ✅ 链上存储:所有文件最终可存储在区块链上
- 响应式系统:Alpine.js (CDN)
- 样式系统:UnoCSS Runtime (CDN)
- 组件系统:Native Web Components (Custom Elements)
- 架构模式:Command Pattern + MVC
整个应用的状态都存储在全局单例 Model 层中,View 直接绑定到 Model 的属性。当 Model 变化时,View 自动更新,消除了组件间传递数据的复杂性。
// Model 层定义
Alpine.store('buzz', {
list: [],
isLoading: false,
error: null,
});
// View 层绑定
<div x-show="$store.buzz.isLoading">Loading...</div>View 层只负责:
- 展示数据:从 Model 绑定数据并渲染
- 派发事件:用户交互时派发事件,不处理业务逻辑
业务逻辑全部放在 Command 中,View 不包含复杂的业务判断。
- View 层:界面展示和用户交互
- Model 层:应用状态管理
- Command 层:业务逻辑原子化
- Delegate 层:服务通信抽象
组件之间不直接通信,而是通过事件来触发系统行为:
View -> Event -> IDController -> Command -> Delegate -> Model -> View (Binding)
每个业务操作(如登录、创建 PIN)都是一个独立的 Command,易于:
- 复用:同一 Command 可在多处使用
- 测试:独立测试业务逻辑
- 维护:修改不影响其他逻辑
┌─────────┐ ┌──────────────┐ ┌──────────┐ ┌─────────────┐ ┌────────┐ ┌─────────┐
│ View │─────>│ IDController │─────>│ Command │─────>│ Business │─────>│ Model │─────>│ View │
│ (组件) │事件 │ (控制器) │ │ (命令) │ │ Delegate │ │ (状态) │绑定 │ (更新) │
└─────────┘ └──────────────┘ └──────────┘ └─────────────┘ └────────┘ └─────────┘
位置:app.js 或通过 IDFramework.initModels() 初始化
内置 Models:
-
wallet:钱包连接状态和用户信息{ isConnected: false, address: null, metaid: null, publicKey: null, network: null, // 'mainnet' | 'testnet' }
-
app:应用级全局状态{ isLogin: false, userAddress: null, }
自定义 Models:应用可以在初始化时注册自定义 Model
IDFramework.init({
buzz: { list: [], isLoading: false },
user: { name: '', email: '' },
});位置:idframework.js - IDFramework.IDController
职责:
- 映射事件到 Command
- 支持异步懒加载 Command 文件
- 管理内置 Command 和文件 Command
内置 Commands:
connectWallet:连接 Metalet 钱包createPIN:创建并广播 PIN 到链上(当前为 Mock 实现)
位置:/commands/ 目录
职责:
- 执行具体的业务逻辑
- 调用 BusinessDelegate 获取数据
- 使用 DataAdapter 转换数据格式
- 更新 Model 层
Command 结构:
export default class MyCommand {
async execute({ payload, stores, delegate }) {
// 1. 业务逻辑处理
// 2. 调用 delegate 获取数据
// 3. 使用 dataAdapter 转换数据
// 4. 更新 stores
}
dataAdapter(rawData) {
// 将原始数据转换为 Model 格式
}
}位置:idframework.js - IDFramework.Delegate
职责:
- 抽象远程服务通信的复杂性
- 处理 HTTP 请求和响应
- 错误处理
- 返回原始 JSON 数据给 Command
位置:/idcomponents/ 目录
职责:
- 展示数据(从 Model 绑定)
- 派发事件(用户交互)
- 不包含业务逻辑
组件规范:
- 命名:以
id-开头(如id-buzz-card.js) - 使用 Shadow DOM
- 使用 CSS Variables 进行主题化
- 添加
part属性支持外部样式覆盖
your-metaid-app/
├── index.html # 应用入口页面
├── app.js # 应用配置和初始化
├── app.css # 全局样式和主题变量
├── idframework.js # 框架核心(必须)
├── idcomponents/ # 视图组件目录
│ ├── id-buzz-card.js
│ ├── id-connect-button.js
│ └── ...
└── commands/ # 业务命令目录
├── FetchBuzzCommand.js
├── PostBuzzCommand.js
└── ...
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>My MetaID App</title>
<!-- Alpine.js -->
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<!-- UnoCSS Runtime -->
<script>
window.__unocss = { theme: {}, shortcuts: {}, rules: [] };
</script>
<script type="module" src="https://cdn.jsdelivr.net/npm/@unocss/runtime"></script>
<!-- 应用样式 -->
<link rel="stylesheet" href="app.css">
</head>
<body>
<!-- 你的应用内容 -->
<div x-data>
<!-- 使用组件 -->
<id-buzz-card content="Hello" author="user123" txid="abc123"></id-buzz-card>
</div>
<!-- 框架核心 -->
<script type="module" src="idframework.js"></script>
<!-- 应用配置 -->
<script type="module" src="app.js"></script>
</body>
</html>// ServiceLocator - 定义服务端点
window.ServiceLocator = {
metaid_man: 'https://manapi.metaid.io',
};
// 自定义 Model
const MyModel = {
data: [],
isLoading: false,
};
// 初始化框架
window.addEventListener('alpine:init', () => {
IDFramework.init({
myModel: MyModel,
});
});
// 注册命令
window.addEventListener('DOMContentLoaded', async () => {
IDFramework.IDController.register('fetchData', './commands/FetchDataCommand.js');
// 启动任务
await IDFramework.dispatch('fetchData');
});:root {
/* 主题变量 */
--id-color-primary: #3b82f6;
--id-bg-card: #ffffff;
--id-text-main: #1f2937;
/* ... */
}
@media (prefers-color-scheme: dark) {
:root {
/* 深色模式变量 */
}
}在 /idcomponents/ 目录下创建组件文件:
// idcomponents/id-my-component.js
class IdMyComponent extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
static get observedAttributes() {
return ['content', 'author'];
}
connectedCallback() {
this.render();
}
render() {
const content = this.getAttribute('content') || '';
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
}
.card {
background-color: var(--id-bg-card, #fff);
padding: var(--id-spacing-md, 1rem);
}
</style>
<div part="card-container" class="card">
${this.escapeHtml(content)}
</div>
`;
}
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
}
customElements.define('id-my-component', IdMyComponent);在 /commands/ 目录下创建命令文件:
// commands/FetchDataCommand.js
export default class FetchDataCommand {
async execute({ payload = {}, stores, delegate }) {
const store = stores.myModel;
store.isLoading = true;
try {
// 使用 BusinessDelegate 获取数据
const rawData = await delegate('metaid_man', '/api/endpoint');
// 使用 DataAdapter 转换数据
const parsedData = this.dataAdapter(rawData);
// 更新 Model
store.data = parsedData;
store.isLoading = false;
} catch (error) {
store.error = error.message;
store.isLoading = false;
}
}
dataAdapter(rawData) {
// 转换原始数据为 Model 格式
return rawData.map(item => ({
id: item.id,
content: item.content,
}));
}
}IDFramework 使用 Hash-based Routing(基于哈希的路由)来实现单页应用(SPA)的页面导航。
- 部署无关性:Hash routing 不需要服务器配置,可以在任何静态文件服务器上运行
- 子目录支持:应用可以部署在
https://example.com/my-app/#/home这样的子目录中 - 简单可靠:不依赖 HTML5 History API,兼容性更好
- 基础路由:
#/home、#/profile、#/buzz - 参数路由:
#/profile/:id、#/buzz/:txid - 查询参数:
#/search?q=keyword
路由状态存储在 Alpine.store('app') 中:
{
currentView: 'home', // 当前视图名称
routeParams: { id: '123' }, // 路由参数
routeHistory: [] // 路由历史(可选)
}1. 程序化导航(推荐)
// 使用框架的路由器
await IDFramework.router.push('/home');
await IDFramework.router.push('/profile/123');2. 直接修改 Hash
// 直接设置 hash,会触发 ROUTE_CHANGE 事件
window.location.hash = '#/home';所有路由变化都由路由 Command(如 MapsCommand 或 RouteCommand)处理:
// commands/MapsCommand.js
export default class MapsCommand {
async execute({ payload, stores, delegate }) {
const route = payload.route || '/home';
const routeMap = {
'/home': 'id-home-page',
'/profile': 'id-profile-page',
'/buzz': 'id-buzz-feed',
};
const componentName = routeMap[route];
if (componentName) {
// 动态加载组件
await IDFramework.loadComponent(`./idcomponents/${componentName}.js`);
// 更新当前视图
stores.app.currentView = componentName.replace('id-', '').replace('-page', '');
}
}
}在 index.html 中使用 Alpine.js 的 x-if 指令根据 currentView 切换视图:
<template x-if="$store.app.currentView === 'home'">
<id-home-page></id-home-page>
</template>
<template x-if="$store.app.currentView === 'profile'">
<id-profile-page></id-profile-page>
</template>
<template x-if="$store.app.currentView === 'buzz-feed'">
<id-buzz-feed></id-buzz-feed>
</template>步骤 1:创建页面组件
在 ./idcomponents/ 目录下创建组件文件,例如 id-new-page.js:
class IdNewPage extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.render();
}
render() {
this.shadowRoot.innerHTML = `
<style>
:host { display: block; }
.page { padding: 1rem; }
</style>
<div class="page">
<h1>New Page</h1>
<p>This is a new page component.</p>
</div>
`;
}
}
customElements.define('id-new-page', IdNewPage);步骤 2:在 app.js 中注册路由
// app.js
const routeMap = {
'/home': 'id-home-page',
'/new-page': 'id-new-page', // 新增路由
};
// 注册路由命令
IDFramework.IDController.register('routeChange', './commands/MapsCommand.js');步骤 3:在路由命令中添加组件加载逻辑
// commands/MapsCommand.js
async execute({ payload, stores, delegate }) {
const route = payload.route;
const componentName = routeMap[route];
if (componentName) {
// 动态加载组件
await IDFramework.loadComponent(`./idcomponents/${componentName}.js`);
// 更新视图状态
stores.app.currentView = componentName.replace('id-', '').replace('-page', '');
}
}步骤 4:在 index.html 中添加模板
<template x-if="$store.app.currentView === 'new-page'">
<id-new-page></id-new-page>
</template>步骤 5:导航到新页面
// 在组件或命令中
await IDFramework.router.push('/new-page');初始化框架,注册内置 Models 和自定义 Models。
参数:
customModels{Object} - 自定义 Model 对象,键为 Model 名称,值为初始状态
示例:
IDFramework.init({
user: { name: '', email: '' },
settings: { theme: 'light' },
});仅初始化 Model 层(通常由 init() 内部调用)。
业务代理方法,用于与远程服务通信。
参数:
serviceKey{string} - ServiceLocator 中的服务键endpoint{string} - API 端点路径options{Object} - Fetch 选项(method, headers, body 等)
返回:Promise<Object> - 原始 JSON 响应
示例:
const data = await IDFramework.Delegate.BusinessDelegate('metaid_man', '/pin/path/list', {
method: 'GET',
});用户相关 API 通信方法,用于获取用户头像、个人信息等。
参数:
serviceKey{string} - ServiceLocator 中的服务键endpoint{string} - API 端点路径options{Object} - Fetch 选项(method, headers, body 等)
返回:Promise<Object> - 原始 JSON 响应
示例:
const avatarData = await IDFramework.Delegate.UserDelegate('metaid_man', '/user/avatar', {
method: 'GET',
});注意:此方法当前未实现,待后续完善。
注册事件到 Command 的映射。
参数:
eventName{string} - 事件名称commandPathOrFunction{string|Function} - Command 文件路径或内置函数
示例:
// 注册文件 Command
IDFramework.IDController.register('fetchBuzz', './commands/FetchBuzzCommand.js');
// 注册内置 Command
IDFramework.IDController.registerBuiltIn('customCommand', myFunction);执行指定事件的 Command。
参数:
eventName{string} - 事件名称payload{Object} - 事件载荷数据stores{Object} - Alpine stores 对象(可选,会自动解析)
返回:Promise<void>
派发事件的便捷方法(供 View 使用)。
参数:
eventName{string} - 事件名称payload{Object} - 事件载荷storeName{string} - 可选的特定 store 名称
示例:
// 在组件中
await IDFramework.dispatch('fetchBuzz', { cursor: 0, size: 30 });连接 Metalet 钱包。
使用:
await IDFramework.dispatch('connectWallet');更新 Stores:
wallet.isConnected = truewallet.address = <用户地址>app.isLogin = trueapp.userAddress = <用户地址>
创建并广播 PIN 到区块链(当前为 Mock 实现)。
参数:
{
content: string, // PIN 内容
path: string, // PIN 路径(默认:'/protocols/simplebuzz')
contentType: string, // 内容类型(默认:'application/json;utf-8')
}使用:
const pinResult = await IDFramework.BuiltInCommands.createPIN({
payload: {
content: 'Hello MetaID',
path: '/protocols/simplebuzz',
},
stores: {
wallet: Alpine.store('wallet'),
app: Alpine.store('app'),
},
});IDFramework 使用 Hash-based Routing(基于哈希的路由)来实现单页应用(SPA)的页面导航。
- 部署无关性:Hash routing 不需要服务器配置,可以在任何静态文件服务器上运行
- 子目录支持:应用可以部署在
https://example.com/my-app/#/home这样的子目录中 - 简单可靠:不依赖 HTML5 History API,兼容性更好
- 基础路由:
#/home、#/profile、#/buzz - 参数路由:
#/profile/:id、#/buzz/:txid - 查询参数:
#/search?q=keyword
路由状态存储在 Alpine.store('app') 中:
{
currentView: 'home', // 当前视图名称
routeParams: { id: '123' }, // 路由参数
routeHistory: [] // 路由历史(可选)
}1. 程序化导航(推荐)
// 使用框架的路由器
await IDFramework.router.push('/home');
await IDFramework.router.push('/profile/123');2. 直接修改 Hash
// 直接设置 hash,会触发 ROUTE_CHANGE 事件
window.location.hash = '#/home';所有路由变化都由路由 Command(如 MapsCommand 或 RouteCommand)处理:
// commands/MapsCommand.js
export default class MapsCommand {
async execute({ payload, stores, delegate }) {
const route = payload.route || '/home';
const routeMap = {
'/home': 'id-home-page',
'/profile': 'id-profile-page',
'/buzz': 'id-buzz-feed',
};
const componentName = routeMap[route];
if (componentName) {
// 动态加载组件
await IDFramework.loadComponent(`./idcomponents/${componentName}.js`);
// 更新当前视图
stores.app.currentView = componentName.replace('id-', '').replace('-page', '');
}
}
}在 index.html 中使用 Alpine.js 的 x-if 指令根据 currentView 切换视图:
<template x-if="$store.app.currentView === 'home'">
<id-home-page></id-home-page>
</template>
<template x-if="$store.app.currentView === 'profile'">
<id-profile-page></id-profile-page>
</template>
<template x-if="$store.app.currentView === 'buzz-feed'">
<id-buzz-feed></id-buzz-feed>
</template>步骤 1:创建页面组件
在 ./idcomponents/ 目录下创建组件文件,例如 id-new-page.js:
class IdNewPage extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.render();
}
render() {
this.shadowRoot.innerHTML = `
<style>
:host { display: block; }
.page { padding: 1rem; }
</style>
<div class="page">
<h1>New Page</h1>
<p>This is a new page component.</p>
</div>
`;
}
}
customElements.define('id-new-page', IdNewPage);步骤 2:在 app.js 中注册路由
// app.js
const routeMap = {
'/home': 'id-home-page',
'/new-page': 'id-new-page', // 新增路由
};
// 注册路由命令
IDFramework.IDController.register('routeChange', './commands/MapsCommand.js');步骤 3:在路由命令中添加组件加载逻辑
// commands/MapsCommand.js
async execute({ payload, stores, delegate }) {
const route = payload.route;
const componentName = routeMap[route];
if (componentName) {
// 动态加载组件
await IDFramework.loadComponent(`./idcomponents/${componentName}.js`);
// 更新视图状态
stores.app.currentView = componentName.replace('id-', '').replace('-page', '');
}
}步骤 4:在 index.html 中添加模板
<template x-if="$store.app.currentView === 'new-page'">
<id-new-page></id-new-page>
</template>步骤 5:导航到新页面
// 在组件或命令中
await IDFramework.router.push('/new-page');- 所有组件文件名必须以
id-开头 - 使用 kebab-case 命名(如
id-buzz-card.js) - 对应的自定义元素标签名与文件名一致(如
<id-buzz-card>)
class IdMyComponent extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
static get observedAttributes() {
return ['prop1', 'prop2']; // 需要观察的属性
}
connectedCallback() {
this.render();
}
attributeChangedCallback(name, oldValue, newValue) {
if (oldValue !== newValue) {
this.render();
}
}
render() {
// 使用 CSS Variables 进行主题化
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
}
.container {
background-color: var(--id-bg-card, #fff);
padding: var(--id-spacing-md, 1rem);
}
</style>
<div part="container" class="container">
<!-- 组件内容 -->
</div>
`;
}
}
customElements.define('id-my-component', IdMyComponent);通过 HTML 属性传递:
<id-buzz-card
content="Hello World"
author="user123"
txid="abc123"
></id-buzz-card>通过自定义事件派发:
// 在组件内部
this.dispatchEvent(new CustomEvent('buzz-clicked', {
detail: { txid: 'abc123' },
bubbles: true
}));组件可以直接调用框架方法:
// 在组件方法中
async handleAction() {
await window.IDFramework.dispatch('myCommand', { data: 'value' });
}- 使用 CSS Variables:所有样式值使用
var(--id-*, fallback)格式 - 提供回退值:确保组件在没有
app.css时也能正常显示 - 添加 part 属性:允许外部通过
::part()选择器覆盖样式
<div part="card-container" class="card">
<!-- 内容 -->
</div>外部样式覆盖:
id-buzz-card::part(card-container) {
border: 2px solid red;
}/**
* MyCommand - 业务逻辑描述
*
* Command Pattern implementation following IDFramework architecture.
*
* @class MyCommand
*/
export default class MyCommand {
/**
* Execute the command
*
* @param {Object} params - Command parameters
* @param {Object} params.payload - Event payload
* @param {Object} params.stores - Alpine stores object
* - wallet: {Object} - Wallet store
* - app: {Object} - App store
* - [customStore]: {Object} - Custom stores
* @param {Function} params.delegate - BusinessDelegate function
* @returns {Promise<void>}
*/
async execute({ payload = {}, stores, delegate }) {
// 1. 业务逻辑处理
// 2. 调用 delegate 获取数据
const rawData = await delegate('serviceKey', '/endpoint');
// 3. 使用 DataAdapter 转换数据
const parsedData = this.dataAdapter(rawData);
// 4. 更新 Model
stores.myModel.data = parsedData;
}
/**
* DataAdapter - 转换原始数据为 Model 格式
*
* @param {Object} rawData - BusinessDelegate 返回的原始数据
* @returns {Object|Array} Model 格式的数据
*/
dataAdapter(rawData) {
// 数据转换逻辑
return transformedData;
}
}- 接收参数:从
payload获取事件数据 - 调用 Delegate:使用
delegate()获取远程数据 - 数据转换:使用
dataAdapter()转换数据格式 - 更新 Model:直接修改
stores中的 Model 状态
async execute({ payload, stores, delegate }) {
const store = stores.myModel;
store.isLoading = true;
store.error = null;
try {
// 业务逻辑
} catch (error) {
store.error = error.message || '操作失败';
console.error('Command error:', error);
} finally {
store.isLoading = false;
}
}metaid-app/
├── index.html # 应用入口页面
├── app.js # 应用配置、ServiceLocator、Model 定义、命令注册
├── app.css # 全局样式、CSS Variables 主题系统
├── idframework.js # 框架核心(必须)
│
├── idcomponents/ # 视图组件目录
│ ├── id-buzz-card.js
│ ├── id-connect-button.js
│ ├── id-post-buzz-panel.js
│ └── ... # 更多组件
│
└── commands/ # 业务命令目录
├── FetchBuzzCommand.js
├── PostBuzzCommand.js
└── ... # 更多命令
| 文件 | 职责 |
|---|---|
index.html |
应用入口,引入依赖,定义页面结构 |
app.js |
应用配置、ServiceLocator、Model 定义、命令注册、启动逻辑 |
app.css |
全局样式、CSS Variables 主题系统、深色模式支持 |
idframework.js |
框架核心:Model 层、Controller 层、BusinessDelegate 层、内置 Commands |
idcomponents/*.js |
视图组件:展示数据、派发事件 |
commands/*.js |
业务命令:业务逻辑、数据转换、Model 更新 |
- 单一职责:每个 Model 只管理一个业务域的数据
- 扁平结构:避免过深的嵌套
- 初始状态:明确定义所有属性的初始值
const UserModel = {
profile: {
name: '',
email: '',
},
preferences: {
theme: 'light',
language: 'zh-CN',
},
};- 原子化:一个 Command 只做一件事
- 可复用:不依赖特定的 View
- 数据转换:使用 DataAdapter 分离数据格式转换逻辑
- 纯展示:组件只负责展示,不包含业务逻辑
- 事件派发:用户交互时派发事件,不直接调用 Command
- 属性验证:在
attributeChangedCallback中验证属性值
- CSS Variables:所有主题相关的值使用变量
- 回退值:始终提供回退值
- Part 属性:为需要外部覆盖的元素添加
part属性
- Model 中存储错误:错误信息存储在 Model 的
error属性中 - View 显示错误:View 绑定到
$store.xxx.error显示错误 - Command 中捕获:在 Command 的 try-catch 中处理错误
- 定义 Model(如需要):在
app.js中添加新的 Model - 创建 Command:在
/commands/中创建业务逻辑 - 注册 Command:在
app.js中注册命令 - 创建组件:在
/idcomponents/中创建视图组件 - 在页面中使用:在
index.html中使用组件
- 创建页面组件:在
/idcomponents/中创建页面组件(如id-new-page.js) - 注册路由:在
app.js的routeMap中添加路由映射(如'/new-page': 'id-new-page') - 更新路由命令:在路由 Command(如
MapsCommand)中添加组件加载逻辑,使用IDFramework.loadComponent()动态加载组件 - 添加模板:在
index.html中添加<template x-if="$store.app.currentView === 'new-page'"><id-new-page></id-new-page></template> - 测试导航:使用
IDFramework.router.push('/new-page')或window.location.hash = '#/new-page'测试导航
- 查看 Model 状态:在浏览器控制台使用
Alpine.store('modelName') - 查看事件:在组件中添加
console.log查看事件派发 - 查看 Command 执行:在 Command 的
execute方法中添加日志
- 懒加载:Command 文件支持懒加载,减少初始加载时间
- 按需更新:只更新 Model 中变化的部分
- 事件防抖:对于频繁触发的事件,使用防抖处理
-
用户操作:在
<id-post-buzz-panel>中输入内容,点击 "Post" -
组件派发事件:
// id-post-buzz-panel.js await IDFramework.dispatch('postBuzz', { content: 'Hello' });
-
Controller 路由:
// idframework.js - IDController IDController.execute('postBuzz', { content: 'Hello' });
-
Command 执行:
// commands/PostBuzzCommand.js async execute({ payload, stores, delegate }) { // 使用内置 createPIN const pin = await IDFramework.BuiltInCommands.createPIN({...}); // 更新 Model stores.buzz.list = [newBuzz, ...stores.buzz.list]; }
-
Model 更新:
// Alpine store 自动更新 Alpine.store('buzz').list = [newBuzz, ...oldList];
-
View 自动更新:
<!-- index.html --> <template x-for="buzz in $store.buzz.list"> <id-buzz-card :content="buzz.content"></id-buzz-card> </template>
A: 在 app.js 的 IDFramework.init() 调用中添加:
IDFramework.init({
myNewModel: { data: [], isLoading: false },
});A: 在 Alpine.js 模板中使用 $store.modelName:
<div x-text="$store.buzz.list.length"></div>A: 在 /commands/ 目录创建文件,实现 execute 方法,然后在 app.js 中注册。
A: 组件可以直接调用 IDFramework.dispatch() 派发事件。
IDFramework 是一个开放的微框架,欢迎贡献:
- 组件贡献:将通用组件提交到组件库
- 命令贡献:将通用业务逻辑封装为 Command
- 文档改进:完善文档和示例
MIT License
IDFramework - 让 MetaWeb 应用开发更简单 🚀