Web框架的替代方案分享(中)

电子说

1.3w人已加入

描述

CHACHA 和 HTML 模板

框架提供了它们自己表达可观察列表的方式。现在很多开发者也依赖提供这种功能的非框架库,如 MobX。

通用的可观察列表的主要问题在于它们是通用的。这以性能为代价增加了便利性,而且还需要特殊的开发者工具来调试那些库在后台做的复杂动作。

使用这些库并理解它们的作用是可以的,无论选择什么样的 UI 框架,它们都是有用的,但使用替代方案可能不会更复杂,而且可以避免一些在你试图推出自己的模型时产生的陷阱。

变化通道(或 CHACHA)

CHACHA——也被称为变化通道(Changes Channel)——是一个双向流,其目的是通知意图方向和观察方向的变化。

  • 在意图方向上,UI 将用户意图的变化通知给模型。
  • 在观察方向上,模型将对模型所做的改变通知给 UI,而这些改变需要显示给用户。

这也许是一个有趣的名字,但它不是一个复杂或新颖的模式。双向流在 Web 和软件中随处可见(例如,MessagePort)。在这种情况下,我们正在创建一个双向流,它有一个特殊的目的:向 UI 报告实际的模型变化,并向模型报告意图。

CHACHA 的接口通常可以从应用的规范中导出,而不需要任何 UI 代码。

例如,一个允许你添加和删除联系人并从服务器加载初始列表的应用程序(带有刷新选项)可以有一个 CHACHA,它看起来像这样:

interface Contact {
  id: string;
  name: string;
  email: string;
}
// "Observe" Direction
interface ContactListModelObserver {
  onAdd(contact: Contact);
  onRemove(contact: Contact);
  onUpdate(contact: Contact);
}
// "Intent" Direction
interface ContactListModel {
  add(contact: Contact);
  remove(contact: Contact);
  reloadFromServer();  
}

请注意,这两个接口中的所有函数都是无效的,只接收普通对象。这是故意的。CHACHA 被构建成一个通道,有两个端口来发送消息,这使得它可以在 EventSource、HTML MessageChannel、服务工作者或任何其他协议中工作。

CHACHA 的好处是,它们很容易测试。你发送动作并期待对观察者的特定调用作为回报。

列表项的 HTML 模板元素

HTML 模板是存在于 DOM 中的特殊元素,但不会被显示。它们的目的是生成动态元素。

当我们使用 template 元素时,我们可以避免在 JavaScript 中创建元素和填充它们的所有模板代码。

下面将使用 template 为列表添加名称:

<ul id="names">
  <template>
   <li><label class="name" /><span class="hljs-name"li>
  <span class="hljs-name"template>
<span class="hljs-name"ul>
<script>
  function addName(name) {
    const list = document.querySelector('#names');
    const item = list.querySelector('template').content.cloneNode(true).firstElementChild;
    item.querySelector('label').innerText = name;
    list.appendChild(item);
  }
class="hljs-name"script>

通过使用列表项的 template 元素,我们可以在原始 HTML 中看到列表项——它不是用 JSX 或其他语言“渲染”的。你的 HTML 文件现在包含了应用程序的所有 HTML——静态部分是渲染的 DOM 的一部分,而动态部分在模板中表达,准备在时机成熟时被克隆并追加到文档中。

集大成者:TodoMVC

TodoMVC 是一个 TODO 列表的应用规范,用于展示不同的框架。TodoMVC 模板带有现成的 HTML 和 CSS,帮助你专注于框架。

你可以在 GitHub 资源库中使用这个结果,并且可以获得完整的源代码。

从规范派生的 CHACHA 开始

我们将从规范开始,并使用它来构建 CHACHA 接口:

interface Task {
   title: string;
   completed: boolean;
}


interface TaskModelObserver {
   onAdd(key: number, value: Task);
   onUpdate(key: number, value: Task);
   onRemove(key: number);
   onCountChange(count: {active: number, completed: number});
}


interface TaskModel {
   constructor(observer: TaskModelObserver);
   createTask(task: Task): void;
   updateTask(key: number, task: Task): void;
   deleteTask(key: number): void;
   clearCompleted(): void;
   markAll(completed: boolean): void;
}

任务模型中的函数直接来自规范和用户可以做的事情(清除已完成的任务,将所有任务标记为已完成或正在进行,获得正在进行和已完成的计数)。

请注意,它遵循 CHACHA 的准则。

  • 有两个界面,一个是动作的,一个是观察的。
  • 所有的参数类型都是基元或普通对象(很容易翻译成 JSON)。
  • 所有的函数都返回 void。

TodoMVC 的实现使用 localStorage 作为后端。

该模型非常简单,与关于 UI 框架的讨论没有多大关系。它在需要的时候保存到 localStorage,并在某些情况发生变化时向观察者触发回调,这些变化可能是用户操作的结果,也可能是模型第一次从 localStorage 加载的时候。

精简的、面向表单的 HTML

接下来,我将采用 TodoMVC 模板,并将其修改为面向表单的模板:表单的层次结构,输入和输出元素代表可以用 JavaScript 改变的数据。

我怎么知道某个东西是否需要成为表单元素?作为一个经验法则,如果它与模型中的数据绑定,那么它就应该是一个表单元素。

完整的 HTML 文件是可用的,但这里是其主要部分:

class="todoapp"> <header class="header">

todosclass="hljs-name"h1>
name="newTask"> <input name="title" type="text" placeholder="What needs to be done?" autofocus> class="hljs-name"form> class="hljs-name"header>
class="hljs-name"form> <input type="hidden" name="filter" form="main" /> <input type="hidden" name="completedCount" form="main" /> <input type="hidden" name="totalCount" form="main" /> <input name="toggleAll" type="checkbox" form="main" />

打开APP阅读更多精彩内容

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分