前端框架自欺欺人米者,TypeScript全無必要?

前端框架的復雜度最近一段時間頻頻遭到質(zhì)疑,引發(fā)了一些吐槽蔓搞,甚至有一篇文章提到:『前端所有主流的框架胰丁,都是在自欺欺人』。本文主要是向前端的初學者介紹前端框架的發(fā)展歷程及設(shè)計思想锦庸,比如為何要引入這樣那樣的“復雜度”?這樣『設(shè)計』有什么好處妻顶?是為了解決什么問題酸员?了解其背后的原因蜒车,我們或許就不會那么多抱怨了讳嘱。

目錄

  • 1 前端框架怎么你了?

  • 2 前端框架的發(fā)展史

  • 3 前端框架為何要引入“復雜度”

  • 4 為什么當下這么多主流框架酿愧?

  • 5 知其然知其所以然

  • 6 題外話:Typescript 引入復雜度了嗎

  • 7 總結(jié)

01 前端框架怎么你了沥潭?

最近 Rich Harris 介紹了 Svelte 5 的新特性 —— runes。這引發(fā)了一位外國小伙的不滿嬉挡,小伙發(fā)表了一篇文章《前端所有主流的框架钝鸽,都是在自欺欺人》對各種主流前端框架進行了一番吐槽。

到底這個 Svelte 5 的 runes 是設(shè)計得多“反人類”庞钢?以至于外國小伙這么無法忍受拔恰。

為此,筆者仔細地閱讀了 Svelte 5 發(fā)布新特性的文章 《Introducing runes》基括。下面一個小節(jié)將講講這個 Svelte 5 的新特性颜懊。https://svelte.dev/blog/runes

1.1 探秘 Svelte 的新特性 runes

原來,這個 runes 的特性是 Svelte 的響應(yīng)式編程的強化版本风皿。眾所周知河爹, Svelte 是目前使用人數(shù)最多的編譯型前端框架。與 Vue桐款, React 基于運行時的數(shù)據(jù)管理方式不同咸这,Svelte 的響應(yīng)式是在編譯期間處理的,這么做讓 Svelte 的渲染性能有比較明顯的優(yōu)勢魔眨,跟原生 JS 性能接近媳维。

大致的原理是這樣的:Svelte 通過魔改了 JavaScript 編譯器,讓 JavaScript 的賦值語句帶有響應(yīng)式的能力遏暴。讓我們看看下面這段 Svelte 的代碼:

<script>  
      let count = 0; 
      const handleClick = () => { 
            count += 1;
       }
</script>
<div> count: {count} <button on:click={handleClick}>inc</button></div>

上面的代碼侨艾,點擊按鈕就能讓 count 遞增,進而讓頁面顯示最新的 count 的值拓挥。這種讓賦值語句帶有響應(yīng)式的魔法唠梨,正是因為 Svelte 的編譯器識別了 “count += 1" 是一個賦值語句,為其生成了響應(yīng)式的邏輯侥啤。

但目前版本的 Svelte 框架還存在一些問題需要解決当叭。比如茬故,當我們把 "let count = 0;"放到一個函數(shù)內(nèi)部蚁鳖, Svelte 就不會給他加上響應(yīng)式的邏輯了磺芭。這就與 Svelte 一開始給我們的變量自動帶有響應(yīng)式的開發(fā)體驗相悖,導致了語句的歧義醉箕,從而提升了開發(fā)的心智負擔钾腺。我們在開發(fā) Svelte 要時刻提醒自己,只有把變量定義在最外層讥裤,才具備響應(yīng)式放棒。

而 Svelte 5 的 runes 實際上就是為了消除這些心智負擔而設(shè)計的。

在 Svelte 5 中己英,只要把 "let count = 0;" 改為 "let count = state(0);"间螟,數(shù)據(jù)就具備響應(yīng)式。這種新的寫法讓所有響應(yīng)式的寫法統(tǒng)一起來损肛,不管你把語句寫在哪里厢破,只要加上state 即可。這實際上是給開發(fā)者進行減負治拿,消除了響應(yīng)式定義的歧義摩泪。

1.2 外國小伙的槽點

而正因為這個改動,變成一個導火索劫谅,點燃了前文說到的外國小伙见坑,發(fā)表了一篇文章對所有主流的前端框架進行批判。

大致看了下外國小伙的文章同波,他有以下一些槽點:

    1. HTML 不是前端框架最佳的選項鳄梅;
    1. 前端框架引入了復雜度問題;
    1. 前端框架編造出的模板語法完全沒必要未檩,用 DOM API 更好戴尸;
    1. 不同框架的模板語法不統(tǒng)一。

這也是目前前端新人會吐槽的點冤狡,隨著前端不斷引入新的框架孙蒙、打包工具、概念等悲雳,很多人表示『學不動了』挎峦。

接下來我們重點從前端框架來看下復雜度增加的原因及利弊,在此之前我們先了解下前端框架的發(fā)展歷史合瓢,弄清楚前端為什么會發(fā)展到現(xiàn)在這樣坦胶。

02 前端框架的發(fā)展史

前端框架從無到有,其實是伴隨著前端開發(fā)的復雜度從簡單到復雜的發(fā)展歷程而演變的。

最早的網(wǎng)頁顿苇,由于瀏覽器功能的局限峭咒,在網(wǎng)頁端并沒有邏輯,是純展示纪岁。包含業(yè)務(wù)邏輯凑队,展示邏輯在內(nèi)的所有邏輯都在后端處理。這個時候前端的職責非常薄幔翰。

