過去的歷史,和一點點將來 UI 框架的幻想

回顧

jQuery

記得 13 年剛開始接觸前端的時候,最興奮的是用原生 JS 手寫了一個左右漂浮的廣告,那時候 jQuery 大行其道,如今回過頭看,這或許是經過 JS 間接操做 UI 的起點,jQuery 的核心部分也是對 Dom 對象的包裝,經過正則匹配來處理 CSS HTML,網上處處都是可隨意 copy 的 jQuery 效果代碼,和各類 jQuery Plugin,那時候的前端並不須要太多的專業計算機知識,理論上覆制粘貼同樣能完成大部分「詭異」的需求,若是要類比下,jQuery 的年代其實還挺超前,好比至今擱淺的 webComponent 和 jQuery Plugin 其實很像,都是經過網絡來分享的包裹了 JS CSS HTML 的一個 Widget,固然內部原理相差甚遠。前端

jQuery 不算是個 UI 框架,可是它開啓了對 Dom 的間接操做,這是現代 UI 框架的基礎理念,影響深遠vue

Handlebar

隨着需求的日益複雜,愈來愈多的需求須要經過響應後端數據來渲染網頁,經過 jQuery 來響應後端數據操做 Dom 變得愈來愈低效和難以維護,因而 Handlebar 橫空出世,不少年輕的前端開發們可能沒有聽過這個模板框架,但在 14 年的時候它但是大名鼎鼎,經過 Handlebar 你能夠提早書寫須要更新的 Dom 模板,大大提升了操做 Dom 的效率react

Angular 1.0

不得不說 angular 是個跨時代的產物,從某種意義上來說算是第一個 UI 框架也不爲過,相對於開發 SPA 須要使用 Dom 操做庫,業務邏輯處理框架好比 Backbone,加上一個好用的模板處理框架好比 Handlebar,angular 第一次把這些特性整合到一塊兒,還不是雜糅的那種而是很是完整的整合成一個新東西,這是集 UI框架/模塊依賴/打包工程於一體,因此可想而知,出來就大火,不過從 2020 往回看 angular 1.0 的大一統思想過於大包大攬,加上做者本身都以爲喪心病狂的髒檢查機制,以致於後期 API 更新乏力,隨着 React 的崛起,逐漸淡出。ios

React

15年的時候,React 忽然就火了起來,最核心要屬 virtualdom,藉助 virtualdom,在渲染性能上的出色表現,React 開始嶄露頭角,並且其函數式的編程哲學也很是吸引人,那一段時間很是多的前端愛好者都致力於將函數式編程引入 JavaScript 的世界,加上 JavaScript 自己極強的可塑性,函數一等公民的底子,讓一貫在業務開發領域偏門的函數式編程走入大衆視野,然後的 Redux 的出現,那一場時間旅行的精彩演講更是將 immutable,thuck 等函數式概念推廣的深刻人心,以致於那一段時間無論 Redux 寫起來有多繁瑣,可是我都致力於把全部邏輯都往上面搬,彷彿若是不這麼作,代碼就很差維護了同樣web

事實上,直到 Hooks 出現以前,React 的函數式編程哲學都有點不三不四,基於 class component 構建的複雜應用,綁上 Redux 這個函數式狀態管理庫,怎麼看怎麼彆扭,加上 HOC 冗長的調用鏈,寫起來還真是有點考驗耐心,因此纔會有 Dva 等基於 Redux 的 platform 來下降其編寫代碼的成本,但本質上也是將 Redux 經過一頓包裝猛如虎,從函數式變成面向對象的方式,引入 module 等概念來矯正這種彆扭編程

但 React 是以函數式而生的,包括 HOC 都是爲了將 class component 變得更函數化一點兒而提出來的,直到開發團隊設計了 Hooks,這個以去掉命令式中條件判斷,內部採用閉包鎖定每一次的做用域,去變量化等方式來讓 React 完全函數化,面對新的 React,此時史無前例的接近一切皆函數的概念,但同時也讓這種「彆扭」達到了頂峯,社區大量的人吐槽沒法好好的寫代碼,不管是 useMemo 仍是 useCallback 都讓習慣了面向對象風格和命令式代碼的開發者無所適從,若是說之前還只是用 immutable 來避免對象的可變性帶來的麻煩,但大部分時候咱們仍是在使用 this,經過對象引用來共享數據,而如今就變成了咱們得忘記面向對象,忘記命令式,若是你想繞過 Hooks 的常量性,你得用 ref 來維持對一個可變對象的引用,一切彷佛都倒過來了,咱們須要從新學習如何編寫代碼,Hooks 帶來的心智成本幾乎是顛覆性的,但說實話我本人仍是很喜歡函數式的,也很是讚歎這種巧妙的設計,不過以 React 這種設計方式,估計對喜歡面向對象風格的人來講怎麼看都是彆扭吧。axios

