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