面試系列之二:你真的了解React嗎如何設計組件以及重要的生命周期
前言
這不是一篇深度解析React工作原理的文章,而是幫你完善你可能忽略的React的知識點。
生活中我們頻繁使用電腦、冰箱,但不一定需要知道電腦和冰箱是如何工作的。但是作為一個技術人員,從jQuery到React,你不僅需要熟悉使用他們,還需要了解他們的工作原理,以體現你的精通。然而如何體現你對它們的了解,無非是從大量的相關細節問題。相信絕大部分公司的技術棧都已經切換到了React,你也知道面試官最喜歡的面試套路是從你簡歷上的「知識點」順藤摸瓜式的追問你,所以React的相關問題無法避免,而同時你也要問自己是否真的了解它,還只是會使用它而已。
所以在繼續閱讀之前,請嘗試回答以下有關React的問題(其中有九成是我在面試中遇到的,另外一成是我自己認為有必要了解的),其中粗體字部分是我認為重點需要掌握的知識點,不僅是在面試過程中,在實際代碼過程中需要運用到的:
React解決了什麼問題
如何設計一個好的組件?
組件的Render函數在何時被調用?
調用時DOM就一定會被更新嗎?
組件的生命周期有哪些?
當某些第三方類庫想對DOM初始化,或者進行遠程數據載入時,應該在哪個周期中完成?
在哪些聲明周期中可以修改組件的state?
不同父節點的組件需要對彼此的狀態進行改變時應該實現?
如何設計出一個好的Flux架構
如何設計出一個好的React組件
如何進行優化?
組件中的key屬性有什麼用?
Component 與 Element 與 Instance 的區別
如果你使用過Redux與Vuex的話,聊聊他們的區別與你的心得
Vue.js 的雙向綁定是如何實現的?
Webpack如何打包輸出多個文件?
webpack打包時如何工作的?
如何解決循環引用的問題
在什麼情況下需要打包輸出多個文件?
loader和plugin的差別
你覺得使用過什麼高級技巧嗎?
(開放問題)React的生態你使用過哪些類庫
如果以上問題都難不倒你,那麼恭喜你,你的React技能樹已滿。如果有些問題不確定或者不了解的也沒有關係,請繼續閱讀這篇文章。我將一一對這些問題做解答。
其中有些問題的答案比較長,可能會和一篇文章相當。所以關於React可能會拆成上中下三集來說。
重要的不是把這些問題的答案背下來,而應該重點去理解他們。如果你之前沒有React的開發經驗,可能對於其中的一些道理沒有那麼深的感觸。所以建議邊做,邊學,邊理解。
但是請注意有些問題可能並沒有官方答案,是我個人通過經驗得出的,所以僅作參考喔。如果有補充也可以留言給我。
React解決了什麼問題
這是基礎但是又很重要的問題,如果只是回答公司要求或者趕上潮流,未免就顯得格局太小和好奇心太弱。
這個問題的答案在我一年前寫的一篇文章里已經有很詳細的回答了:寫給前端看的架構文章(1):MVC VS Flux。重點分析了MVC與Flux的差異,MVC的弱勢以及Flux彌補的不足(這麼好的一篇文章竟然沒人關注,傷心)。
總之一句話:MVC架構的雙向綁定以及一對多的關係容易造成連級/聯動(Cascading)修改,對於代碼的調試和維護都成問題。
如何設計一個好的組件
這道題目中的「組件」不僅限於React組件,廣義上看,前端代碼模塊,獨立類庫甚至函數在編寫時都應該遵循良好的規則。
怎樣的組件設計算的上「好」,要從幾個層次來看這個問題。我們從宏觀到微觀依次來看。
首先你要知道組件的出現是為了解決怎樣的問題——是為了更好的復用。然而怎樣才能能其他的使用者更好的復用你的組件?API夠爛肯定不行,這樣的話其他人就沒法調用;兼容性差也不行,因為同一個系統中可能存在不同版本React編寫的組件,甚至還可能和Vue組件發生交互;內部實現差了也不行,這樣的話你的下一任接替你職位的人修改起來會非常麻煩,結果不外乎重寫。
高內聚,低耦合
我絕對相信這六個字你已經聽到耳朵起繭。但我還是要重申,無論是什麼語言編程,無論是前端還是後端,無論多耳熟能詳的架構(Microervices),無論是多具體的設計原則(後面會說的SOLID),本質上都是對這個原則的實踐。所以我們的設計也不例外。
顧名思義,在做組件設計時,甚至編寫函數時,應該把相同功能的部分放在一起,而把不相干的部分儘可能的撇開關係。如果你想去反向驗證你的設計是否符合這個原則的話,可以嘗試去修改這個模塊的一個功能,看看到底是否會牽連其他模塊的修改;或者當你想復用這個組件時,是否會引入其他無關的組件。
接下來我們從SOLID原則看看對這六個字的具體實踐。
S.O.L.I.D
SOLID 原則是面向對象設計中的原則,但就我經驗而言,其中的這些也同樣適用於組件設計。例如單一職責(Single responsibility principle),React組件設計推崇的是「組合」,而非「繼承」。例如你的頁面需要一個表單組件,表單中需要有輸入框,按鈕,列表,單選框等。那麼在開發中你不應該只開發一(整)個表單組件(),而是應該開發若干個單一功能的組件,比如輸入框、提交按鈕、單選框等,最後再將它們組合起來。這其中的重點是每個組件僅做一件事。
不僅僅是編寫組件,哪怕僅僅是編寫一個簡單的函數也是應該如此,例如你需要一個函數非同步請求數據並返回JSON數據格式,那麼你應該拆分為兩個函數,一個複雜數據請求,另一個負責數據轉化。你可能會好奇為什麼一個簡單的JSON.parse也拆分出來,因為將來需要會變動,你可能不僅僅需要JSON.parse,還需要轉義,需要轉化為proto buffer數據格式。而拆分之後如果再面臨修改的話,就不會影響到數據請求部分的代碼。
上面這個例子也同樣適用於開放/封閉(Open/closed principle)原則。開放/封閉強調的是對修改封閉(禁止修改內部代碼),對拓展開放(允許你拓展功能)。因為修改意味著風險,可能會影響到不用修改的代碼, 同時意味著暴露細節。你一定納悶如果不允許修改代碼的話如何拓展功能呢,在傳統的面向對象編程中,這樣的需求是通過繼承和介面機制來實現的。在React中我們使用官方推薦的 Higher-Order Components 的模式去實現。這個在後面會詳細敘述。
介面隔離(Interface segregation principle)這個就放之四海而皆準了。第三方類庫或者模塊都避免不了對外提供調用介面,比如對於jQuery來說$是選擇器,css用於設置樣式,animate負責動畫,你不希望把這三個介面都合并成一個叫做together吧,雖然實現起來沒有問題,但是對於你將來維護這個類庫,以及使用者調用類庫,以及調用者的接替者閱讀代碼(因為他要區分不同上下文中調用這個介面究竟是用來幹嘛的),都是不小的困難。
最後一條依賴反轉(Inversion Of Control)原則。這條原則聽上去有點拗口,不過它有另外一個名字:Hollywood Principle,雖然我也不理解為什麼會有這個別名。這條原則是意思是,當你在為一個框架編寫模塊或者組件時,你只需要負責實現介面,並且到註冊到框架里即可,然後等待框架來調用你,所以它的另另一個別名是 「Don t call us, we ll call you」。
這麼說你可能沒什麼太大感覺,也不明白和「依賴」和「反轉」有什麼關係,說到底其實是一個控制權的問題。這裡舉一個圖書Building Microservices: Designing Fine-Grained Systems中的例子。常規情況下當你在用express編寫一個server時,代碼是這樣的:
const app = express(); module.exports = function (app) {
app.get( /newRoute , function(req, res) {...}) };
這意味著你正在編寫的這個模塊負責了/newRoute這個路徑的處理,這個模塊在掌握著主動權。
而用依賴反轉的寫法是:
module.exports = function plugin() {
return {
method: get ,
route: /newRoute ,
handler: function(req, res) {...}
} }
意味著你把控制權交給了引用這個模塊的框架,這樣的對比就體現了控制權的反轉。
其實前端編程中常常用到這個原則,注入依賴就是對這個思維的體現。比如requireJS和Angular1.0中對依賴模塊的引用使用的都是注入依賴的思想。
至於里氏替換原則在前端是真的用不上了。
Higher-Order Components 和 Container Components 和 Stateless Components
上面我們了解完總體的設計思想之後,細化的來看針對React組件還有哪些具體的設計模式。
Higher-Order Components
讓我們考慮一下這的業務場景:假設你現在有一個基礎款的
組件,允許自定義屬性和點擊事件,以及添加容器內的文字。接下來你需要另一個相似的高級的
組件,包含基礎款的所有功能,並且還有額外的功能,例如有額外的標題和圖片。你要怎麼實現這個功能?
程序員思維告訴我們新的高級組件應該繼承自基礎款的組件,而不是重寫。但是如何實現呢?這就要使用我們的 Higher-Order Components 模式(以下簡稱HOC)了。
這個模式很簡單,我們定義一個工廠函數名為enchce(wrapperComponent, config),支持傳入舊的基礎款的組件,而這個函數的功能則是在採用類似外觀模式在對舊的組件做一次新的封裝,引入新的功能。
舉一個實際的例子。假設我們的基礎款的組件長這個樣子:
class BaseComponent extends React.Component { render() { return (
{ this.props.title }
{ this.props.content }
); } }
而我們希望新的組件稍稍的改變事,click事件的回調函數不使用傳入值,而使用的是更強大的自定義函數,那麼我們的enhance函數是這樣:
const enhance = (WrappedComponent) => { return class ClickLogger extends React.Component { constructor(props) { super(props); this.onClick = this.onClick.bind(this); } onClick(e) { console.log(e) } render() { const { title, content } = this.props; return (
); } } } const LoggableComponent = enhance(BaseComponent);
當然在這個例子中你可以直接使用class ClickLogger extends BaseComponent,但是如果存在多次復用,或者類似於Vue中的Mixins情況,那麼HOC模式就很重要了。注意HOC模式的重點是:不要修改原有的組件
Facebook有整篇的官方的博文來介紹這個模式,關於在什麼場景下使用這個模式,高級用法以及需要注意的事項。這裡就不贅述了
Container Components
先來看看這樣這樣一個組件,你認為有什麼問題:
class CommentList extends React.Component { this.state = { comments: [] }; componentDidMount() { fetchSomeComments(comments => this.setState({ comments: comments })); } render() { return ( — ))} ); } }
這個組件的問題在於數據抓取和數據展示同放在同一個組件和代碼塊中。這樣一來無論是數據抓取部分邏輯還是數據展示邏輯都無法復用。於是我們把數據邏輯部分分離出來成為獨立的組件,這類組件就是Container Components,而展現部分組件則是Presentational Components。根據這個思路,上面這個例子可以劃分為兩個組件:
Presentational Components:
const CommentList = props => — ))}
Container Components:
class CommentListContainer extends React.Component { state = { comments: [] }; componentDidMount() { fetchSomeComments(comments => this.setState({ comments: comments })); } render() { return ; } }
當然你還可以將Container Components封裝為類似於HOC的工廠函數
Stateless Components
上一小節的 Presentational Components 就近似於 Stateless Components 的概念,也就是自己不維護狀態而是依靠外部傳入的狀態。關於 Stateless Components 的具體描述在關於 Flux 的設計章節會有詳細敘述
代碼層面
更細節的問題就是代碼層面了。然而如何寫好代碼這件事對於React來說並沒有什麼特殊之處。如果在面試的過程中需要補充這方面的內容的話,請強調你的代碼是足夠符合規範的。以及在設計代碼的過程中你會根據業務場景靈活的運用設計模式組織代碼。又例如對於組織樣式代碼,也會運用BEM規則。
總之代碼需要可讀性強,復用性高,可維護性好。以後如果還有什麼想到的再繼續補充
組件的Render函數在何時被調用
如果單純、俠義的回答這個問題,毫無疑問Render是在組件 state 發生改變時候被調用。無論是通過 setState 函數改變組件自身的state值,還是繼承的 props 屬性發生改變都會造成render函數被調用,即使改變的前後值都是一樣的。
如果你想手動決定是否調用也沒有問題,如果你還記得React的生命周期的話,一定記得有一個boolean shouldComponentUpdate(object nextProps, object nextState)生命周期函數,這個函數的返回值決定了Render是否被調用,默認都返回true,即允許render被調用。如果你對自己的判斷能力有自信,你可以重寫這個函數,根據參數判斷是否應該調用 Render 函數。這也是React其中的一個優化點。
但退一步說,即使render函數被調用了,DOM就一定被更新了?這要看更新的是哪一類DOM了。
React組件中存在兩類DOM,一類是眾所周知的Virtual DOM,相信大家也耳熟能詳了;另一類就是瀏覽器中的真實DOM(Real DOM/Native DOM)。React的Render函數被調用之後,React立即根據props或者state重新創建了一顆Virtual DOM Tree,雖然每一次調用時都重新創建,但因為在內存中創建DOM樹其實是非常快且不影響性能的,所以這一步的開銷並不大。而Virtual DOM的更新並不意味這Real DOM的更新,接下來的事情也是大家知道的,React採用演算法將Virtual DOM和Real DOM進行對比,找出需要更新的最小步驟,此時Real DOM才可能發生修改。
所以正確答案是,每一次的state更改都會使得render函數被調用,但頁面的DOM不一定會發生修改
組件的生命周期有哪些?
這一道問題其實是有標準答案的,具體可以參考Facebook官方的這篇文章React.Component,我在這裡強調一下重點。
組件的聲明周期有三種階段,一種是初始化階段(Mounting),一種是更新階段(Updating)最後一種是析構階段(Unmounting)。而這兩個階段的聲明周期函數都是相似且有一一對應的關係的。
組件的初始化階段的聲明周期函數以及重點用法如下:
constructor(): 用於綁定事件以及初始化state(可以通過"fork"props的方式給state賦值)
componentWillMount(): 只會在服務端渲染時被調用,你可以在這裡同步操作state
render(): 這個函數是用來渲染DOM沒有錯。但它只能用來渲染DOM,請保證它的純粹性。如果有操作DOM或者和瀏覽器打交道的一系列操作,請在下一步驟componentDidMount中進行
componentDidMount(): 如果你有第三方操作DOM的類庫需要初始化(類似於jQuery,Bootstrap的一些組件)操作DOM、或者請求非同步數據,都應該放在這個步驟中做
組件更新階段:
componentWillReceiveProps(nextProps): 在這裡你可以拿到即將改變的狀態,可以在這一步中通過setState方法設置state
shouldComponentUpdate(nextProps, nextState):這一步驟非常重要,它的返回值決定了接下來的生命周期函數是否會被調用,默認返回true,即都會被調用;你也可以重寫這個函數使它返回false。
componentWillUpdate(): 我也不知道這個聲明周期函數的意義在哪裡,在這個函數內你不能調用setState改變組件狀態
render()
componentDidUpdate(): 和componentDidMount類似,在這裡執行DOM操作以及發起網路請求
組件析構階段:
componentWillUnmount(): 主要用於執行一些清理工作,比如取消網路請求,清楚多餘的DOM元素等
認識了以上所有的生命周期之後,請不假思索的回答,有哪些生命周期是允許設置state的?


