怎樣在不使用框架的基礎上開發一個 Javascript 組件
許多開發者(包括我)犯的一個錯誤是當遇到問題時他們總是自上而下地考慮問題。他們想問題的時候,總是從考慮框架(Framework),插件(Plugin),預處理器(Pre-processors),后處理器(Post-processors),面向對象模式(objected-oriented patterns)等等這些方面出發,他們也可能會從他們以前看過的一篇文章來考慮。而這時如果有一個生成器(Generator)的話,他們當然也愿意使用生成器提供的腳手架(Scaffold)來解決這樣的問題。但是隨著使用所有這些優秀的工具和強大的插件,我們往往忽略了,我們到底要構建什么,以及我們為什么要構建。在大多數場景下,我們實際上并不需要任何 的這些框架!我們在 沒有 使用任何 JavaScript 框架和工具的情況下構建了一個簡單組件實例。這篇文章給想給那些中高級程序員提個醒,其實不用框架和膨脹軟件(Bloatware)也可以做事。當然,這里的經驗和代碼示例對初級工程師們來說也是易懂和實用的。
我們要建立一個公司員工列表(通常我說的是一個最近推文或某事的列表但他們現在需要你建立一個應用訪問他們的 API,挺復雜的)。我們的產品經理想要在公司網站首頁上放上最近員工的列表,并且要做到自動更新。這個列表要包括新員工的照片,名字,所在城市等信息。沒什么夸張的,對吧?那么,在目前情況下,比方說公司首頁是和其他代碼庫是分開的,而且它已經用 jQuery 做了幾個動畫效果。那么,這是我們的假設:
- 一個半自動更新列表
- 單頁面
- 你是這個項目唯一的開發者
- 時間和資源都是無限的
- 這個頁面上已經用了 jQuery
所以你從何處下手呢?你是否立即要用 Angular ?因為你知道你不花時間使用一個 $scope.employees
和 ng-repeat
。你是否要用 React ?因為它在列表中插入員工標簽 很快 。亦或是切換到靜態網頁然后使用 Webpack?然后你就能用 Jade 寫 HTML 用Sass 寫 CSS ?因為說實話誰還會看原始的標簽。不想騙你,最后一個對我 真的 很有吸引力。但是我們真的需要它嗎?正確的答案是 'no' 。這些東西并不能切實解決我們手上的問題。而且他們讓軟件棧方面變得更加令人困惑。想想如果下次另一個工程師,特別是初級工程師來接手這個項目;當另一個工程師只是做較小修改時,你并不想要他被這些花哨功能所困惑。所以,我們簡單組件的代碼是什么樣的呢?
<ul class="employee-list js-employee-list"></ul>
就是它。這就是我們所有開始的地方。你可能注意到我給這個 div 添加的第二個類是以 js-
開始的。如果你不熟悉這種模式的話,這樣做是因為我想向以后的開發者表明這個組件與 JavaScript 關聯。這種方式我們就能夠區分 只是 為 JS 做交互的類和 只是和 CSS 綁定的類。它能讓重構更容易。現在,讓我們最后讓這個列表變得美觀 一點 。(讀者注意:我可能是世界上最糟的設計師)。我更喜歡使用像一種 BEM 和 SMACSS 的 CSS 結構,但是為了這個例子更簡潔,這些名稱和結構就先這樣保留吧:
* { box-sizing: border-box; }
.employee-list {
background: lavender;
padding: 2rem 0.5rem;
border: 1px solid royalblue;
border-radius: 0.5rem;
max-width: 320px;
}
那么現在我給列表添加一些樣式,雖然還沒完成,但這是個過程。現在,增加一個示例員工:
<ul class="employee-list js-employee-list">
<li class="employee">
<!-- 占位圖服務真是很好用 -->
<img src="http://placebeyonce.com/100-100" alt="Photo of Beyoncé" class="employee-photo">
<div class="employee-name">Beyoncé Knowles</div>
<div class="employee-location">Santa Monica, CA</div>
</li>
</ul>
.employee {
list-style: none;
}
.employee + .employee {
padding-top: 0.5rem;
}
.employee:after {
content: ' ';
height: 0;
display: block;
clear: both;
}
.employee-photo {
float: left;
padding: 0 0.5rem 0.5rem 0;
}
棒極了!所以現在我們有一個擁有簡單樣式和布局的一個員工列表。那么,接下來是什么?員工的數量應該可能不只有一個。我們需要自動獲取他們。我們來獲取員工數據:
// 用一個 IIFE 包裹代碼,從而使它們與其他代碼隔離開。
(() => {
// 嚴格模式用來防止錯誤和確保 ES6 特性可用
'use strict'
// 我們使用 jQuery 的 ajax 方法確保代碼簡潔
// 從 randomuser.me 拉取數據 作為我們 'employee API' 的數據源
// (記住這是一個假的推文列表(a fake tweet list))
$.ajax({
url: 'https://randomuser.me/api/',
dataType: 'json',
success: (data) => {
// 成功!我們得到數據!
alert(JSON.stringify(data))
}
})
})()
很棒!我們獲得了員工數據,其間沒有依靠框架和復雜的預處理器,也沒有花兩小時爭論要選用哪個腳手架工具。目前我們使用 alert
函數 來替代測試框架以確保數據符合我們的預期。現在,我們需要通過一些模版解析數據去插入到 .employee-list
中。所以 完成之后然后來制作模版:
$.ajax({
url: 'https://randomuser.me/api/',
// query string parameters to append
data: {
results: 3
},
dataType: 'json',
success: (data) => {
// 成功!我們獲得數據!
let employee = `<li class="employee">
<img src="${data.results[0].picture.thumbnail}" alt="Photo of ${data.results[0].name.first}" class="employee-photo">
<div class="employee-name">${data.results[0].name.first} ${data.results[0].name.last}</div>
<div class="employee-location">${data.results[0].location.city}, ${data.results[0].location.state}</div>
</li>`
$('.js-employee-list').append(employee)
}
})
好極了!現在我們有了一個獲取用戶的腳本,把用戶插入模版中,然后將模版呈現在頁面上。雖然有點馬虎而且只能處理一個用戶。現在到重構的時間了:
// 把員工信息轉換成一塊標簽
function employee_markup (employee) {
return `<li class="employee">
<img src="${employee.picture.thumbnail}" alt="Photo of ${employee.name.first}" class="employee-photo">
<div class="employee-name">${employee.name.first} ${employee.name.last}</div>
<div class="employee-location">${employee.location.city}, ${employee.location.state}</div>
</li>`
}
$.ajax({
url: 'https://randomuser.me/api/',
dataType: 'json',
// 查詢字符串參數
data: {
results: 3
},
success: (data) => {
// 成功! 我們獲得了數據
let employees_markup = ''
data.results.forEach((employee) => {
employees_markup += employee_markup(employee)
})
$('.js-employee-list').append(employees_markup)
}
})
現在你得到了!一個沒有使用框架和任何構建流程的功能完備的小 JavaScript 組件。包含注釋在內它只有 66 行代碼并且完全可以擴展添加一個動畫,連接,分析,之類的功能。查看以下完成的組件:
See the Pen <a href='http://codepen.io/jacopotarantino/pen/MyGVOv/'>MyGVOv</a> by jacopotarantino (<a href='http://codepen.io/jacopotarantino'>@jacopotarantino</a>) on <a href='http://codepen.io'>CodePen</a>.
源代碼 MyGVOv 作者: jacopotarantino (@jacopotarantino) 在 CodePen.
現在,顯然這只是一個非常非常簡單的組件而且可能不能滿足你特定項目的所有需求。如果你保持簡單的想法,你能堅持無框架這個原則做到更多。或者,如果你的需求很多但復雜度較低,可以考慮像 Webpack 這樣的構建工具。構建工具(在這個主題上)并不完全像 框架和插件它們那樣完成事情。構建工具并不會在最后服務用戶的代碼中添加臃腫的東西,它只存在于你的工具箱中。因為我們的目標是從框架中剝離并為我們的使用者創造更好體驗,和對自己來說則是創造更好管理的代碼。Webpack 能處理大量繁雜的事務從而讓你專注于更有意思的事。我在我的 UI Component Generator 用了它,其中還引入了非常小的框架和工具可以讓你去寫沒有冗余的大量功能代碼。當你不用 JavaScript 框架,事情可能很快變得"原始"而且代碼可能變得令人困惑。所以,當你做這些組件時,要考慮一種代碼結構并且堅持它。一致性是確保代碼優雅的關鍵。
記住,最重要的是你一定要測試和給你代碼編寫文檔。 “不寫代碼文檔,等于沒寫” - @mirisuzanne
彩蛋
我做了一次標題黨,而我使用了 jQuery。這只是為了簡潔起見,我并不贊成使用 jQuery,你并不需要它。對于這些好奇,其實可以利用下面的原生代碼來重寫那些超級易懂的代碼。
原生 JavaScript 的 AJAX 請求
不幸地這個代碼沒有任何簡化,但你可以自己用相對少的代碼來實現。
(() => {
'use strict'
// 創建一個新的 XMLHttpRequest。這是在無框架情況下使用 AJAX 的方法
const xhr = new XMLHttpRequest()
// 聲明 HTTP 請求方法和地址
xhr.open('GET', 'https://randomuser.me/api/?results=3')
// in a GET request what you send doesn't matter GET 請求
// in a POST request this is the request body
xhr.send(null)
// 等待 'readystatechange' 狀態改變去觸發 xhr 對象
xhr.onreadystatechange = function () {
//等待 xhr 成功成功返回
if (xhr.readyState !== 4 ) { return }
// 非 200 狀態時輸出錯誤信息
if (xhr.status !== 200) { return console.log('Error: ' + xhr.status) }
// 一切正常!輸出響應
console.log(xhr.responseText)
}
})()
用原生 JavaScript 進行 DOM 插入
現在瀏覽器們基本接受了 jQuery 的選擇器,這個超級簡單。
(() => {
'use strict'
const employee_list = document.querySelector('.js-employee-list')
const employees_markup = `
<li class="employee"></li>
<li class="employee"></li>
<li class="employee"></li>
`
employee_list.innerHTML = employees_markup
})()
就這么簡單!
沒有采用 ES6 特性
除非是的你工作需要,否則我真的不推薦回退到 ES5,下面這些是 ES6 可以代替的。
字符串插值
用 Photo of ${employee}.
替換所有的 'Photo of ' + employee + '.'
let
和 const
這個例子中的 var
關鍵字都可以用 let
和 const
關鍵字替代,但你自己代碼你要當心。
箭頭函數
用 (employee) => {
替換 function (employee) {
。 再提醒一次,這個例子中代碼可以被替代,但是你自己的代碼你要當心。let
, const
,和箭頭函數和 var
和 function
的作用域不同,并且如果你的代碼馬虎,沒有結構化,在它們之間切換可能會破壞你的代碼。
來自:github