Vue

我寫過一點 Vue,網上總說 Vue 抄襲 React,而後尤雨溪忙着四處滅火,我想持此見解的人應該很多,不過說句公道話,開源技術自己就是彼此借鑑來發展的,只不過 Vue 的名氣大因此樹大招風,從技術層面講,我以爲 Vue 和 React 是徹底兩種不一樣的設計初衷,即使互相有彼此的借鑑但背後的設計哲學是徹底不同的,和 React 的函數式編程哲學不一樣的是 Vue 我以爲是沿襲了 angular 1.0 中的設計,實際上是比較正統的面向對象風格,包括響應式數據驅動,經過操做數據來控制 UI,這些在 angular 1.0 中其實都有體現,模板的指令化也是命令式的風格而不是函數式,你看 React 就歷來沒內置 IF 啥的組件,因此 Vue 可以擁有如此高的人氣,也是其面向對象的風格更容易被廣大開發所接受,畢竟咱們都是學 C 出身的,大學裏也受過面向對象編程的教育,底子在那裏,確實更容易接受這種編程風格,其實對於這兩種編程風格,我卻是以爲正好是個太極,彼之長乃吾之短也後端

舉個例子,假如咱們要開發一個用戶的 profile,從面相對象的角度來思考,咱們可能會這麼寫數組

class UserProfile extends Widget {
    async constructor(){
        const profile = await axios.get('user/profile')
        this.email = profile.email
        this.name = profile.name
        this.phone = profile.phone
        // other propotype
    }
    get name(){
        return 'username:' + this.name
    }
    get email(){
        return emailHandler(this.email)
    }
    set name(localNameString){
        this.name = nameValid(localNameString)
    }
    @axios('pose','user/profile/upload')
    upload(){
        return {
            name:this.name,
            email:this.email,
            phone:this.phone
        }
    }
    render(){
        return {
            `<div class="name">{{name}}</div> <div class="email">{{email}}</div> <div class="phone">{{phone}}</div> `
        }
    } 
}
複製代碼

把目前能用的特性都用上, 面向對象的核心是一切皆對象, 對象是最小單位, 經過繼承和混入以對象爲核心來構建應用, 由於在設計上最小代碼單位是 class, 因此就不會出現將屬於類的邏輯和屬於函數的邏輯混用的狀況, 在 JavaScript 這種靈活性極強的語言中, 咱們很容易將業務邏輯書寫在 class 之外的地方, 而不是像上面的示例同樣將一切封裝到類裏, 事實上在 Hooks 出現以前 React 就是這種狀況, 基於 class 的 component 和一堆工具函數或者函數級別的代碼混用, 最小單位不一致, 混合風格編程致使的邏輯複用性很可貴到保障, HOC 算是一種折中方案, 可是和 render props 同樣隨着業務代碼的膨脹, 很是容易陷入深層嵌套, 愈來愈難以維護, 函數式的 React 急需一種函數式的解決方案將邏輯函數化, 完全擺脫面向對象帶來的困擾, 因而 Hooks 來了, 從某種意義上講, Hooks 像個英雄.瀏覽器

有了 Hooks, 咱們應該試着忘記面向對象, 對你來講, 函數是最小單位, 沒有屬性, 也沒有方法, 一切都是函數鏈運算的結果, 沒有變量也沒有條件運行, 循環只能用遞歸, 沒有 for 循環, 在這些約束下, 上面那個例子若是用 Hooks 加函數式的思惟來寫的話

function upload(profile){
    axios.post('user/profile', profile)
}
function useSetProfile(profile){
    const [profile, setProfile] = useState([{
        name:profile.name,
        email:profile.email,
        phone:profile.phone
    }])
    return setProfile
}
asnyc function useSyncUserProfile(callback,deps){
    useEffect(async ()=>{
        const profile = await axios.get('user/profile/upload')
        callback(profile)
    },deps)
}
function userProfileWidget(profile){
    const setProfile = useSetProfile({
        name:'jaka',
        email:'1123@qq.com',
        phone:'1123123'
    })
    useSyncUserProfile(setProfile,[profile])
    function onBtnClick(){
        setProFile(profile)
        upload()
    }
    return render(
        `<div class="name">{{name}}</div> <div class="email">{{email}}</div> <div class="phone">{{phone}}</div> <button onClick={() => onBtnClick()}></button> `
    )
}
複製代碼

