此 HTML 包括以下內容:
- 我們有一個 main 表單,其中有所有的全局輸入和按鈕,還有一個新的表單用于創建一個新任務。請注意,我們使用 form 屬性將元素與表單聯系起來,以避免表單中的元素嵌套。
- template 元素代表一個列表項,它的根元素是另一個表單,代表與特定任務相關的互動數據。當任務被添加時,這個表單將通過克隆模板的內容而被重復。
- 隱藏的輸入表示不直接顯示的數據,但用于樣式設計和選擇。
注意這個 DOM 是如何簡潔的。它沒有在其元素中散布類。它包括應用程序所需的所有元素,以合理的層次結構排列。多虧了隱藏的輸入元素,你已經可以很好地感覺到以后文檔中可能會有什么變化。
這個 HTML 不知道它將如何被樣式化,也不知道它到底與什么數據綁定。讓 CSS 和 JavaScript 為你的 HTML 工作,而不是讓你的 HTML 為某個特定的造型機制工作。這將使你在改變設計時變得更加容易。
最小控制器 JavaScrip
現在我們在 CSS 中已經有了大部分的反應性,在模型中也有了列表處理,剩下的就是控制器的代碼了,也就是把所有的東西固定在一起的“膠帶”。在這個小程序中,控制器的 JavaScript 大約是 40 行。
下面是一個版本,每個部分都有解釋:
import TaskListModel from './model.js';
const model = new TaskListModel(new class {
上面,我們創建了一個新模型。
onAdd(key, value) {
const newItem = document.querySelector('.todo-list template').content.cloneNode(true).firstElementChild;
newItem.name = `task-${key}`;
const save = () => model.updateTask(key, Object.fromEntries(new FormData(newItem)));
newItem.elements.completed.addEventListener('change', save);
newItem.addEventListener('submit', save);
newItem.elements.title.addEventListener('dblclick', ({target}) => target.removeAttribute('readonly'));
newItem.elements.title.addEventListener('blur', ({target}) => target.setAttribute('readonly', ''));
newItem.elements.destroy.addEventListener('click', () => model.deleteTask(key));
this.onUpdate(key, value, newItem);
document.querySelector('.todo-list').appendChild(newItem);
}
當一個項目被添加到模型中,我們在用 UI 中創建其相應的列表項。
在上面的代碼段中,我們克隆了項目 template 的內容,為一個特定的項目分配了事件監聽器,并將新的項目添加到列表中。
注意,這個函數,以及 onUpdate、onRemove 和 onCountChange,都是要從模型中調用的回調。
onUpdate(key, {title, completed}, form = document.forms[`task-${key}`]) {
form.elements.completed.checked = !!completed;
form.elements.title.value = title;
form.elements.title.blur();
}
當一個項目被更新時,我們設置它的 completed 和 title 值,然后 blur(退出編輯模式)。
onRemove(key) { document.forms[`task-${key}`].remove(); }
當從模型中移除一個項時,我們將從視圖中移除其對應的列表項。
onCountChange({active, completed}) {
document.forms.main.elements.completedCount.value = completed;
document.forms.main.elements.toggleAll.checked = active === 0;
document.forms.main.elements.totalCount.value = active + completed;
document.forms.main.elements.activeCount.innerHTML = `${active} item${active === 1 ? '' : 's'} left`;
}
在上面的代碼中,當完成的或活動的項目數量發生變化時,我們設置適當的輸入來觸發 CSS 反應,并格式化顯示計數的輸出。
const updateFilter = () => filter.value = location.hash.substr(2);
window.addEventListener('hashchange', updateFilter);
window.addEventListener('load', updateFilter);
而我們從 hash 片段中更新過濾器(以及在啟動時)。我們在上面所做的只是設置一個表單元素的值:CSS 處理其余部分。
document.querySelector('.todoapp').addEventListener('submit', e => e.preventDefault(), {capture: true});
在這里,我們確保當表單被提交時我們不會重新加載頁面。這一行代碼把這個應用程序變成了一個 SPA。
document.forms.newTask.addEventListener('submit', ({target: {elements: {title}}}) =>
model.createTask({title: title.value}));
document.forms.main.elements.toggleAll.addEventListener('change', ({target: {checked}})=>
model.markAll(checked));
document.forms.main.elements.clearCompleted.addEventListener('click', () =>
model.clearCompleted());
而這就處理了主要的操作(創建、標記所有、清除完成)。
與 CSS 的反應性
完整的 CSS 文件可以供你查看。
CSS 處理了規范中的很多要求(做了一些有利于無障礙的修正)。我們來看看一些示例。
根據規范,“X”(destroy)按鈕只在懸停時顯示。我還添加了一個輔助位,使它在任務被聚焦時可見。
.task:not(:hover, :focus-within) button[name="destroy"] { opacity: 0 }
當 filter 鏈接是當前鏈接時,它會得到一個紅色邊框:
.todoapp input[name="filter"][value=""] ~ footer a[href$="#/"] 。
nav a:target {
border-color: #CE4646;
}
注意,我們可以使用鏈接元素的 href 作為部分屬性選擇器 -- 不需要 JavaScript 來檢查當前的過濾器,并在適當的元素上設置一個 selected 類。
我們還使用了 :target 選擇器,這讓我們不必擔心是否要添加過濾器。
title 輸入的視圖和編輯樣式根據其只讀模式而改變:
.task input[name="title"]:read-only {
…
}
.task input[name="title"]:not(:read-only) {
…
}
過濾(即只顯示活動的和已完成的任務)是用選擇器完成的:
input[name="filter"][value="active"] ~ * .task
:is(input[name="completed"]:checked, input[name="completed"]:checked ~ *),
input[name="filter"][value="completed"] ~ * .task
:is(input[name="completed"]:not(:checked), input[name="completed"]:not(:checked) ~ *) {
display: none;
}
上面的代碼可能看起來有點冗長,用 Sass 這樣的 CSS 預處理程序可能更容易閱讀。但它所做的事情很簡單:如果過濾器處于 active 狀態,而 completed 的復選框被選中,或者相反,那么我們就會隱藏復選框及其同級。
我選擇在 CSS 中實現這個簡單的過濾器,以顯示它能走多遠,但如果它開始變得棘手,那么把它移到模型中是完全有意義的。
總結及要點
我相信,框架為實現復雜的任務提供了方便的方法,而且它們有超越技術的好處,比如使一組開發人員向特定的風格和模式看齊。Web 平臺提供了許多選擇,而采用一個框架可以讓每個人至少部分地在這些選擇上達成一致,這是有價值的。另外,聲明式編程的優雅性也是值得稱道的,而且組件化的大特點也不是我在這篇文章中所處理的。
但請記住,替代模式是存在的,通常成本較低,而且不一定需要較少的開發者經驗。允許自己對這些模式感到好奇,即使你決定在使用框架時從它們中挑選。
模式概述
- 保持 DOM 樹的穩定。它啟動了一個連鎖反應,使事情變得簡單。
- 如果可以的話,依靠 CSS 的反應性而不是 JavaScript。
- 使用表單元素作為表示互動數據的主要方式。
- 使用 HTML template 元素而不是 JavaScript 生成的模板。
- 使用雙向的變化流作為模型的接口。
作者簡介:
Noam Rosenthal,Web 平臺顧問,WebKit 和 Chromium 的貢獻者,標準編輯,也是經驗豐富的 Web 開發者。他的工作主要是在 Web 開發和瀏覽器 / 標準開發之間架起橋梁。
原文鏈接:
https://www.smashingmagazine.com/2022/02/web-frameworks-guide-part2/
-
Web
+關注
關注
2文章
1257瀏覽量
69368 -
框架
+關注
關注
0文章
399瀏覽量
17437 -
編程
+關注
關注
88文章
3596瀏覽量
93610
發布評論請先 登錄
相關推薦
評論