使用React編寫組件時,咱們須要有意識地將組件劃分爲容器性組件(container component)和展現性組件(presentational component),這樣有助於咱們在編寫組件時,更加明確這個組件應該負責哪些事情。前端
容器性組件,負責業務流程邏輯的處理,如發送網絡請求,處理請求數據,將處理過的數據傳遞給子組件的Props使用。同時,容器性組件提供源數據的方法,以Props方式傳遞給子組件,當子組件的狀態變動引發源數據的變化時,子組件經過調用容器性組件提供的方法同步這些變化。算法
展現性組件,負責組件的外表,也就是組件如何渲染,具備很強的內聚性。展現性組件不關心渲染時使用的組件屬性(Props)是如何獲取到的,它只要知道有了這些Props後,組件應該如何渲染就足夠了。屬性如何獲取,是容器性組件負責的事情。當展現性組件狀態的變化須要同步到源數據時,須要調用容器性組件中的方法,這個方法通常也是經過Props傳遞給展現性組件。服務器
例如,一個Todo項目,有一個Todo組件和一個TodoList組件,Todo組件是一個容器性組件,負責從服務器端獲取待辦事項列表,獲取到待辦事項列表後傳遞給TodoList顯示。當在TodoList中新建一項待辦事項後,須要經過TodoList 的 Props,調用Todo組件中保存待辦項目的方法,將新建的待辦項目同步到服務器端。網絡
容器性組件和展現性組件能夠相互嵌套,一個容器性組件能夠包含多個展現性組件和其餘的容器性組件;一個展現性組將也能夠包含容器性組件和其餘的展現性組件。這樣的分工,可使與組件渲染無直接關係的邏輯由容器性組件集中負責,展現性組件只關注組件的渲染邏輯,從而使展現性組件更容易被複用。對於很是簡單的頁面,通常只要一個容器性組件就足夠了;但對於負責頁面,則須要多個容器性組件,不然全部的業務邏輯都在一個容器性組件中處理的話,會致使這個組件很是複雜,同時這個組件獲取到的源數據,可能須要通過不少層的組件Props的傳遞,才能到達最終使用的展現性組件。異步
Props、State的概念都很清晰,組件的普通屬性是指在組件中直接掛載到this下的屬性。其實,Props和State也是組件的兩個普通屬性,由於咱們能夠經過this.props 和 this.state 直接獲取到。那麼Props、State 和 組件的其餘普通屬性,分別應該在什麼場景下使用呢?函數
Props和State都是用於組件渲染的,也就是說,一個組件最終長成什麼樣,取決於這個組件的Props和State。Props和State的變化都會觸發組件的render方法。但這二者也是有區別的。Props是隻讀的數據,它是由父組件傳遞過來的;而State是組件內部本身維護的狀態,是可變的。State能夠根據Props的變化而變化。若是組件中還須要其餘屬性,而這個屬性又與組件的渲染無關(也就是render方法中不會用到),那麼就能夠把這個屬性直接掛在到this下,而不是做爲組件的一個狀態。性能
例如,組件中須要一個定時器,每隔幾秒改變一下組件的狀態,就能夠定義一個this.timer屬性,以備在componentWillUnmount時,清除定時器。fetch
React官網提到,this.state和this.props的更新多是異步的,React可能會出於性能考慮,將多個setState的調用,合併到一次State的更新中。因此,不要依賴this.props 和 this.state的值計算下一個狀態。引用官網的一個代碼示例:優化
// Wrong this.setState({ counter: this.state.counter + this.props.increment, });
若是必定要這麼作,可使用另外一個以函數做爲參數的setState方法,這個函數的第一個參數是前一個State,第二個參數是當前接收到的最新Props。以下所示:this
// Correct this.setState(function(prevState, props) { return { counter: prevState.counter + props.increment }; });
在調用setState以後,也不能當即使用this.state獲取最新狀態,由於這時的state極可能尚未被更新,要想保證獲取到的state是最新的state,能夠在componentDidUpdate中獲取this.state。也可使用帶用回調函數參數版本的setStatesetState(stateChange, [callback])
,回調函數中的this.state會保證是最新的state。
當組件的屬性可能發生變化時,這個方法會被調用。這裏說可能,是由於父組件render方法每次被調用時,子組件的這個方法都會被調用(子組件第一次初始化時除外),但並不必定每次子組件的屬性都會發生變化。若是組件的State須要根據Props的變化而變化,那麼這個方法就是最適合這個這個邏輯的地方。例如當Props變化時,組件的State須要重置,就能夠在這個方法中調用this.setState()來重置狀態。須要注意,在這個方法中調用this.setState()並不會從新觸發componentWillReceiveProps的調用,也不會致使render方法被觸發兩次。通常狀況下,接收到新Props會觸發一次render,調用this.setState也會觸發一次render,但在componentWillReceiveProps中調用this.setState,React會把本來須要的兩次render,合併成一次。
這個方法常做爲優化React性能使用。當shouldComponentUpdate返回false時,組件本次的render方法不會被觸發。能夠經過在這個方法中比較先後兩次state或者props,根據實際業務場景決定是否須要觸發render方法。
React提供了一個React.PureComponent組件,這個組件重寫了shouldComponentUpdate,會對先後兩次的state和props進行淺比較,如何不一致,纔會返回true,觸發後續的render方法。這裏的淺比較指,只會對state和props的第一級屬性進行比較(使用!==
),這知足通常的使用場景。若是你的組件繼承了React.PureComponent,但在setState時,傳入的state是直接修改的原有state對象,就會由於依然知足淺比較的條件,而不會從新觸發render方法,致使最終DOM和state不一致。例如state={books: ['A','B']}
,在setState時,使用this.setState({name: this.state.books.push('C')})
直接修改books對象,這樣雖然books內容發生了修改,但由於對象引用並無變化,因此依然知足淺比較條件,不會觸發render方法。
通常狀況下,讓shouldComponentUpdate返回默認的true是不會有太大問題的。雖然這樣可能致使一些沒必要要的render方法被調用,但render方法直接操做的是虛擬DOM,只要虛擬DOM沒有發生變化,並不會致使實體DOM的修改。而JS慢是慢在實體DOM的修改上。只要你的render方法不是很複雜,多調用幾回render方法並不會帶來多大的性能開銷。
父組件每次render方法被調用,或者組件本身每次調用setState方法,都會觸發組件的render方法(前提是shouldComponentUpdate使用默認行爲,老是返回true)。那麼組件每次render,是否是都會致使實體DOM的從新建立呢?答案是,不是!
React之因此比直接操做DOM的JS庫快,緣由是React在實體DOM之上,抽象出一層虛擬DOM,render方法執行後,獲得的是虛擬DOM,React 會把組將當前的虛擬DOM結構和前一次的虛擬DOM結構作比較,只有存在差別性,React纔會把差別的內容同步到實體DOM上。若是兩次render後的虛擬DOM結構保持一致,並不會觸發實體DOM的修改。
React速度快的緣由,還有一個是它出色的Diff算法。標準的比較兩棵樹的Diff算法的時間複雜是 O(n3) 。而React基於很是符合實際場景的兩個假設,就將Diff算法的時間複雜度降到了接近O(n)。這兩個假設是:
<Article>
和<Comment>
將產生是兩個徹底的樹狀結構;<div>children</div>
和<p>children</p>
也是兩個徹底不一樣的樹。這種狀況下,組件會被徹底重建,舊的DOM節點被銷燬,組件經歷componentWillUnmount()
,而後從新建立一棵新樹, 組件經歷 componentWillMount()
和 componentDidMount()
。<ul> <li key='a'>Book A</li> <li key='b'>Book B</li> </ul>
當在第一個位置插入一條記錄Book C 時,
<ul> <li key='c'>Book C</li> <li key='a'>Book A</li> <li key='b'>Book B</li> </ul>
因爲有key的標識,React知道此時新增了一條記錄,會建立一個新的<li>
元素,並把它插入到列表中的第一個位置。若是沒有設置key,React並不知道是新增了一條記錄,仍是原來的兩條記錄徹底替換成新的三條記錄,或者其餘更加複雜的修改場景。React須要自上而下的比較每一條記錄,這樣每次比較節點都不一樣,因此須要修改兩次節點,而後再新增一個節點,效率明顯要差不少。
這裏同時揭露了另外一個問題,不要使用元素在集合中的索引值做爲key,由於一旦集合中元素順序發生改變,就可能致使大量的key失效,進而引發大量的修改操做。
當咱們須要從服務器獲取數據時,咱們應該在組件的哪個生命週期方法中發送網絡請求呢?React官網上提到,能夠在componentDidMount中發送網絡請求,這也是通常狀況下的最佳實踐。有些人也會把發送網絡請求放在componentWillMount中,而且認爲這個方法先於componentDidMount調用,因此能夠更快地獲取數據。我的認爲,這種使用方法通常也是沒有問題的,但在一些場景下會出現問題,好比須要在服務器端渲染時,componentWillMount會被調用兩次,一次是在Server端,一次是在Client端。可參考這篇文章。
歡迎關注個人公衆號:老幹部的大前端,領取21本大前端精選書籍!