1. 程式人生 > >jQuery === 面條式代碼?

jQuery === 面條式代碼?

handle 最重要的 disabled add 選擇 sco 針對 按鈕置灰 完成



技術分享

  自從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. $("button").prop("disabled", true);
  3. // 文本框的值發生變化時
  4. $("textarea").on("input", function() {
  5. // 只要超過一個字符,就
  6. if ($(this).val().length > 0) {
  7. // 按鈕可以點擊
  8. $("button").prop("disabled", false);
  9. } else {
  10. //否則,按鈕不能點擊
  11. $("button").prop("disabled", true);
  12. }
  13. });

  這個代碼本身寫得很累贅,首先,既然一開始那個button是disabled的,那就直接在html上寫個disabled屬性就行了:

  1. <form class="tweet-box">
  2. <textarea name="textMsg"></textarea>
  3. <input disabled type="submit" name="tweet" value="Tweet">
  4. </form>

  第二個要控制按鈕的狀態,其實核心只要一行代碼就行了,不需要寫那麽長:

  1. let form = $(".tweet-box")[0];
  2. $(form.textMsg).on("input", function() {
  3. form.tweet.disabled = this.value.length <= 0;
  4. }).trigger("input");

  這個代碼應該夠簡潔了吧,而且代碼在jQuery和原生之間來回切換,遊刃有余。

  (2)實現剩余字數功能

  如下圖所示:

技術分享

  這個也好實現:

  1. let form = $(".tweet-box")[0],
  2. $leftWordCount = $("#left-word-count");
  3. $(form.textMsg).on("input", function() {
  4. // 已有字數
  5. let wordsCount = this.value.length;
  6. $leftWordCount.text(140 - wordsCount);
  7. form.tweet.disabled = wordsCount <= 0;
  8. });

  (3)添加圖片按鈕

  如下圖所示,左下角多了一個選擇照片的按鈕:

