面試系列之四:你真的了解React嗎(下)Flux與Vuex的差異以及Webpack的工作原理

本系列文章旨在完善你可能忽略的React相關的知識點,通過回答以下問題:

  • React解決了什麼問題
  • 如何設計一個好的組件?
  • 組件的Render函數在何時被調用?
    • 調用時DOM就一定會被更新嗎?
  • 組件的生命周期有哪些?
    • 當某些第三方類庫想對DOM初始化,或者進行遠程數據載入時,應該在哪個周期中完成?
    • 在哪些聲明周期中可以修改組件的state?
  • 不同父節點的組件需要對彼此的狀態進行改變時應該實現?
    • 如何設計出一個好的Flux架構
    • 如何設計出一個好的React組件
  • 如何進行優化?
    • 組件中的key屬性有什麼用?
  • Component 與 Element 與 Instance 的區別
  • 如果你使用過Redux與Vuex的話,聊聊他們的區別與你的心得
    • Vue.js 的雙向綁定是如何實現的?
  • Webpack如何打包輸出多個文件?
    • webpack打包時如何工作的?
      • 如何解決循環引用的問題
    • 在什麼情況下需要打包輸出多個文件?
    • loader和plugin的差別
    • 你覺得使用過什麼高級技巧嗎?
  • (開放問題)React的生態你使用過哪些類庫

本文是本系列的最後一篇文章,之前的內容可以參考之前的本系列的前兩篇文章:

面試系列之二:你真的了解React嗎(上)如何設計組件以及重要的生命周期

面試系列之三:你真的了解React嗎(中)組件間的通信以及React優化

如果你使用過Redux與Vuex的話,聊聊他們的區別與你的心得

我個人是比較擅長Flux與Vuex的,這裡主要是分享我對Flux與Vux的使用經驗,聊聊Flux與Vuex的區別。這題也算的上是半開放式的命題,Redux我也學習過寫過相關的代碼,但是相對Vuex,我沒法詳細的說出以及比較Flux與Redux的優劣。所以各位在面試的時候根據自己的情況酌情回答。如果以後有機會的話,我也會補上對Redux的使用心得。

就我個人的經驗我更傾向於使用Vuex,它們很相似,都強調組件化,都強調單向數據流,Vuex也是受到Flux啟發而誕生。但根據它們之間的差異說一說我選擇Vuex的理由:

  • 代碼文件大小:React代碼打包之後相對較大,基本是300KB起跳;而Vue和Vuex框架代碼則相對較小,基礎庫能維持在100KB左右。
  • 現成的框架:在Flux初期,Facebook只是推出了Flux這個框架概念,而沒有實現這個框架。除非你使用一些第三方的Flux框架,否則你需要自己去實現Flux中的兩個事件機制(Component對於Store的響應,Store對於Action的響應)。當然現在React的github項目里已經有Flux框架的示例代碼,以及他們推出了Relay框架。相反Vuex不僅提出了這個框架概念,還實現並且提供了這個框架,讓開發起來更加便捷。
  • 針對性的改進:如果你閱讀過Vuex的官方文檔的話,你會明白Vuex其實是針對Flux存在的一些缺陷而開發的。具體的缺陷其實我們在上一篇中提到過,例如不同的組件都維護自己的狀態的話,不同組件之間想改變對方的狀態其實會比較困難的。Vuex的解決辦法也是上一篇中提到的那樣,把state提升到全局的高度,儘可能是使用stateless組件。同時又引入了module等概念更利於代碼的解耦和開發。
  • 具體細節上的差異:Vuex中保留了action與store的概念,並且引入了新的mutation。action和mutation廣義上來說都是提交對store修改,不同的是action可以是非同步的,並且大多數情況是在event handler中提交,通過$store.dispatch方法;唯一修改 Store 的地方只能通過mutation,而且mutation必須是同步的,直接對store進行修改,舉例一個簡單store的例子:

export const SampleStore = {n state: {n data: n }, n actions: {n fetchData({commit}) {n fetch(/api/sample, (response) => {n return response.json();n }).then((data) => {n commit(updateData, data);n });n }n },n mutations: {n updateData(inputData, state) {n state.data = inputData;n }n },n getters: {n data: state => state.datan }n}n