后來 Netscape 公司發(fā)明了革命性的 JavaScript 漩氨,讓網(wǎng)頁可以運行程序,這就讓程序員可以把一部分交互邏輯放到網(wǎng)頁端遗增。當時存在一個問題是叫惊,不同瀏覽器廠商對外暴露的 BOM 接口和 JavaScript 語法在細節(jié)上是有差異的,并且能力上并不對齊贡定。導致當時的 web 程序員在開發(fā)同一個邏輯時可都,不得不適配多個瀏覽器的接口缓待。

當時每個程序員都在重復這樣的工作。這個時候渠牲,jQuery 和 Prototype.js 呼之欲出旋炒。它們把底層對瀏覽器的接口調(diào)用都封裝了一層,在不同瀏覽器上可以采用同樣的寫法签杈,解決了程序員反復開發(fā)瀏覽器兼容代碼的問題瘫镇。

這應(yīng)該算得上是最早期的前端框架的形態(tài),但其更像是工具函數(shù)的封裝答姥,它的誕生主要是為了解決瀏覽器兼容性的問題铣除。

直到 DOM 標準, ECMAScript 標準的制定鹦付,各個瀏覽器內(nèi)核開始遵循標準尚粘,最終趨于統(tǒng)一,差異問題逐漸消除敲长,這類前端框架才退出歷史舞臺郎嫁。

隨著前端項目不斷復雜化,面臨著項目規(guī)模不斷變大帶來的模塊管理困難的問題祈噪,這時候支持模塊管理的工具庫應(yīng)運而生泽铛,比如 SeaJS 還有 RequireJS。后面又出現(xiàn)了基于編譯的模塊構(gòu)建工具辑鲤,如 fis盔腔、webpack、vite 等等,進一步優(yōu)化模塊加載弛随、分包等問題澈蝙。

同時通過 MV* (MVC,MVP撵幽,MVVM)設(shè)計模式降低復雜度的框架也不斷涌現(xiàn)灯荧,如 Backbone、Ember盐杂、Knockout逗载、Angular、React链烈、Vue 等等厉斟。

后面隨著瀏覽器能力不斷提升,前端被賦予的職責也越來越多强衡,而開發(fā)的復雜度也隨之提升擦秽。伴隨而來的是,復雜度產(chǎn)生的可維護性低問題漩勤「谢樱基于直接操作 DOM,BOM 的開發(fā)模式越败,沒有運用一定的設(shè)計模式触幼,必然會隨著需求的迭代凸顯維護性低的問題。

所以這個時候誕生的框架究飞,就是為了提升可維護性而產(chǎn)生的置谦。它們帶來了組件的概念,響應(yīng)式數(shù)據(jù)的概念亿傅,模板渲染的概念媒峡。這些設(shè)計模式,幫助我們開發(fā)出封裝性更好葵擎,復用性更強谅阿,隱藏了 DOM 的操作的底層細節(jié),這些特性都大幅降低了項目的復雜度坪蚁。

縱觀前端框架發(fā)展史奔穿,我們可以看到,每個框架的出現(xiàn)都是為了解決當下的一個痛點敏晤,當然框架本身會引入一定的復雜度贱田,但整體來說是利遠大于弊。

03 前端框架為何要引入“復雜度”

接下來嘴脾,我們聊聊現(xiàn)代前端框架帶來的“復雜度”男摧。為什么要引入這些“復雜度”蔬墩,以及這些設(shè)計帶來的好處是什么?

3.1 HTML 模板:隱藏實現(xiàn)細節(jié)耗拓,降低開發(fā)難度

我們知道現(xiàn)代的前端框架基本都采用了類似 HTML 的語法來開發(fā)界面拇颅。并或多或少對這種語法進行擴展,支持條件渲染乔询,循環(huán)渲染樟插,組件渲染等等「偷螅可能剛開始接觸會覺得稍微有點理解成本黄锤。

下文將對比原生的寫法,來找出這種設(shè)計的必要性食拜。

松散 VS 結(jié)構(gòu)化

我們知道直接使用 DOM 開發(fā)鸵熟,通常是使用 document.createElement,appendChild, removeChild 對 DOM 樹進行操作负甸,通過 setAttribue 修改 DOM 的屬性流强。

使用這種原始的 API,我們需要時刻關(guān)注很多 DOM 的增刪改查的細節(jié)呻待,處理起來比較繁瑣打月,也不夠優(yōu)雅。我們寫出來的带污,可能是一堆松散的 DOM API 調(diào)用僵控。

比如我們要實現(xiàn)這么一個功能:界面上有一個方塊和一個按鈕香到,每按下按鈕鱼冀,當方塊是顯示狀態(tài),則隱藏方塊悠就,當方塊是隱藏狀態(tài)千绪,則顯示方塊。

使用原生的 API 實現(xiàn)是這樣的:

<div class="block">a block</div>
<button class="toggle-button">toggle block</button>
<script>
      const block = document.querySelector('.block');
      const toggleButton = document.querySelector('.toggle-button');
      let blockVisible = true;
      toggleButton.addEventListener('click', () => {  
            blockVisible = !blockVisible;  
            block.style.display = blockVisible ? 'block' : 'none';  
      });
</script>

從代碼可以看到梗脾,我們需要對每個要操作的 DOM 定義類名荸型,方便我們拿到他們的引用,需要獲取對 DOM 節(jié)點的引用:document.querySelector('.block')炸茧,對 DOM 事件進行綁定:toggleButton.addEventListener('click', () => {})瑞妇。