技術分享

  如果用戶選擇了照片,那麽可輸入字數將會減少23個字符,並且Add Photo文案要變成Photo Added。我們先來看下作者是怎麽實現的,如下代碼:

  1. if ($(this).hasClass("is-on")) {
  2. $(this)
  3. .removeClass("is-on")
  4. .text("Add Photo");
  5. $("span").text(140 - $("textarea").val().length);
  6. } else {
  7. $(this)
  8. .addClass("is-on")
  9. .text("? Photo Added");
  10. $("span").text(140 - 23 - $("textarea").val().length);
  11. }

  如果代碼像作者這樣寫的話確實是比較亂,而且比較面條式。但是我們可以優雅地實現。首先,選擇照片一般會寫一個input[type=file]的隱藏輸入框蓋在上傳圖標下面:

  1. <div class="upload-container">
  2. <img src="upload-icon.png" alt>
  3. <span id="add-photo">Add Photo</span>
  4. <input type="file" name="photoUpload">
  5. </div>

  然後監聽它的change事件,在change事件裏面給form套一個類:

  1. $(form.photoUpload).on("change", function() {
  2. // 如果選擇了照片則添加一個photo-added的類
  3. this.value.length ? $(form).addClass("photo-added")
  4. // 否則去掉
  5. : $(form).removeClass("photo-added");
  6. });

  然後就可以來實現文案改變的需求了,把上面#add-photo的span標簽添加兩個data屬性,分別是照片添加和未添加的文案,如下代碼所示:

  1. <span id="add-photo" data-added-text="Photo Added"
  2. data-notadded-text="Add Photo"></span>

  通過form的類結合before/after偽類控制html上的文案,如下代碼所示:

  1. #add-photo:before {
  2. content: attr(data-empty-text);
  3. }
  4. form.photo-added #add-photo:before {
  5. content: attr("data-added-text);
  6. }

  這樣就可以了,我們算是用了一個比較優雅的方式實現了一個文案變化的功能,其中CSS的attr可以兼容到IE9,並且這裏html/css/js相配合,共同完成這個變化的功能,這應該也挺好玩的。

  剩下一個要減掉23字符的需求,只需要在減掉的時候判斷一下:

  1. $(form.textMsg).on("input", function() {
  2. // 已有字數
  3. let wordsCount = this.value.length;
  4. form.tweet.disabled = wordsCount <= 0;
  5. $leftWordCount.text(140 - wordsCount -
  6. //如果已經添加了圖片再減掉23個字符
  7. ($(form).hasClass("photo-added") ? 23 : 0));
  8. });

  然後在選擇圖片之後trigger一下,讓文字發生變化,如下代碼倒數第二行:

  1. /*
  2. * @trigger 會觸發文字輸入框的input事件以更新剩余字數
  3. */
  4. $(form.photoUpload).on("change", function() {
  5. // 如果選擇了照片則添加一個photo-added的類
  6. this.value.length ? $(form).addClass("photo-added") :
  7. // 否則去掉
  8. $(form).removeClass("photo-added");
  9. $(form.textMsg).trigger("input");
  10. });

  這裏又使用了事件的機制,用reac應該基本上都是用狀態state控制了。

  再來看最後一個功能。

  (4)沒有文字但是有照片發推按鈕要可點

  上面是只要沒有文字,那麽發推按鈕不可點,現在要求有圖片就可點。這個也好辦,因為如果有圖片的話,form已經有了一個類,所以只要再加一個判斷就可以了:

  1. $(form.textMsg).on("input", function() {
  2. // 已有字數
  3. let wordsCount = this.value.length;
  4. form.tweet.disabled = wordsCount <= 0
  5. //disabled再添加一個與判斷
  6. && !$(form).hasClass("photo-added");
  7. $leftWordCount.text(140 - wordsCount -
  8. //如果已經添加了圖片再減掉23個字符
  9. ($(form).hasClass("photo-added") ? 23 : 0));
  10. });

  最後看一下,匯總的JS代碼,加上空行和註釋總共只有23行:

  1. let form = $(".tweet-box")[0],
  2. $leftWordCount = $("#left-word-count");
  3. $(form.textMsg).on("input", function() {
  4. // 已有字數
  5. let wordsCount = this.value.length;
  6. form.tweet.disabled = wordsCount <= 0
  7. //disabled再添加一個與判斷
  8. && !$(form).hasClass("photo-added");
  9. $leftWordCount.text(140 - wordsCount -
  10. //如果已經添加了圖片再減掉23個字符
  11. ($(form).hasClass("photo-added") ? 23 : 0));
  12. });
  13. /*
  14. * @trigger 會觸發文字輸入框的input事件以更新剩余字數
  15. */
  16. $(form.photoUpload).on("change", function() {
  17. // 如果選擇了照片則添加一個photo-added的類
  18. this.value.length ? $(form).addClass("photo-added") :
  19. // 否則去掉
  20. $(form).removeClass("photo-added");
  21. $(form.textMsg).trigger("input");
  22. });

  html大概有10行,還有6行核心CSS,不過這兩個比較易讀。再來看一下React的完整版本,作者的實現:

  1. var TweetBox = React.createClass({
  2. getInitialState: function() {
  3. return {
  4. text: "",
  5. photoAdded: false
  6. };
  7. },
  8. handleChange: function(event) {
  9. this.setState({ text: event.target.value });
  10. },
  11. togglePhoto: function(event) {
  12. this.setState({ photoAdded: !this.state.photoAdded });
  13. },
  14. remainingCharacters: function() {
  15. if (this.state.photoAdded) {
  16. return 140 - 23 - this.state.text.length;
  17. } else {
  18. return 140 - this.state.text.length;
  19. }
  20. },
  21. render: function() {
  22. return (
  23. <div className="well clearfix">
  24. <textarea className="form-control"
  25. onChange={this.handleChange}></textarea>
  26. <br/>
  27. <span>{ this.remainingCharacters() }</span>
  28. <button className="btn btn-primary pull-right"
  29. disabled={this.state.text.length === 0 && !this.state.photoAdded}>Tweet</button>
  30. <button className="btn btn-default pull-right"
  31. onClick={this.togglePhoto}>
  32. {this.state.photoAdded ? "? Photo Added" : "Add Photo" }
  33. </button>
  34. </div>
  35. );
  36. }
  37. });
  38. React.render(
  39. <TweetBox />,
  40. document.body
  41. );

  React的套路是監聽事件然後改變state,在jsx的模板裏,使用這些state展示,而jQuery的套路是監聽事件,然後自己去控制DOM展示。React幫你操作DOM,jQuery要自己去操作DOM,前者提供了便利但同時也失去了靈活性,後者增加了靈活性但同時增加了復雜度。

  使用jQuery不少人容易寫出面條式的代碼,但是寫代碼的風格我覺得和框架沒關系,關鍵還在於你的編碼素質,就像你用了React寫class,你就可以說你就是面向對象了?不見得,我在《JS與面向對象》這篇文章提到,寫class並不代表你就是面向對象,面向對象是一種思想而不是你代碼的組織形式。一旦你離開了React的框架,是不是又要回到面條式代碼的風格了?如果是的話那就說明你並沒有沒有掌握面向對象的思想。不過,React等框架能夠方便地組件化,這點是不可否認的。

  還有一個需要註意的是,框架會幫你屏蔽掉很多原生的細節,讓你專心於業務邏輯,但往往也讓你喪失了原生的能力不管是html還是js,而這才是最重要的功底。例如說對於事件,由於所有的事件都是直接綁在目標元素,然後通過state或者其它第三方的框架進行傳遞,這樣其實就沒什麽事件的概念了。所以需要警惕使用了框架但是喪失了基本的前端能力,再如ajax分頁改變url,或者說單頁面路由的實現方式,還有前後退的控制,基本上能夠完整回答地比較少。很多人都會用框架做頁面,但是不懂JS.

jQuery === 面條式代碼?