【react總結(一)】:一次性完全弄懂組件(函數式組件、PureComponent、React.memo、高階組件)

1. 函數式組件(無狀態組件)

1. 什麼是函數式組件

首先須要去理解純函數的概念:相同的輸入,擁有有相同輸出,沒有任何反作用。同理: 咱們的函數式組件也不會去根據組件的狀態(state)的不一樣輸入,不會擁有不一樣的表現形式。(與state無關,取決於props與context)。javascript

2. 函數式組件有什麼優缺點
1. 優勢:
  1. 不須要聲明Class,也就不須要constructor、extends代碼(書寫簡潔,內存佔用小:無需class 的 props context _context 等諸多屬性,組件不須要被實例化,無生命週期,提高性能)。
  2. 能夠寫成無反作用的純函數。
  3. 視圖和數據的解耦分離。
2. 缺點:
  1. 沒有實例化,沒法使用ref;
  2. 沒有生命週期方法;
  3. shouldComponentUpdate方法沒有,重複渲染都無法避免。
3. 函數式組件的應用場景
  1. 無需state。
  2. 無需生命週期函數。
4. 我在開發中的應用場景
// 引入package等省略,這是一個全局公共的Loading。
const Loading = ({ size = '', loading = false, inner = false }) => {
  if (loading) {
    return inner? (
      // 沒有size屬性,默認用自定義的大小
      <div className={classNames(
        styles['inner-loading-container'],
        {[styles['custom-size']]: !size}
      )}>
        <Spin size={size || 'small'} className={styles.loading} /> </div>
    ): (
      createPortal(
        <div className={styles['loading-container']}> <Spin className={styles.loading} /> </div>, document.querySelector('body'), ) ); } return null; }; 複製代碼

2. 基於Class聲明的組件

  1. ES5 React.createClass (會自動綁定函數,這樣會致使沒必要要的性能開銷。擁有mixin。棄用)
  2. ES6 React.Component(按需綁定,HOC)

3. PureComponent (v15.3新增)

1. 爲何會有PureComponent

正常狀況下,父組件每次有state或者props改變,子組件都會從新渲染。可是若是咱們在shouldComponentUpdate階段判斷新舊屬性和狀態是否相等,是否須要從新渲染子組件。減小render()方法的觸發,節省了在虛擬DOM生成與對比的過程,性能獲得提高。前端

2. PureComponent的侷限性

這個比較的過程是一個淺比較,沒法判斷複雜數據類型(引用類型)的變化。java

3. 如何優化
  1. 不要將複雜的狀態寫入一個組件,將部分僅須要簡單狀態類展現型組件抽離。
  2. 複雜的數據類型帶來的反作用能夠經過immutable來解決。
4. 咱們是否能夠所有都寫成PureComponent形式
  1. 即便是淺比較,一樣是須要消耗性能。react

  2. 若是不是immutale數據的話你可能會出現問題(同一引用類型改變致使不更新渲染),一旦深層次的結構出現問題,你定位問題可能須要好久。git

  3. 這不足以成爲一個正常應用的性能瓶頸,當你發現已經到不優化不可的地步,那確定不是PureComponent致使的,優化的優點也就不那麼明顯了。github

    // 淺比較shallowEqual的源碼
    const hasOwn = Object.prototype.hasOwnProperty
    // 這個函數其實是Object.is()的polyfill
    function is(x, y) {
      if (x === y) {
        return x !== 0 || y !== 0 || 1 / x === 1 / y
      } else {
        return x !== x && y !== y
      }
    }
    
    export default function shallowEqual(objA, objB) {
        // 首先對基本數據類型的比較
      if (is(objA, objB)) return true
     // 因爲Obejct.is()能夠對基本數據類型作一個精確的比較, 因此若是不等
      // 只有一種狀況是誤判的,那就是object,因此在判斷兩個對象都不是object以後,就能夠返回falseif (typeof objA !== 'object' || objA === null ||
          typeof objB !== 'object' || objB === null) {
        return false
      }
    // 過濾掉基本數據類型以後,就是對對象的比較了
      // 首先拿出key值,對key的長度進行對比
      const keysA = Object.keys(objA)
      const keysB = Object.keys(objB)
    // 長度不等直接返回false
      if (keysA.length !== keysB.length) return false
      for (let i = 0; i < keysA.length; i++) {
      // key值相等的時候
      // 借用原型鏈上真正的 hasOwnProperty 方法,判斷ObjB裏面是否有A的key的key值
      // 屬性的順序不影響結果也就是{name:'daisy', age:'24'} 跟{age:'24',name:'daisy' }是同樣的
      // 最後,對對象的value進行一個基本數據類型的比較,返回結果
        if (!hasOwn.call(objB, keysA[i]) ||
            !is(objA[keysA[i]], objB[keysA[i]])) {
          return false
        }
      }
    
      return true
    }
    複製代碼

4. 高階組件

1. 什麼是高階組件