我們開發(fā)過程中,不希望去關(guān)注這些重復的細節(jié)梭冠,我們需要更直觀的寫法辕狰。我們希望能直觀地從模板就看出我們這個程序的意圖,比如按鈕點擊了要去執(zhí)行什么邏輯控漠,某個 div 是否有顯示隱藏的狀態(tài)變化蔓倍。我們看看前端框架(Vue) 是怎么實現(xiàn):

<template>
    <div v-if="blockVisible">a block</div>
    <button @click="handleClick">toggle block</button>
</template>

<script setup>
  const blockVisible = ref(true);
  const handleClick = () => {
    blockVisible.value = !blockVisible.value;
  }
</script>

上面的代碼看起來就很簡潔了悬钳,也更結(jié)構(gòu)化了。

只需要改變 v-if 的值偶翅,Vue 就會幫我們處理了 DOM 節(jié)點的“顯示”和“隱藏”默勾。

在 DOM 版本代碼里的三個步驟,定義類名聚谁、獲取引用母剥、綁定事件,在 Vue 里只剩下綁定事件需要我們做形导,而 Vue 這種綁定事件的寫法也更加簡潔媳搪。

框架幫我們監(jiān)聽了狀態(tài)的變化,并自動更新了視圖骤宣,比如上面例子里 的 blockVisible秦爆,我們只要對它賦值,Vue 就會知道更新哪里的視圖憔披,不需要我們記住這個變量關(guān)聯(lián)了哪個 DOM 節(jié)點等限。

@click 幫我們綁定了事件,讓我們直觀的知道按鈕按下芬膝,就要去執(zhí)行一個叫 handleClick 的方法望门。

整個開發(fā)過程,我們不需要關(guān)注 DOM 節(jié)點是怎么操作的锰霜,符合對隱藏細節(jié)的封裝原則筹误。

設(shè)想如果我們在開發(fā)業(yè)務(wù)的過程,還要不斷地考慮怎么操作 DOM癣缅,DOM 和數(shù)據(jù)之間怎么關(guān)聯(lián)厨剪,其實是不符合職責的解耦原則,我們首要關(guān)注的是我們開發(fā)的業(yè)務(wù)邏輯友存, DOM 操作祷膳,UI 狀態(tài)流轉(zhuǎn)交給框架。

所以這個“復雜度”其實是降低了我們開發(fā)的難度屡立,是我們可以更加專注在業(yè)務(wù)邏輯直晨,而且代碼看起來更加結(jié)構(gòu)化了,使得代碼更易開發(fā)膨俐、維護成本都得到了大幅提升勇皇。

注:這里用 v-if 會直接掛載或刪除 DOM 節(jié)點,如果要一比一還原 DOM API 版本的代碼焚刺,只需要改為 v-show

3.2 組件:提升復用性

相信沒有一個現(xiàn)代的前端框架敛摘,能夠脫離組件的概念。那為何要引入組件這個“復雜度”呢檩坚?

直接用 DOM 鋪開來寫着撩,可不可以呢诅福?

答案當然是不行的。就跟我們寫通用的代碼邏輯一樣拖叙,必不可少的就是封裝氓润。如果我們不對重復的邏輯加以封裝,那代碼將會變得冗余薯鳍,導致難以維護咖气。

我們開發(fā)過程中,都會對重復的邏輯進行封裝挖滤,變成函數(shù)崩溪,或者類,通過不斷的拆分斩松、封裝伶唯、解耦,讓我們的代碼時刻保持在一個可維護的狀態(tài)惧盹。

但早期的 DOM 規(guī)范是沒有組件的概念的(注:直到 Web Component 的誕生)乳幸,所有組件復用的邏輯,都需要自己封裝钧椰。

比如我們需要編寫一個經(jīng)典的 Todo list粹断。如果我們使用原生 DOM,是這樣的:

<div class="todo-list">
  <div>
    new item 1
    <button>X</button>
  </div>
  <div>
    new item 2
    <button>X</button>
  </div>
</div>
<button>add item</button>

<script>
    const todoList = document.querySelector('.todo-list');
    const addButton = document.querySelector('button');
    
    addButton.addEventListener('click', () => {
      const todoItem = document.createElement('div');
      const deleteButton = document.createElement('button');
      todoItem.appendChild(deleteButton);
      todoItem.innerText = 'new item';
      todoList.appendChild(todoItem);
    });
</script>

上面的代碼嫡霞,基本沒有什么復用性可言瓶埋, todoItem 的邏輯完全跟 todoList 耦合了。

或許我們可以封裝一下诊沪,讓 todoItem 不與 todoList 耦合养筒。于是我們把代碼改成這樣:

<div class="todo-list">
</div>
<button>add item</button>

<script>
    class TodoItem {
      constructor(content) {
          const todoItem = document.createElement('div');
          const deleteButton = document.createElement('button');
          todoItem.appendChild(deleteButton);
          todoItem.innerText = content;
          this.dom = todoItem;
      }
      appendTo(parent) {
          return parent.appendChild(this.dom);
      }
    }
 
    const todoList = document.querySelector('.todo-list');
    const addButton = document.querySelector('button');
    
    new TodoItem('new item 1').appendTo(todoList);
    new TodoItem('new item 2').appendTo(todoList);

    addButton.addEventListener('click', () => {
      new TodoItem().appendTo(todoList);
    });
</script>