注意在上面的例子中,fetchData這個action是用於非同步的請求數據,而updateData這個mutation用於同步的修改store,而component中的event handler只能調用action,而不允許直接調用mutation。我認為這以及非常直觀的顯示了它們之間的差異。

最後新增的getters類似於面向對象編程語言中property的訪問器,保證view訪問到的數據是你允許訪問的。

Vue.js 的雙向綁定是如何實現的

傳統的數據綁定不外乎兩種方式:

  • 事件機制(pub/sub):我們通過特定的方法修改數據,例如Store.set(key, value),set方法修改數據的同時觸發一個事件,告訴view數據發生了更改,view立即從新從store拉取數據。這類似於Flux中View對於Store數據的響應,只不過通過某種方法或者directive將這種機制封裝起來了。這種機制的弱勢在於你沒法用傳統的方式等號=對數據進行賦值。
  • 輪詢(pull/dirty check):這個方式就更加簡單了,數據的消費方不斷的檢測數據有沒有修改。當然不是無時無刻的進行檢測,而是在input事件或者change事件的時候進行檢測。Angular 1.0使用的就是這種機制。我個人傾向於把這種方式稱為輪詢而不是臟檢查

然而對於Javascript來說,還存在第三種方式,那就是利用Javascript中的Object天生的支持的屬性訪問器。

在Javascript中,我們可以給對象中的值定義訪問器,例如:

let data = {};nObject.defineProperty(data, key, {n get() {n console.log(Get method invoked);n },n set(newVal) {n console.log(Set method invoked);n }n})n

那麼接下來當你每次想訪問data中key欄位時,無論是取值data.key還是賦值data.key = Hi,都會有列印信息。這也意味著,我們能夠在用戶執行普通的賦值和取值操作時,做一些事情,例如通知數據的消費者數據發生了更改,讓它們重新編譯模板。這也就是Vue.js雙向綁定的思路。

當然這只是雙向數據綁定的一個環節,但是是最核心的環節,其他還包括如何添加訂閱者,如何編譯模板等等,在這裡就不詳述了,可以參考以下兩篇文章:

  • Vue.js雙向綁定的實現原理
  • 剖析Vue實現原理 - 如何實現雙向綁定mvvm

Webpack如何打包輸出多個文件?

關於Webpack的入門,可以參考我的之前的一片文章《Webpack 速成》,這篇文章從需求出發,解釋了Webpack的一些基本用法。

首先你要明白Webpack的用途,從最終效果上來說它和gulp或者grunt非常相似,甚至有些功能是重疊的。但Webpack的初衷是用於模塊打包,解決模塊間的兼容性問題。例如有些模塊是在服務端(Node.js)開發使用,想挪用到瀏覽器端使用;有的模塊是以ES6 module標準編寫,而有的模塊則是以AMD標準編寫的,而它們之間需要互相調用。你一定聽說過UMD這個概念,Universal Module Define,就是在定義模塊是儘可能的兼容多的標準。而Webpack幫你解決了這個問題,讓你不用的過多擔心標準,而專註於模塊的開發。

關於loader和plugin的區別在剛剛說的那篇入門中有提到,簡單來說loader決定了你Webpack打包模塊的能力,例如你需要打包vue組件,那麼你需要引入vue-loader;如果你需要編譯打包less代碼,則需要引入less-loader。而plugin提供的則是模塊打包之後更邊緣的便捷功能,例如Webpack自帶Uglify插件用於壓縮代碼,自帶CommonsChunkPlugin插件用於提取公共模塊。

打包多個文件的場景很簡單,但是實際情況中運用的或許不多。舉一個不恰當的例子,例如你的站點有多個頁面(home、gallery、about),但每個頁面都是一個Vuex架構下的單頁面應用。於是可以設置多個入口,key為入口名稱,value為入口文件路徑:

entry: {n app: ./entry/app.js,n gallery: ./entry/gallery.js,n about: ./entry/about.jsn}n

同時在output欄位處,設置文件的輸出規則是以入口的key開頭,即下面代碼filename欄位的[name]值:

output: {n path: path.resolve(., output),n filename: [name].bundle.jsn}n