高階組件的概念其實是來源於咱們的高階函數:接收函數做爲輸入,或者輸出另外一個函數的一類函數,被稱做高階函數。高階組件: 接受React組件做爲輸入,輸出一個新的React組件的組件。高階組件和裝飾器是一個模式,高階組件能夠看成裝飾器使用。redux

2.常見用法: 1.屬性代理(Props Proxy) 2. 反向繼承(Inheritance Inversion)
  1. 屬性代理:1. 更改props 2. 抽象state 3. 經過refs訪問組件實例 4. 封裝樣式、佈局等。
  2. 反向繼承:劫持被繼承class的render內容,進行修改,過濾後,返回新的顯示內容。(super.render()、super.Fn())
3. 使用案例:
  1. react-redux中的connect(mapStateToProps, mapDispatchToProps, mergeProps,options): 此方法會將react組件鏈接到redux的store。connect經過函數參數mapStateToProps,從全局store中取出當前組件須要的state,並把state轉化成當前組件的props;同時經過函數參數mapDispatchToProps,把當前組件用到的Redux的action creator,以props的方式傳遞給當前組件。connect並不會修改傳遞進去的組件的定義,而是它會返回一個新的組件。bash

  2. react-router中的withRouter: 經過withRouter包裝的組件,咱們能夠在props中訪問到location, router等對象,這正是withRouter經過高階組件的方式傳遞過來的。react-router

  3. UI框架中的受控組件:react-hoc-example框架

4. 注意事項
  1. 不要在組件的render方法中使用高階組件,儘可能也不要在組件的其餘生命週期方法中使用高階組件。由於高階組件每次都會返回一個新的組件,在render中使用會致使每次渲染出來的組件都不相等(===),因而每次render,組件都會卸載(unmount),而後從新掛載(mount),既影響了效率,又丟失了組件及其子組件的狀態。高階組件最適合使用的地方是在組件定義的外部,這樣就不會受到組件生命週期的影響了。

  2. 若是須要使用被包裝組件的靜態方法,那麼必須手動拷貝這些靜態方法。由於高階組件返回的新組件,是不包含被包裝組件的靜態方法。hoist-non-react-statics能夠幫助咱們方便的拷貝組件全部的自定義靜態方法。

  3. Refs不會被傳遞給被包裝組件。儘管在定義高階組件時,咱們會把全部的屬性都傳遞給被包裝組件,可是ref並不會傳遞給被包裝組件,由於ref根本不屬於React組件的屬性。若是你在高階組件的返回組件中定義了ref,那麼它指向的是這個返回的新組件,而不是內部被包裝的組件。若是你但願獲取被包裝組件的引用,你能夠把ref的回調函數定義成一個普通屬性(給它一個ref之外的名字)。

    function FocusInput({ inputRef, ...rest }) {
      return <input ref={inputRef} {...rest} />;
    }
    
    //enhance 是一個高階組件
    const EnhanceInput = enhance(FocusInput);
    
    // 在一個組件的render方法中...
    return (<EnhanceInput 
      inputRef={(input) => {
        this.input = input
      }
    }>)
    
    // 讓FocusInput自動獲取焦點
    this.input.focus();
    複製代碼

5. React.memo(v16.6新增)

類組件使用PureComponent或者shouldComponentUpdate可以優化props值不變時候的渲染性能(默認是shallowEqual)。如今, 你能夠經過使用React.memo對function組件進行一樣的優化。固然你也能夠在方法的第二個參數自定義compare方法。實際意義是:函數式組件也有「shouldComponentUpdate」生命週期了

const MyComponent = React.memo(function MyComponent(props) {
  /* 只在props更改的時候纔會從新渲染 */
});

function areEqual(prevProps, nextProps) {
  /*
  return true if passing nextProps to render would return
  the same result as passing prevProps to render,
  otherwise return false
  */
}
function MyComponent(props) {
     /* render using props */
}
export default React.memo(MyComponent, areEqual);
複製代碼

6. 關於爲何在react組件中this會丟失

1. 爲何

類聲明和類表達式的主體以 嚴格模式 執行,主要包括構造函數、靜態方法和原型方法。Getter 和 setter 函數也在嚴格模式下執行。不是React的緣由,這是JavaScript中原本就有的。若是你傳遞一個函數名給一個變量,而後經過在變量後加括號()來調用這個方法,此時方法內部的this的指向就會丟失.

2. 爲何使用箭頭函數沒有this丟失問題

箭頭函數沒有 this,因此須要經過查找做用域鏈來肯定 this 的值。這就意味着若是箭頭函數被非箭頭函數包含,this 綁定的就是最近一層非箭頭函數的 this。this 是有詞法約束力的。這意味它可使用封閉的函數上下文或者全局上下文做爲 this 的值。

3. 關於this請看另外一篇:前端基本功(三):javascript中讓人腦袋疼的this關鍵字
相關文章
相關標籤/搜索