相比第一個類的例子, 第二個例子中, 咱們的思考單位聚焦在"函數"上, 沒有對象五臟俱全的完整性, 對於一個函數來講只有入參/出參, 這樣的好處天然顯而易見的是更細的粒度, 更容易測試, 不過缺點也一樣明顯, 對於一個 function component 來講要具有自說明性, 在函數命名上須要制定相應的規範, 不像面向對象類/屬性/方法自然的協同概念, 函數這個概念對於描述真實世界來講缺失太單調了些.

咱們再看看 Vue3.0 給出的一種編程風格, 事實上在 Vue3.0 柔和了函數式, 面向對象兩種風格, 而且還加入了響應式來自動觸發 getter/setter, 帶一點流式編程的味道

<template>
  <div class="name">{{name}}</div>
  <div class="email">{{email}}</div>
  <div class="phone">{{phone}}</div>
  <button @click="onBtnClick">
  </button>
</template>
<script>
  import { reactive, computed } from 'vue'
  function uplaod(profile){
      axios.post('user/profile/upload', profile)
  }
  function useSyncUserProfile(state){
      watchEffect(async ()=>{
        const profile = await axios.get('user/profile')
        state.name = profile.name
        state.email = profile.email
        phone = profile.phone
      })
  }
  export default {
    setup() {
      const state = reactive({
        name: 'jaka',
        email:'1123@qq.com',
        phone: 1833333,
      })
      onMounted(){
        useSyncUserProfile(state)
      }
      function onBtnClick() {
        upload(state)
      }

      return {
        state,
        onBtnClick,
      }
    },
  }
</script>
複製代碼

Vue 在引入函數式 API 的基礎上並無增長相似 React 那樣的強約束, 依然保有生命週期, 從大結構上來看像一個函數組合成的對象, 能夠算是對象函數吧...

因此 Vue 不像 React 那樣設計的很是完全, 糅合了面向對象可描述性和函數式的細粒度組合性, 不過 JavaScript 自己並非響應式的, 因此 Vue 也有它自身的問題, 在 2.0 是沒法內部自動劫持數組, 沒法對對象新增屬性自動劫持, 3.0 的 proxy 解決了這個問題, 不過帶來的新問題就是一旦響應式對象被解構一切就 over 了, 從這個角度看, Vue 核心依然是面向對象的, 和 React 徹底不一樣, 他的 Hooks 也是創建在 JavaScript 函數即對象的基礎上, 而不是像 React 那樣使用了函數閉包的特性.

不過不管是 React 仍是 Vue, 我以爲函數式 API 的引入其實都增長了框架的上手門檻, 這個上手是指能好好寫....你得了解許多新的底層細節, 遵循一些約定, 而不像向過去那樣只要把一切交給框架, 框架就會幫你作好代碼級別的約束

雖然這些都仍是剛剛出爐的熱乎概念, 但都是過去了, 本文到這裏難免要開始作些 YY 的幻想, 將來幾年 UI 框架會變成什麼樣子呢?

幻想

將函數堅持到底的 React

目前 React 的 Concurrent 已經在實驗室裏了, 始終堅持函數式的回報就是, 一切皆函數很是有利於將渲染顆粒度控制到堆棧級, 畢竟 JavaScript 堆棧就是函數堆棧, 利用瀏覽器的一些魔法 API 好比 requestidlecallback, 充分利用 cpu 時間片的間隙見縫插針的往裏面塞可執行函數, 將 60fps 渲染變得更簡單, 固然這不是幻想這只是展望, 畢竟已經在路上了, 若是再日後想想, 咱們都知道在最近火熱的 ServerLess 常常會提到 函數即服務 的概念, 微服務被函數所替代, 因此後端都函數即服務了, 若是前端實現了函數即渲染...感受這是先後函數大統一呀, 因此將來的應用是能夠經過一堆函數自動串聯在一塊兒跑的麼? 🤔 相比 webComponent 那不太靠譜的包裝方式, 若是引入 Deno 那種模塊網絡化的思想, 把函數組件分發到網絡上, 由於粒度的一致性和函數自然容易被機器理解的特性, 這不就實現了 webComponent 的願景麼, 可能之後咱們能夠這麼寫代碼

import dataPicker from 'https://function/ui/component/data-picker'
import useWeiBoUserData from 'https://function/hooks/use-webbo-user-data'

async function app(){
    const weiboUserData = await useWeiBoUserData()
    render(){
        return {
            ``` <dataPicker></dataPicker> <div>{weiboUserData.toJSON()}</div> ```
        }
    }
}

// HTML
<div app="react"></div>
複製代碼

不須要繁重的打包編譯, 瀏覽器內置 ui 框架, 而後經過網絡就能共享函數組件, 函數邏輯, 函數服務...感受要進入新時代了

若是你還有什麼腦洞能夠開一開, 不妨留在評論裏, 或許若干牛後回來還能吹個水, 昔日我就預言....

相關文章
相關標籤/搜索