想寫好前端,先練好內功

前言

封不平聽在耳裏,暗叫:「到這地步,我再能隱藏甚麼?」仰天一聲清嘯,斜行而前,長劍橫削直擊,迅捷無比,未到五六招,劍勢中已發出隱隱風聲。他出劍愈來愈快,風聲也是漸響,劍鋒上所發出的一股勁氣漸漸擴展,旁觀衆人只覺寒氣逼人,臉上、手上被疾風颳得隱隱生疼,情不自禁的後退,圍在相鬥兩人身周的圈子漸漸擴大,竟有四五丈方圓。泰山派的一個道士在旁說道:「氣宗的徒兒劍法高,劍宗的師叔內力強,這到底怎麼搞的?華山派的氣宗、劍宗,這可不是顛倒來玩了麼?

《笑傲江湖》中的「劍宗餘孽」封不平本想仗着有嵩山派撐腰,一舉奪了華山掌門寶座。可打了半天劍法上佔不了便宜,最後只能使出「狂風快劍」,企圖之內力取勝。可見,任何高明武功若無內功心法相輔,也是徒勞無功。javascript

說回前端,現在的前端技術棧就如同武俠小說中的江湖同樣,各門各派自成一體,可謂「百花齊放」、「百家爭鳴」。html

這邊 React 、Vue 、AngularJS 、JQuery 誰還都談不上能一統江湖。「武林新貴」 Flux 、Redux 、Mobx 們已經忙着爭奪誰是數據流框架老大。Native 端 RN 剛偃旗息鼓,Weex 就大有「 I'm the everywhere 」之勢。連備受爭議的 GraphQL 內部都還有 Apollo、Relay 掐來掐去。前端

常聽到身邊的前端工程師抱怨,上週剛發佈的 XXX 新版本文檔還沒看,今天 YYY 公司又發佈了新框架,到底先學哪一個?其實,不管是哪一種框架哪項技術都是解決實際業務需求的手段、方法,和武林中各門各派的武功招式是同樣的,各有所長,各有各的獨到之處。java

咱們學習技術,除了瞭解具體使用方法,還須要掌握技術背後的設計理念和工程思想,這些背後的東西是咱們技術選型的依據,是架構設計的基礎,是軟件系統的靈魂。這就比如是的武功中「內功心法」催動拳腳刀槍,一招一式,虎虎生風,縱有大敵當前,亦是淡然自若。react

接下來分別談一下三種工程思想,分別是:「開閉原則」、「函數式編程」和「消息機制」,這三種工程思想在後端開發中均有普遍的使用,容易被你們忽略的是目前不少前端技術框架也應用了這三種思想,如下結合具體案例分析,但願可以幫助你們加深對技術自己的理解。git

開閉原則

說到面向對象設計,大部分人腦海中閃過的恐怕都是「23種設計模式」。設計模式表明的是業務場景中總結出的最佳實現方式,屬於實踐的範疇,在其之上是更爲重要的「SOLID」五大原則:編程

  • Single Responsibility Principle 單一責任原則
  • The Open Closed Principle 開放封閉原則
  • The Liskov Substitution Principle 里氏替換原則
  • The Dependency Inversion Principle 依賴倒置原則
  • The Interface Segregation Principle 接口分離原則

SOLID 五大原則的出發點也是軟件工程的終極目標:「高內聚、低耦合」。在後端開發中運用最多的是「依賴倒置原則」,與其相關的設計模式大約有5-6個。以下圖所示:redux

上圖也能夠理解爲從抽象概念到具體實踐的逐步演進。c#

在前端技術框架中,運用最多的是「開放封閉原則」,咱們先來看一下這條原則是怎麼定義的:後端

A software artifact should be open for extension but closed for modification.

翻譯過來就是:軟件系統應當對擴展開放,對修改封閉(感受像沒說)。這裏舉一個簡單的例子來講明開閉原則,先幫助你們理解概念:

public abstract class Shape
{
    public abstract double Area();    
}

public class Rectangle: Shape
{
    public double Width { get; set;}
    public double Height { get; set;}
    public override double Area()
    {
         return Width*Height'
    }
}

public class Circle: Shape
{
    public double Radius { get; set}
    public override double Area()
    {
        return Radius*Radius*PI;
    }
}

public double Area(Shape [] shapes)
{
    doubel area = 0;
    foreach (var shape in shapes)
    {
        area += shape.Area();
    }
    return area;
}

上例中不管場景如何擴展,Area 函數都無需修改,每一個 Shape 類經過繼承接口和多態特性,各自實現面積計算。