這樣可能會好一些,我們把跟 TodoItem 相關(guān)的邏輯都封裝到 TodoItem 類里娄徊。這樣闽颇,我們不僅可以把 TodoItem 用在 todoList 的場景,也可以用在其他場景寄锐。

如果用現(xiàn)有的前端框架,組件的功能已經(jīng)原生內(nèi)建了尖啡,我們可以開箱即用橄仆,編寫起來更簡潔更優(yōu)雅。

我們是可以按照上面原生的方式去封裝衅斩,但實際的情況遠沒有一個 demo 這么簡單盆顾,我們需要考慮樣式隔離,組件生命周期等等畏梆,現(xiàn)代框架 React您宪、Vue.js 非常好的解決了這些問題奈懒。

我們看看如果用 Vue 怎么寫同樣的邏輯:

todo-item.vue:


<template>
  <div>
    new item
    <button>delete</button>
  </div>
</template>

todo-list.vue:

<div class="todo-list">
  <todo-item v-for="item in todoItems" />
</div>
<button @click="handleClick">add item</button>

<script setup>
    import TodoItem from './todo-item.Vue';
    const todoItems = [];
    const handleClick = () => {
      todoItems.push({});
    };
</script>

這么實現(xiàn),代碼量直接減少 10 行宪巨,同時我們獲得了數(shù)據(jù)響應(yīng)式磷杏,DOM 節(jié)點復用(v-for),樣式隔離等等好處捏卓。

這里 Vue demo 單獨定義了 一個 todo-item.Vue 的組件极祸,可以直接在 todo-list 組件里引用,通過 v-for 語句怠晴,可以遍歷插入 todo-item 組件遥金。我們只需要修改 todoItems 數(shù)組的值,對應(yīng)的視圖就會更新蒜田。

在這些框架里稿械,我們可以把一個組件當成一個 “HTML 標簽”來使用,其實這也是 web components 的思想冲粤。

這樣寫出來的代碼溜哮,通過看 HTML 模板的代碼,就可以很清楚的看出組件的層級關(guān)系色解。

3.3 VDOM/ 編譯器機制:跨平臺

假如我們用原生的 DOM API 寫了一個網(wǎng)頁應(yīng)用茂嗓,但我們需要進一步開拓我們的應(yīng)用市場,我們的應(yīng)用需要作為一個獨立的 App 或者小程序進行發(fā)布科阎。這個時候我們第一時間想到的方法有兩種:第一是重新用 Java述吸,Swift 和小程序框架重寫我們的應(yīng)用,第二種是我們把網(wǎng)頁作為一個內(nèi)嵌頁锣笨,嵌入對應(yīng)的外殼里蝌矛。

第一種研發(fā)成本非常大,第二種無論性能還是體驗都比較一般错英。那是不是魚和熊掌不能兼得呢入撒?

既要研發(fā)成本低,又要性能體驗好椭岩,其實使用現(xiàn)代前端框架是一個合適的方案】蟊埃現(xiàn)代的前端框架折晦,底層基本都是大同小異的設(shè)計思路,不是虛擬 DOM 就是編譯機制。無論是怎么樣的形式抖拦,它們都做到把面向開發(fā)者的接口與面向底層的細節(jié)隔離開了导绷。這種設(shè)計的好處是弄捕,我們開發(fā)的代碼可以具備跨平臺的能力缩筛。

比如 React 有 React Native,Taro锌仅, Vue 有 Weex章钾,Uni-App 等原生運行時墙贱,我們幾乎可以用最小的改動成本,將我們面向網(wǎng)頁開發(fā)的應(yīng)用遷移到移動端或者小程序上面贱傀。

我們可以看看下面這個圖:

就像上面的示意圖惨撇,框架面向開發(fā)者提供了一套抽象的接口,開發(fā)者基于這套接口開發(fā)應(yīng)用窍箍,不會接觸到平臺底層的細節(jié)串纺。

比如,當我們使用 Vue 開發(fā)一個應(yīng)用時椰棘,我們通常是不需要關(guān)注如何操作 DOM纺棺,如何綁定樣式的。只要框架在內(nèi)部對接了多套平臺的 API邪狞,開發(fā)者開發(fā)的應(yīng)用就可以運行在多個平臺上祷蝌,并且做到一次開發(fā)到處運行。

其實我們不需要深入探討每個框架是怎么實現(xiàn)的帆卓,只需要知道巨朦,在框架的設(shè)計中,有這么一套對底層平臺的抽象:把 UI 元素的創(chuàng)建剑令,更新糊啡,刪除等接口抽象出來,然后再針對不同平臺實現(xiàn)對應(yīng)的操作吁津。

下面的偽代碼描述了框架是如何做到:

面向底層的抽象接口:

