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