總結一下開閉原則就是:軟件系統的核心邏輯都不該該輕易改變,不然會破壞系統的穩定性和增長測試成本。咱們應當創建合適的抽象並統一接口,當業務須要擴展時,咱們能夠經過增長實體類來完成。

接下來咱們看一個「開閉原則」在前端框架中的應用: Ant Design 組件庫中的 Form 表單組件。

和其它組件不一樣,Form 組件並無具體的形態,它更像是一個容器,提供了接入的標準,並提供了校驗、表單提交等功能。繪製表單中的一項以下所示:

<FormItem>
  {getFieldDecorator('userName', {
     rules: [{ required: true, message: 'Please input your username!' }],
     })(<Input prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />}
        placeholder="Username" />
   )}
</FormItem>

Ant Design 組件庫中已經提供個幾乎全部的常見表單組件,如:Select 、Checkbox 、Radio 、Cascader 等,但在實際業務中,咱們仍是會須要設計業務相關的表單項,Form 表單經過統一組件接口的方式知足了這個技術需求,具體規約以下:

自定義或第三方的表單控件,也能夠與 Form 組件一塊兒使用。只要該組件遵循如下的約定:

  1. 提供受控屬性 value 或其它與 valuePropName 的值同名的屬性。
  2. 提供 onChange 事件或 trigger 的值同名的事件。
  3. 不能是函數式組件。

具體例子

這正是「開閉原則」的一個典型實踐案例,即表單核心邏輯(校驗、提交等)保持不變並封裝在 Form 組件中,自定義表單項只須要知足上述三條規約,就能平滑接入到 Form 組件中,和 Ant Design 原生組件契合在一塊兒。

Ant Design 中的 Form 組件經過這樣一個簡潔的設計,完美提供了表單類型頁面的統一解決方案。

函數式編程

隨着人工智能、區塊鏈、AR、VR、新零售等業務場景的出現,產品界面交互正在變得愈來愈複雜,這就對現代的前端開發者提出了更高的要求。如何快速、正確、高效地開發出高複雜度頁面是目前前端技術最須要解決的問題。

函數式編程(如下簡稱 FP )憑藉其高複用性、易測試性和與之帶來的健壯性和簡潔開始逐漸佔據前端技術圈,咱們發現愈來愈多的前端框架以 FP 爲設計核心準則。

咱們先簡單介紹一下 FP,函數式編程的特徵主要包括如下幾個方面:

  • 函數爲「一等公民」
  • 模塊化、組合
  • 引用透明
  • 避免狀態改變
  • 避免共享狀態

JS 語言中的函數不一樣於 Java ,C/C++ 等語言, 能夠被當作參數和返回值進行傳遞,所以天生具有「一等公民」特性。「模塊化、組合」、「引用透明」、「避免狀態改變」、「避免共享狀態」這四個特徵都須要經過特定代碼模式實現。先舉兩個小例子:

找出字符串中率先出現的四個非數字字符?

非 FP 風格
var words = [], count = 0;
var text = str.split(''); 
for (var i = 0; couont < 4, i < text.length; i++) {   
  if(!text[i].match(/[0-9]/)) {
    words = words.concat(text[i]);
    count++;   
  }
}
FP 風格
var words = str.split('').filter(function(x){
  return (!x.match(/[1-9]+/))}).slice(0,4);

第二段代碼中使用的 js 數組方法 filter 和 slice,去掉了 for 循環,代碼更簡潔流暢。在寫具體業務代碼的時候,「模塊化、組合」是 FP 最經常使用的技術,也是最重要的實現功能的手段。

分別實現數組全部元素相加、相乘、相與?

非 FP 風格
function plus(array) {
  var res = array[0];
  for (let i = 1; i < array.length; i++) {
    res += array[i];   
  }
}

function mul(array) {
  var res = array[0];
  for (let i = 1; i < array.length; i++) {
    res *= array[i];
  }
}

function and (array) {
  var res = array[0];
  for (let i = 1; i < array.length; i++) {
    res = res & array[i];
  }
}

plus(array);
mul(array);
and(array);
FP 風格
var ops = { 
  "plus": (x,y)=>x+y,
  "mul" : (x,y)=>x*y,
  "and" : (x,y)=>x&y
}

function operation(op, array) {
  return array.slice(1).reduce(ops[op], array[0]);
} 

operation("plus", array);
operation("mul",  array);
operation("and",  array);

後一段代碼中,使用了 reduce 函數代替了 for 循環,並將數值計算部分做爲模塊提取出來,當有新的計算類型時,只須要在 ops 對象中定義計算過程。這裏就體現了 FP 中「模塊化、組合」的特性。在 FP 風格下,咱們習慣將複雜邏輯切割成一個個小模塊,經過組合這些模塊實現新的業務功能,當有新的需求到來時,咱們儘量地複用已有模塊達到目標。FP 代碼在複用性方面相比 OOD 有明顯的優點。

