自從React/Vue等框架流行之後,jQuery被打上了麵條式程式碼的標籤,甚至成了“過街老鼠”,好像誰還在用jQuery,誰就還活在舊時代,很多人都爭先恐後地擁抱新框架,各大部落格網站有很大一部分的部落格都在介紹新的框架,爭當時代的“弄潮兒”。新框架帶來的新的理念,新的開發方式不可否認帶來了生產效率,但是jQuery等就應該被打上“舊時代”麵條式程式碼的標籤麼?
我們從一篇文章說起:《React.js 的介紹 – 針對瞭解 jQuery 的工程師(譯)》,英文原文是這個《React.js Introduction For People Who Know Just Enough jQuery To Get By》, 這篇文章我好久前就看過,現在再把它翻出來,裡面對比了下jQuery和React分別實現一個發推的功能,作者用jQuery寫著寫著程式碼就亂套了,而用React不管需求多複雜,程式碼條理依舊很清晰。
我們一步步按照原文作者的思路來拆解。
(1)輸入個數為0時,傳送按鈕不可點選
如下圖所示,當輸入框沒有內容時,發推按鈕置灰不可點,有內容點才能點。
作者寫的程式碼是這樣的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// 初始化狀態 $("button").prop("disabled", true); // 文字框的值發生變化時 $("textarea").on("input", function() { // 只要超過一個字元,就 if ($(this).val().length > 0) { // 按鈕可以點選 $("button").prop("disabled", false); } else { //否則,按鈕不能點選 $("button").prop("disabled", true); } }); |
這個程式碼本身寫得很累贅,首先,既然一開始那個button是disabled的,那就直接在html上寫個disabled屬性就行了:
1 2 3 4 |
<form class="tweet-box"> <textarea name="textMsg"></textarea> <input disabled type="submit" name="tweet" value="Tweet"> </form> |
第二個要控制按鈕的狀態,其實核心只要一行程式碼就行了,不需要寫那麼長:
1 2 3 4 |
let form = $(".tweet-box")[0]; $(form.textMsg).on("input", function() { form.tweet.disabled = this.value.length <= 0; }).trigger("input"); |
這個程式碼應該夠簡潔了吧,而且程式碼在jQuery和原生之間來回切換,遊刃有餘。
(2)實現剩餘字數功能
如下圖所示:
這個也好實現:
1 2 3 4 5 6 7 8 9 |
let form = $(".tweet-box")[0], $leftWordCount = $("#left-word-count"); $(form.textMsg).on("input", function() { // 已有字數 let wordsCount = this.value.length; $leftWordCount.text(140 - wordsCount); form.tweet.disabled = wordsCount <= 0; }); |
(3)新增圖片按鈕
如下圖所示,左下角多了一個選擇照片的按鈕:
如果使用者選擇了照片,那麼可輸入字數將會減少23個字元,並且Add Photo文案要變成Photo Added。我們先來看下作者是怎麼實現的,如下程式碼:
1 2 3 4 5 6 7 8 9 10 11 |
if ($(this).hasClass("is-on")) { $(this) .removeClass("is-on") .text("Add Photo"); $("span").text(140 - $("textarea").val().length); } else { $(this) .addClass("is-on") .text("✓ Photo Added"); $("span").text(140 - 23 - $("textarea").val().length); } |
如果程式碼像作者這樣寫的話確實是比較亂,而且比較麵條式。但是我們可以優雅地實現。首先,選擇照片一般會寫一個input[type=file]的隱藏輸入框蓋在上傳圖示下面:
1 2 3 4 5 |
<div class="upload-container"> <img src="upload-icon.png" alt> <span id="add-photo">Add Photo</span> <input type="file" name="photoUpload"> </div> |
1 2 3 4 5 6 7 |
$(form.photoUpload).on("change", function() { // 如果選擇了照片則新增一個photo-added的類 this.value.length ? $(form).addClass("photo-added") // 否則去掉 : $(form).removeClass("photo-added"); }); |
然後就可以來實現文案改變的需求了,把上面#add-photo的span標籤新增兩個data屬性,分別是照片新增和未新增的文案,如下程式碼所示:
1 2 |
<span id="add-photo" data-added-text="Photo Added" data-notadded-text="Add Photo"></span> |
通過form的類結合before/after偽類控制html上的文案,如下程式碼所示:
1 2 3 4 5 6 7 |
#add-photo:before { content: attr(data-empty-text); } form.photo-added #add-photo:before { content: attr("data-added-text); } |
這樣就可以了,我們算是用了一個比較優雅的方式實現了一個文案變化的功能,其中CSS的attr可以相容到IE9,並且這裡html/css/js相配合,共同完成這個變化的功能,這應該也挺好玩的。
剩下一個要減掉23字元的需求,只需要在減掉的時候判斷一下:
1 2 3 4 5 6 7 8 |
$(form.textMsg).on("input", function() { // 已有字數 let wordsCount = this.value.length; form.tweet.disabled = wordsCount <= 0; $leftWordCount.text(140 - wordsCount - //如果已經新增了圖片再減掉23個字元 ($(form).hasClass("photo-added") ? 23 : 0)); }); |
然後在選擇圖片之後trigger一下,讓文字發生變化,如下程式碼倒數第二行:
1 2 3 4 5 6 7 8 9 10 |
/* * @trigger 會觸發文字輸入框的input事件以更新剩餘字數 */ $(form.photoUpload).on("change", function() { // 如果選擇了照片則新增一個photo-added的類 this.value.length ? $(form).addClass("photo-added") : // 否則去掉 $(form).removeClass("photo-added"); $(form.textMsg).trigger("input"); }); |
這裡又使用了事件的機制,用reac應該基本上都是用狀態state控制了。
再來看最後一個功能。
(4)沒有文字但是有照片發推按鈕要可點
上面是隻要沒有文字,那麼發推按鈕不可點,現在要求有圖片就可點。這個也好辦,因為如果有圖片的話,form已經有了一個類,所以只要再加一個判斷就可以了:
1 2 3 4 5 6 7 8 9 10 |
$(form.textMsg).on("input", function() { // 已有字數 let wordsCount = this.value.length; form.tweet.disabled = wordsCount <= 0 //disabled再新增一個與判斷 && !$(form).hasClass("photo-added"); $leftWordCount.text(140 - wordsCount - //如果已經新增了圖片再減掉23個字元 ($(form).hasClass("photo-added") ? 23 : 0)); }); |
最後看一下,彙總的JS程式碼,加上空行和註釋總共只有23行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
let form = $(".tweet-box")[0], $leftWordCount = $("#left-word-count"); $(form.textMsg).on("input", function() { // 已有字數 let wordsCount = this.value.length; form.tweet.disabled = wordsCount <= 0 //disabled再新增一個與判斷 && !$(form).hasClass("photo-added"); $leftWordCount.text(140 - wordsCount - //如果已經新增了圖片再減掉23個字元 ($(form).hasClass("photo-added") ? 23 : 0)); }); /* * @trigger 會觸發文字輸入框的input事件以更新剩餘字數 */ $(form.photoUpload).on("change", function() { // 如果選擇了照片則新增一個photo-added的類 this.value.length ? $(form).addClass("photo-added") : // 否則去掉 $(form).removeClass("photo-added"); $(form.textMsg).trigger("input"); }); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
var TweetBox = React.createClass({ getInitialState: function() { return { text: "", photoAdded: false }; }, handleChange: function(event) { this.setState({ text: event.target.value }); }, togglePhoto: function(event) { this.setState({ photoAdded: !this.state.photoAdded }); }, remainingCharacters: function() { if (this.state.photoAdded) { return 140 - 23 - this.state.text.length; } else { return 140 - this.state.text.length; } }, render: function() { return ( <div className="well clearfix"> <textarea className="form-control" onChange={this.handleChange}></textarea> <br/> <span>{ this.remainingCharacters() }</span> <button className="btn btn-primary pull-right" disabled={this.state.text.length === 0 && !this.state.photoAdded}>Tweet</button> <button className="btn btn-default pull-right" onClick={this.togglePhoto}> {this.state.photoAdded ? "✓ Photo Added" : "Add Photo" } </button> </div> ); } }); React.render( <TweetBox />, document.body ); |
React的套路是監聽事件然後改變state,在jsx的模板裡,使用這些state展示,而jQuery的套路是監聽事件,然後自己去控制DOM展示。React幫你操作DOM,jQuery要自己去操作DOM,前者提供了便利但同時也失去了靈活性,後者增加了靈活性但同時增加了複雜度。
使用jQuery不少人容易寫出麵條式的程式碼,但是寫程式碼的風格我覺得和框架沒關係,關鍵還在於你的編碼素質,就像你用了React寫class,你就可以說你就是物件導向了?不見得,我在《JS與物件導向》這篇文章提到,寫class並不代表你就是物件導向,物件導向是一種思想而不是你程式碼的組織形式。一旦你離開了React的框架,是不是又要回到麵條式程式碼的風格了?如果是的話那就說明你並沒有沒有掌握物件導向的思想。不過,React等框架能夠方便地元件化,這點是不可否認的。
還有一個需要注意的是,框架會幫你遮蔽掉很多原生的細節,讓你專心於業務邏輯,但往往也讓你喪失了原生的能力不管是html還是js,而這才是最重要的功底。例如說對於事件,由於所有的事件都是直接綁在目標元素,然後通過state或者其它第三方的框架進行傳遞,這樣其實就沒什麼事件的概念了。所以需要警惕使用了框架但是喪失了基本的前端能力,再如ajax分頁改變url,或者說單頁面路由的實現方式,還有前後退的控制,基本上能夠完整回答地比較少。很多人都會用框架做頁面,但是不懂JS.