當然[name]也可以替換為其他的欄位,例如有其他欄位可選:

  • [name]: The module name
  • [id]: The module identifier
  • [file]: The module filename

不知道你有沒有考慮過Webpack的打包原理,為什麼它能將多個文件都打包在一起呢?如果你有了解過之前RequireJS或者SeaJS的載入原理的話,其實打包過程也很相似:一個模塊如果有依賴模塊的話,在所有的依賴模塊載入完成之前它自己的工廠函數代碼是不會被執行的,即使執行也會報錯,因為它依賴的變數和函數都並不存在。Webpack打包原理也相同:首先Webpack需要有一個入口模塊,也就是webpack配置文件里的entry。通過對入口模塊進行語法分析也好,注入依賴分析也好,找到模塊的依賴。此時Webpack應該會有一個HashMap,key為模塊的路徑或者名稱,而value則為模塊的工廠代碼或者具體內容。HashMap主要有兩個作用,一方面是用於緩存(可能存在一個模塊被多次引用的情況),另一方面則用於標記(模塊是否被載入)。Webpack則針對入口模塊以深度優先的原則逐個將依賴模塊進行載入。最後將入口模塊自己打包進bundle中。

然而以上所說的這個打包流程我故意漏掉了一個重要環節,就是如何解決模塊間循環引用的問題(A引用模塊B,B同時引用了模塊A)。但就解決循環引用這個問題而言,是可以以論文的篇幅進行敘述的。遺憾的是針對Webpack或者RequireJS,我並沒有找到確切的資料描述它們是如何解決循環引用的問題。在面試中詢問我的這個問題的時候,我臨時想到的是一個簡單粗暴的辦法,即在對一個模塊進行構建時對它的依賴建立一個鏈表,例如模塊A的依賴鏈表是:B->C->D->E->F,在構建這個鏈表的同時,比如我們打算在F後添加模塊G時,我們會去檢測G的依賴鏈表裡是否存在模塊A,如果存在的話則形成了循環依賴。

最後關於Webpack的高級用法——我被問到了這個問題,但我不知道如何定義什麼是高級用法。如果你開發過Webpack的plugin我覺得算是一項加分點,而至於其他點,我認為在面試中你可以把你認為的的高級用法都說出來,哪怕只是使用了alias。

你使用過哪些React的生態類庫

這就是一個完全開放的命題了。如果說之前的答案還能靠強化學習甚至死記硬背的話,那麼這一題完全是要依靠個人平時的積累。你要敘述的不僅僅是你用過什麼,還要告訴面試官你為什麼用它和解決了什麼問題。像報菜名一樣報上一大堆類庫的名稱沒有任何意義

這是最後一個問題了。順便也適合做一個結尾,在面試的過程中最大的一個感悟是,技術,或者說是能力在日常中就應該積累。為什麼我說日常而不是工作,因為大部分工作使用的技術方向還是較窄,尤其是新技術和允許發揮的空間(代碼兼容性、時間排期)。等到你有朝一日想換工作就晚了,羅馬不是一天建成的。我認為最好的積累方式應該是在工作中發現問題,並且嘗試用天馬行空的技術解決問題。不要先想手上有什麼,能解決什麼樣的問題;而是應該先想解決方案,再想方設法用技術去實現方案。噢,對了,這樣同時也能增強自己的項目經歷。

最後送大家兩個錦囊:

第一句是關於如何學好前端的。至於出處拿著這句話直接谷歌去吧,我個人是非常認同這個建議的

First do it, then do it right, then do it better. ——Addy Osmani

第二句是雞湯,給你再學習前端挫敗時候看的

Ever tried. Ever failed. No matter. Try again. Fail again. Fail better. ——Samuel Beckett

  • Vuex
  • Vue.js雙向綁定的實現原理
  • 剖析Vue實現原理 - 如何實現雙向綁定mvvm

推薦閱讀:

【譯】也許你不必使用 Redux
推薦閱讀-第4期
FreeGrit瀟洒毅行 的 Live -- ?? 業餘自學,如何入門金融和編程?
React 探秘 - React Component 和 Element(文末附彩蛋demo和源碼)

TAG:前端开发 | React |