React 中的 FP 思想

React 框架中,當用戶操做 UI 或者 API 的返回帶來了數據的改變,React 隨即進行 virtual dom diff 計算獲得 dom 的修改指令,對 dom 元素應用修改指令便獲得最新的 html 界面,以下圖所示:

不難發現,React 實際上是應用數據對UI的一種映射,不一樣的數據會映射出不一樣樣式的 UI 界面,咱們能夠得出以下的表達式:

$UI = React(data)$

沒錯,React 的本質實際上是一種函數,而且仍是符合 FP 要求的「引用透明」函數。所謂「引用透明」就是指函數的輸出僅依賴函數參數,不受任何外部環境影響。這樣的函數可測試性強,也很是容易進行組合。

在 React 的體系下,任何組件均可由一個個更小的組件構成,每一個組件都只關心本身的輸入,他們不斷地接受新的數據並輸出對應的新的UI界面。React 框架中經常使用的「高階組件」能夠看做引用透明」函數的組合模式。

在具體業務中咱們一般還須要權衡 React 組件的複用性和開發體驗,若是組件被拆分的過於細,當然複用性會提高,但文件數量會增長,對應的文檔和溝通成本也會增長,這也是 FP 在實踐過程當中常常遭人詬病的點,即複用性提高後帶來的額外開發成本。

消息機制

消息機制是軟件工程中一個廣泛運用的工程思想。「設計模式」中的觀察者模式、Windows 操做系統底層、Spring 框架中的 ApplicationListener 模塊、Objective-C 語言中的函數調用、都是經過消息機制驅動的。

使用消息機制最大的好處在於能夠作到業務模塊間安全解耦,模塊間經過發送消息的方式進行協做,咱們先舉一個後端開發中的例子,下圖是一個簡單的預約系統的建模圖,並無使用消息機制:

在沒有消息機制的狀況下,用戶模塊須要知道訂單模塊的存在,並向起進行接口調用,同理訂單模塊須要向支付模塊進行接口調用。這種設計下模塊間是耦合的。

咱們再來看一下使用消息機制的狀況:

上圖中,不管是客戶下訂單、支付仍是預約都是經過消息的方式傳遞的,每一個模塊都是向一個消息處理器起發消息,同時也監聽消息處理器發送回來的消息。在這種模式下,模塊徹底不知道其它模塊的存在,完全作到了解耦。

在前端業務開發中,咱們常常也會用到 EventEmitter 庫來進行消息傳遞。好比頁面上有兩塊區域,一塊用 React 框架渲染,一塊用 D3 渲染的,當兩塊區域須要數據同步時,就可使用消息機制進行通信,保證頁面數據總體一致。

若是你的業務中有不一樣生命週期的組件,建議採用消息機制進行管理,不只消除了耦合,邏輯關係部分的代碼也集中到了一個文件中,內聚性獲得了提高

使用消息機制的一個附屬產物就是中間件,咱們能夠爲消息定製各類中間件,在中間中完成一些通用邏輯,讓業務代碼更精煉。

說到前端框架中消息機制的運用,固然首推 Redux 框架,在 Redux 框架中,任何數據交互都須要先轉化爲一個 action,由 action 去觸發 reducer 和相關的 middleware 處理 action,改變數據,最終同步到頁面 UI 上,以下圖所示:

關於使用 Redux 的種種利弊,在各大社區中都有很深刻的討論,本文再也不贅述。

總結

「開閉原則」、「函數式編程」、「消息機制」這個三個軟件工程中重要的思想方法比如三套內功口訣,掌握了他們,才能更深入地理解技術框架自己,發揮出技術框架的最大威力。

寫到這裏忽然又想起《天龍八部》中一段:

喬峯眼見旁人退開,驀地心念一動,呼的一拳打出,一招「衝陣斬將」,也正是「太祖長拳」中的招數。這一招姿勢既瀟灑大方已極,勁力更是剛中有柔,柔中有剛,武林高手畢生所盼望達到的拳術完美之境,竟在這一招中表露無遺。

一套平平無奇的「太祖長拳」在喬峯手中盡能有如此氣象!

多少年之後,每當人們聊起金庸,聊起那個武俠世界,想必都會津津有味地回味、談論起聚賢莊中這石破天驚的一拳。

參考

文章可隨意轉載,但請保留此 原文連接
很是歡迎有激情的你加入 ES2049 Studio,簡歷請發送至 caijun.hcj(at)alibaba-inc.com 。
相關文章
相關標籤/搜索