※捕獲-嵌入-防護:領域驅動設計的指導原則
※Candy Crush被製作成真人秀,手游與傳統媒體的結合沒擦出任何火花
※惠普薄銳 ENVY 13 體驗:中端的定價,卻有「旗艦」的設計?
※科大訊飛陶曉東:智能影像技術如何解決臨床問題?
※面試系列之一:關於前端面試演算法的一些建議
TAG:推酷 |
※iPhone X Plus概念設計:你期待的雙卡雙待來了!
※做一個好的設計,需要考慮那些因素byFoster+partners設計事務所
※iPhone X第二代再次曝光,或搭載真全面屏無劉海設計值得期待!
※最具人氣的棋盤設計還是聯名款!Shoe Palace x Vans不容錯過的別注系列登場!
※聖馬丁的實驗先鋒品牌Winnie Witt在她的設計作品裡,它們不再是沒有生命的,而你也能夠聽到它們的每一個細微呼吸
※全球最值得期待的設計Party,我們一起去吧
※Essential Phone 設計師:造能與蘋果抗衡的手機,我有 3 點重要見解
※結合Christian Louboutin的實習經歷,告訴你如何你快速走上設計師之路
※vivo xplay7產品設計細節曝光,相信是在你的意料之中!
※Julia Banas身著夢幻時尚的設計,帶來社會般的慵懶情調
※如此前衛的設計你是否會買帳?Vetements x Reebok
※必掌握的平面設計核能概念:Hierarchy,層次!從此向扁平無趣Say Bye!
※JeffDean又用深度學習搞事情:這次要顛覆整個計算機系統結構設計
※還用Windows系統的設計師沒前途了?
※不得不說的iPhoneX五大匠心之處:喬布斯式用戶體驗的設計遺風你Get到了嗎
※這雙粉粉可愛的Nike Air More Uptempo,你能猜到它的設計靈感嗎?
※編程真的要從娃娃抓起?體驗專為兒童設計的Linux發行版Suger
※設計不僅僅是圖片,更是做你自己——《Design It Yourself》
※以試管為主題的創意Logo設計案例分享(一)
※iPhone SE2真要來了?或只是設計師的一廂情願而已!