interface IUIOperations {

面向瀏覽器端的實現(xiàn):

interface IUIOperations {
  createElement(type: ElementType): Element;
  removeElement(ele: Element);
  updateElement(ele: Element);
  setProperty(ele: Element, propName: string; propValue: string);
  removeProperty(...
}

面向原生平臺的實現(xiàn):

class DOMUIOperations implements IUIOperations {
  createElement(type: ElementType): Element {
    return new DOMElement(document.createElement(type.getName()));
  }
  removeElement(ele: Element) {
    ...
  }
  updateElement(ele: Element)
  ...

}

網(wǎng)上有一些文章說虛擬 DOM 的作用之一是提供這種跨平臺的特性棚蓄,實際上按照我們上面所畫的框架架構(gòu)圖,內(nèi)部實現(xiàn)是怎么樣的對是否能跨平臺其實并沒有太大影響碍脏,只要做好對底層平臺的抽象就可以了梭依。這也就是為什么現(xiàn)在編譯型的框架,雖然它并沒有虛擬 DOM典尾,照樣也能跨平臺役拴。

有了這個機制,就可以實現(xiàn)一套代碼同時跑在移動端 App钾埂、PC 應(yīng)用程序河闰、H5 頁面等多端。

3.4 數(shù)據(jù)響應(yīng)式:降低數(shù)據(jù)管理復雜度

早期勃教,我們使用 DOM 開發(fā)應(yīng)用時淤击,遇到數(shù)據(jù)與視圖之間的狀態(tài)同步場景,通常免不了手忙腳亂對 DOM 的一頓操作故源,開發(fā)過程中要時刻關(guān)注每個數(shù)據(jù)關(guān)聯(lián)的 DOM 節(jié)點。

而數(shù)據(jù)響應(yīng)式的誕生汞贸,讓我們開發(fā)中绳军,不需要關(guān)注這些細節(jié)印机。我們只需要操作數(shù)據(jù),框架可以讓視圖可以自動更新门驾。

假設(shè)我們需要在按鈕按下時射赛,將一段文本反轉(zhuǎn)過來,并顯示到頁面上奶是。

<div>
</div>
<button>reverse</button>

<script>
  const div = document.querySelector('div');
  const button = document.querySelector('button');
  let msg = 'hello, world';
  div.innerText = msg;
  button.addEventListener('click', () => {
      msg = msg.split('').reverse().join('');
      div.innerText = msg;
  });
</script>

每當需要往視圖上更新數(shù)據(jù)時楣责,我們都需要對 DOM 進行顯式的修改。

我們再看看如果通過框架的數(shù)據(jù)響應(yīng)式聂沙,上面的程序會是怎么寫的:

<div>
  {msg}
</div>
<button on:click="handleClick">reverse</button>

<script>
  let msg = 'hello, world';
  const handleClick = () => {
      msg = msg.split('').reverse().join('');
  };
</script>

我們發(fā)現(xiàn)秆麸,我們并沒有看到哪一行代碼是顯式在修改視圖的,數(shù)據(jù)與視圖唯一有關(guān)聯(lián)的地方及汉,就是在視圖模板里加入了數(shù)據(jù)變量的引用:<div>{msg}<div>

當我們對數(shù)據(jù)修改時沮趣,框架就可以感知這種修改,并對數(shù)據(jù)所關(guān)聯(lián)的視圖進行刷新坷随。

具體是怎么做到的呢房铭?每個框架的實現(xiàn)都不盡相同。

這里以 Vue 的實現(xiàn)簡單說一下温眉,當 Vue 按照模板首次渲染時缸匪,會收集模板和數(shù)據(jù)變量的關(guān)聯(lián)關(guān)系,相當于視圖訂閱了數(shù)據(jù)變量變化的事件类溢,一旦數(shù)據(jù)發(fā)生變化凌蔬,就會根據(jù)這個關(guān)聯(lián)關(guān)系,找到對應(yīng)的視圖豌骏,并調(diào)用它的更新函數(shù)龟梦。

有了框架幫我們處理好數(shù)據(jù)和視圖的關(guān)聯(lián),我們就不需要自己顯式的管理和操作數(shù)據(jù)關(guān)聯(lián)的 DOM窃躲。我們的心智負擔進一步降低计贰,這讓我們有更多的精力去開發(fā)上層的交互和業(yè)務(wù)邏輯。

04 為什么當下這么多主流框架蒂窒?

上面講了現(xiàn)代框架引入復雜度的好處躁倒,那是否可以一個框架就夠了呢?這些框架做得都是大同小異的事洒琢,為何還需要重復造輪子呢秧秉?

有 React,Vue衰抑,Angular象迎,近期又有了 Svelte, solid,最近又出現(xiàn)了 Qwik砾淌,Astro啦撮。

其實每個框架的誕生也有其背景和其想解決的問題。

接下來我們重點聊下現(xiàn)代框架的發(fā)展歷程汪厨,及每個框架的設(shè)計哲學和對應(yīng)的受眾赃春。

4.1 React 誕生的意義

首先聊聊 React,2011 年前后劫乱, Facebook 的業(yè)務(wù)快速發(fā)展织中,產(chǎn)生大量需求。

這里需要注意一下當時的背景衷戈,這個時期主流的開發(fā)方式還是通過 jQuery 直接操作 DOM狭吼,手動管理 UI 的狀態(tài),自行確保視圖和數(shù)據(jù)之間的狀態(tài)同步脱惰。

隨著需求的不斷增多搏嗡,如果繼續(xù)采用這種傳統(tǒng)的開發(fā)方式開發(fā) UI 交互,必然會帶來后續(xù)維護困難的問題拉一。

在這種背景下采盒,要繼續(xù)疊加需求,只能通過不斷加入開發(fā)人員蔚润,不斷產(chǎn)生大量的冗余的交互邏輯和錯亂的狀態(tài)管理磅氨。這沒有根本解決問題,這個問題需要從設(shè)計層面上來優(yōu)化嫡纠。

React 就是在這樣的背景下誕生烦租,最初只是為了解決 Facebook 內(nèi)部開發(fā)的可維護性低問題。在內(nèi)部實踐取得成功后除盏,再逐步對外推廣叉橱,在確實解決了其他開發(fā)者同樣面臨的痛點后,最終才成為一個主流的框架者蠕。

React 框架的設(shè)計理念之一是極簡主義

從語法角度上看窃祝, React 在傳統(tǒng)的前端技術(shù)棧上,只引入了 jsx踱侣,用于表達虛擬 DOM 的構(gòu)造過程粪小,其他的一切都是原生的 JavaScript。這樣設(shè)計的好處是抡句,降低了使用者的學習成本和心智負擔探膊,讓框架的靈活性極高。比如待榔,我們可以使用原生 Javascript 的 if else 語句表達視圖的條件顯示逞壁,用 for,map 等表達視圖中的循環(huán)列表,而不需要使用特殊的語法猾担。

從庫的職責上看袭灯,React 的核心只有 UI刺下,不包含 store绑嘹,路由等功能,開發(fā)者可以自行選擇合適的第三方庫搭配使用橘茉。

React 的另一個設(shè)計理念是函數(shù)式編程

React 強調(diào)把視圖的渲染更新當做是一個純函數(shù)工腋,盡量在一部分組件里避免副作用。這樣帶來的好處是畅卓,在代碼組織上擅腰,組件的狀態(tài)管理更為內(nèi)聚清晰,在測試上翁潘,組件的可測性更強趁冈。

React 的設(shè)計理念讓 React 使用起來極為的靈活。靈活的好處就是可定制性強拜马,代價缺少約束渗勘。所以使用 React 的開發(fā)用戶要求更高,還有需要配套搭建前端工程化俩莽,建立適合自身述求的開發(fā)約束旺坠。

4.2 為何還有 Vue

既然 React 已經(jīng)解決了當下的問題,為什么 Vue 還有市場呢扮超?讓我們看看 Vue 是怎么走出一條自己的路來的取刃。

早在 2013 年,Vue 是作為尤雨溪的個人實驗作品誕生的出刷。發(fā)布之后璧疗,很快得到一些開發(fā)者的認可。比如馁龟,PHP 框架 Laravel 的作者 Taylor Otwell 表示崩侠,他學習 Vue 的原因是 React 太難學了, Vue 很好入門屁柏,使用起來也很簡單啦膜。這個觀點可以代表一部分最早接觸 Vue 的人。

正如前面所說淌喻,React 當時的設(shè)計理念是極簡主義僧家,大部分操作都通過 JavaScript 來編寫,并且不官方捆綁像狀態(tài)管理裸删,路由等配套的庫八拱。這對于初學者來說并不友好。

早期,web 分工很細肌稻,有專門切圖的清蚀,有專門寫 html 的,專門寫 css爹谭,還有專門寫 JavaScript 邏輯的枷邪。

所以 React 的設(shè)計對于一些不太熟悉 JavaScript 的 Web 開發(fā)者來說,并不友好诺凡,他們更愿意接受類似 HTML 的寫法东揣。

而 Vue 的設(shè)計正好符合他們的口味,他們從傳統(tǒng)的項目腹泌,過渡到 Vue 遠遠要比過渡到 React 來的簡單得多嘶卧。

這就要講到 Vue 的設(shè)計理念之一,漸進式的開發(fā)理念凉袱。大白話就是芥吟,讓框架的初學者更容易接受。

Vue 采用了跟傳統(tǒng) HTML 開發(fā)接近的語法专甩,在同一個文件里钟鸵,通過 template 標簽定義模板,script 標簽定義 JavaScript 邏輯配深,在 style 標簽內(nèi)定義樣式携添。初學者從傳統(tǒng) HTML 開發(fā)轉(zhuǎn)過來,開發(fā)思想的慣性得到了保持篓叶,開發(fā) Vue 就像在開發(fā) HTML 一樣烈掠。

再一個是,Vue 保留了前后端未分離時期缸托,后端模板渲染的那一套左敌,也就是在 HTML 的基礎(chǔ)上擴展條件渲染,循環(huán)渲染的語法俐镐。這讓從舊時代后端模板渲染的那些開發(fā)者感到格外親切矫限,也更容易接受。

Vue 的另一個設(shè)計理念佩抹,開箱即用叼风,俗稱 Vue 全家桶。

這是 Vue 的另外一個殺手锏棍苹,通過捆綁官方的狀態(tài)管理 Vuex无宿,路由 Vue-router,讓用戶免去這些功能的選型困擾枢里,做到開箱即用孽鸡。這樣做的好處是蹂午,讓一部分沒法自行做出合理技術(shù)選型的用戶,可以在官方的推薦下彬碱,被動做出正確的技術(shù)選型豆胸。

除此之外,Vue 還很貼心的設(shè)計了提供了數(shù)據(jù)響應(yīng)式的設(shè)計巷疼,使用者不需要關(guān)注數(shù)據(jù)驅(qū)動視圖的細節(jié)晚胡。官網(wǎng)提供非常完善友好的文檔,并翻譯成多國語言等等皮迟。

Vue 的核心設(shè)計理念可以總結(jié)為:初學者友好向的框架搬泥。正是 Vue 的設(shè)計者意識到,一部分框架雖然設(shè)計思想很先進伏尼,但學習成本卻比較高,阻擋了一部分用戶尉尾。所以 Vue 從易用性角度設(shè)計框架爆阶,不出意外獲得大批被其他框架勸退的開發(fā)者。

4.3 Svelte

隨著 React沙咏,Vue 的廣泛使用辨图,基于虛擬 DOM 構(gòu)建前端框架已經(jīng)成為一種主流的方式。早期對虛擬 DOM 的宣傳是肢藐,可以減少對 DOM 的操作次數(shù)故河,優(yōu)化渲染性能。現(xiàn)在這一說法被推翻了吆豹,按現(xiàn)在的說法是鱼的,虛擬 DOM 是封裝了 DOM 的操作細節(jié),降低開發(fā)的復雜度痘煤。

那虛擬 DOM 是唯一的抽象方式嗎凑阶?

答案是否定的。Svelte 就是那個推翻虛擬 DOM 壟斷的框架衷快。Svelte 創(chuàng)新的提出了基于編譯的方式宙橱,來解決對 DOM 操作的封裝。

為什么 Svelte 要采用編譯來解決這一問題呢蘸拔?

這里需要講到 Svelte 的設(shè)計理念之一:性能優(yōu)先师郑。正是因為 Svelte 設(shè)計的初衷就是做一個輕量級,注重性能的框架调窍,使它拋棄了虛擬 DOM 的方式宝冕。虛擬 DOM 需要重復生成虛擬 DOM 樹,進行 diff 比對陨晶,DOM patch 等操作猬仁,這些都是運行時的性能損耗帝璧。Svelte 的解決之道是,通過把這些操作提前到編譯期來處理湿刽,通過編譯的烁,生成對應(yīng)的命令式語句,直接對 DOM 進行更新诈闺,有效的把計算從運行時轉(zhuǎn)移到編譯期渴庆。在 Svelte 的內(nèi)部,為了追求性能雅镊,還通過位運算做變量的變更標記襟雷。由于 Svelte 沒有傳統(tǒng)意義上的運行時,其框架體積也非常小仁烹,有利于首屏加載耸弄。

Svelte 的另一個設(shè)計理念是降低心智負擔,具體體現(xiàn)在 Svelte 對數(shù)據(jù)響應(yīng)式的設(shè)計上卓缰。傳統(tǒng)的數(shù)據(jù)響應(yīng)式计呈,都需要利用到語言的一些 hack 方法來模擬,使用起來其實不太直觀征唬,存在一定的心智負擔捌显,比如 Vue3 的 ref,需要通過 .value 來取值总寒。而 Svelte 通過編譯技術(shù)扶歪,很好的規(guī)避了這個問題。在 Svelte 里摄闸,變量定義自然就會獲得數(shù)據(jù)響應(yīng)的能力善镰,這是因為,在編譯時贪薪,Svelte 會識別 JavaScript 的賦值語法媳禁,并針對這個語法額外生成響應(yīng)式的代碼。這樣設(shè)計的好處是画切,開發(fā)者可以開發(fā)符合他們認知的 JavaScript竣稽,并且額外獲得數(shù)據(jù)的響應(yīng)式,而背后的細節(jié)由 Svelte 框架幫忙處理霍弹,很好地轉(zhuǎn)移了復雜度毫别。

4.4 各有千秋

前端框架都有自己標榜的核心設(shè)計理念,比如 React 不斷在復用角度深挖典格,發(fā)明了 hooks 的概念岛宦。而 Vue 不斷在易用性的角度深挖,發(fā)明了 setup 寫法耍缴,讓定義響應(yīng)式數(shù)據(jù)砾肺,就跟編寫普通的 JavaScript 一樣簡單挽霉。而后起之秀 Svelte 和 Solid,則是改變了前端框架在處理 DOM 的常規(guī)手段变汪,提出了使用編譯的方式來處理數(shù)據(jù)的響應(yīng)式侠坎,來獲得比虛擬 DOM 方式更好的性能。

他們各有優(yōu)劣裙盾,都解決了一部分人的痛點实胸,大家可以結(jié)合自己團地和業(yè)務(wù)的實際情況,選擇適合自己的框架番官。

4.5 整體來看

前端框架之間的關(guān)系庐完,如果我們把它們合在一起,作為前端發(fā)展歷程來看徘熔,我們會發(fā)現(xiàn)门躯,它們并不是相互排斥的,而是相互借鑒近顷,共同進步的生音。從整體來看,它們是一個進化的共同體窒升,互相吸收彼此好的東西,摒棄自身不好的東西慕匠,最后發(fā)展是趨同的饱须。

在這個過程中,跟不上隊伍的那個台谊,只能被無情的拋棄蓉媳,而不斷創(chuàng)新的,不斷滿足開發(fā)者訴求锅铅,解決痛點的酪呻,會繼續(xù)進化下去。

前端框架除了解決軟件設(shè)計上的問題外盐须,也跟瀏覽器的發(fā)展密切玩荠。

那些炫酷特性的實現(xiàn),離不開瀏覽器的發(fā)展贼邓。比如阶冈,Vue 從最開始使用 defineProperty 來實現(xiàn)響應(yīng)式,到現(xiàn)在使用 proxy 塑径,讓響應(yīng)式的能力更強女坑。

如果一個前端框架僅僅滿足于彌補瀏覽器的不足而存在,那可能在瀏覽器快速發(fā)展的趨勢下统舀,會被很快遺忘匆骗。比如用來適配瀏覽器選擇器 API 的 jQuery劳景,又或者是某個只有組件封裝功能的前端框架,也會被 Web Component 所替代碉就。

只有具備自己的設(shè)計理念盟广,并且不滿足于這種底層基礎(chǔ)的封裝的前端框架,才能有機會加入前端框架的競爭之中铝噩。

05 知其然知其所以然

所以我們遇到任何事情衡蚂,都應(yīng)該知其然知其所以然,了解其背后的原因骏庸,了解其實現(xiàn)原理毛甲,這樣即可以提升我們的認知,也可以幫助我們更好的用好工具具被,讓各種前端框架為我們服務(wù)玻募,解決我們實際場景的問題。

比如知道了框架的原理一姿,可以讓我們寫出更健壯的代碼七咧。

很多時候,框架都會給出一些教程叮叹,如果我們只是學習了教程艾栋,確實是可以開始寫代碼干活了。但假設(shè)我們不知道框架的設(shè)計思想蛉顽,我們不會知道為何要這么寫蝗砾,為何不能那么寫。如果遇到教程里沒有的携冤,我們就無法變通悼粮。

只有深入去理解框架的設(shè)計思想,我們才能在開發(fā)中化繁為簡曾棕,輕松駕馭各種開發(fā)問題的解法扣猫。

06 題外話:Typescript 引入復雜度了嗎

最近一段時間,還有一個話題很熱翘地,就是探討 TypeScript 是否有必要申尤,是不是引入了過多的復雜度,甚至覺得寫類型比寫代碼還更難子眶。

TypeScript 確實引入了一定的復雜度瀑凝, 但卻是前端往嚴謹項目開發(fā)的必然趨勢。

TypeScript 通過給 JavaScript 加上了類型系統(tǒng)臭杰,將 JavaScript 中的語言中弱類型帶來的陷阱大部分都規(guī)避了粤咪,大幅提升了系統(tǒng)健壯性和可維護性。

通常我們使用 TypeScript 會有兩種場景渴杆,一種是開發(fā)業(yè)務(wù)需求寥枝,另一種是開發(fā)庫 / 框架宪塔。

那開發(fā)業(yè)務(wù)需求有必要引入 TypeScript 嗎?還是要看情況囊拜,如果是嚴謹正規(guī)的長周期維護項目某筐,建議是使用,可以避免大量的弱類型語言陷阱冠跷,大幅提升系統(tǒng)的可維護性南誊。如果是比較臨時,生命周期極短的項目蜜托,比如臨時開發(fā)的簡單小需求抄囚,不需要持久迭代的,短期內(nèi)就會下線的橄务,那可以不需要 幔托。

實際上,日常開發(fā)業(yè)務(wù)蜂挪,我們通常只會使用類型定義重挑,頂多用到泛型函數(shù),類型定義和簡單的類型推導棠涮,并不會使用到“Typescript 的類型體操”這種模板元編程的程度谬哀。如果因為學不會類型體操,而否定 Typescript 在項目里的作用严肪,就有些過了玻粪,它們并沒有因果關(guān)系。

再說說 Typescript 在開發(fā)庫 / 框架的場景诬垂,毋庸置疑,主流的項目基本都采用 Typescript 來開發(fā)了伦仍。庫 / 框架本身就是一種嚴謹?shù)捻椖拷峋剑汩_發(fā)的東西是要面向廣大的開發(fā)者的,你有必要保障項目的質(zhì)量充蓝∷矸悖可能有的人會說,也有的庫是用 JavaScript 寫的谓苟,用其他工具來靜態(tài)檢測不就可以了官脓。確實是可以,不過相比之下涝焙,Typescript 的類型系統(tǒng)足夠強大卑笨,開箱即用,不是很特殊的理由仑撞,是不建議去折騰其他的方案赤兴。

07 總結(jié)

本文因一篇國外的吐槽文而起妖滔,里面的觀點錯得比較普遍、典型桶良,筆者感覺有必要為前端框架做一下澄清座舍,于是寫了這篇文章。全文講述了筆者對前端框架的前世今生的發(fā)展歷程陨帆,特別重點闡述了現(xiàn)代前端框架的誕生的背景和設(shè)計理念曲秉,并說明其引入復雜度的原因及收益,如有不同觀點疲牵,歡迎交流探討承二。

原文:https://new.qq.com/rain/a/20231220A058CV00

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市瑰步,隨后出現(xiàn)的幾起案子矢洲,更是在濱河造成了極大的恐慌,老刑警劉巖缩焦,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件读虏,死亡現(xiàn)場離奇詭異,居然都是意外死亡袁滥,警方通過查閱死者的電腦和手機盖桥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來题翻,“玉大人揩徊,你說我怎么就攤上這事∏对” “怎么了塑荒?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長姜挺。 經(jīng)常有香客問我齿税,道長,這世上最難降的妖魔是什么炊豪? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任凌箕,我火速辦了婚禮,結(jié)果婚禮上词渤,老公的妹妹穿的比我還像新娘牵舱。我一直安慰自己,他們只是感情好缺虐,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布芜壁。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪沿盅。 梳的紋絲不亂的頭發(fā)上把篓,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天,我揣著相機與錄音腰涧,去河邊找鬼韧掩。 笑死,一個胖子當著我的面吹牛窖铡,可吹牛的內(nèi)容都是我干的疗锐。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼费彼,長吁一口氣:“原來是場噩夢啊……” “哼滑臊!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起箍铲,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤雇卷,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后颠猴,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體关划,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年翘瓮,在試婚紗的時候發(fā)現(xiàn)自己被綠了贮折。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡资盅,死狀恐怖调榄,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情呵扛,我是刑警寧澤每庆,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站今穿,受9級特大地震影響扣孟,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜荣赶,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鸽斟。 院中可真熱鬧拔创,春花似錦、人聲如沸富蓄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至灭红,卻和暖如春侣滩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背变擒。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工君珠, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人娇斑。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓策添,卻偏偏與公主長得像,于是被迫代替她去往敵國和親毫缆。 傳聞我的和親對象是個殘疾皇子唯竹,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容