1. 程式人生 > >React總結篇之六_React高階元件

React總結篇之六_React高階元件

  • 高階元件的概念及應用
  • 以函式為子元件的模式
    這兩種方式的最終目的都是為了重用程式碼,只是策略不同,各有優劣,開發者可以在實際工作中決定採用哪種方式。

一、高階元件
1. 高階元件(Higher Order Component,HOC)並不是React提供的某種API,而是使用React的一種模式,用於增強現有元件的功能。一個高階元件就是一個函式,這個函式接受一個元件作為輸入,然後返回一個新的元件作為結果,而且,返回的新元件擁有了輸入元件所不具備的功能。這裡提到的元件並不是元件例項,而是元件類,也可以是一個無狀態元件的函式。

  1. 高階元件的意義:
    (1)重用程式碼。有時候很多React元件都需要公用同樣一個邏輯,比如說React-Redux中容器元件的部分,沒有必要讓每個元件都實現一遍shouldComponentUpdate這些生命週期函式,把這部分邏輯提取出來,利用高階元件的方式應用出去,就可以減少很多元件的重複程式碼。
    (2)修改現有React元件的行為。

    有些現成的React元件並不是開發者自己開發的,來自於第三方,或者即便是我們自己開發的,但是我們不想去觸碰這些元件的內部邏輯,這時候可以用高階元件。通過一個獨立於原有元件的函式,可以產生新的元件,對原有元件沒有任何侵害。

  2. 根據返回的新元件和傳入元件引數的關係,高階元件的實現方式可以分為兩大類:
    • 代理方式的高階元件
    • 繼承方式的高階元件

二、代理方式的高階元件
高階元件的特點是返回的新組建類直接繼承自React.Component類,新元件扮演的角色是傳入引數元件的一個“代理”,在新元件的render函式中,把被包裹元件渲染出來,除了高階元件自己要做的工作,其餘功能全部轉手給了被包裹的元件。如果高階元件要做的功能不涉及除了render之外的生命週期函式,也不需要維護自己的狀態,那也可以乾脆返回一個純函式,像上面的removeUserProp,程式碼如下:
React總結篇之六_React高階元件

代理方式的高階元件,可以應用在下列場景中:

  • 操縱prop
  • 訪問ref
  • 抽取狀態
  • 包裝元件
  1. 操縱prop
    代理型別高階元件返回的新元件,渲染過程也被新元件的render函式控制,而render函式相當於一個代理,完全決定如何使用被包裹的元件。在render函式中,this.props包含新元件接收到的所有prop,最簡單的方式是把this.props原封不動的傳遞給被包裹的元件,當然,高階元件也可以增減,刪除,修改傳遞給包裹元件的props列表。
    下面是一個增加props的例子:
    React總結篇之六_React高階元件
    這個高階元件addNewProps增加了傳遞給包裹元件的prop,而不是刪除prop,我們讓addNewProps不只接受被包裹引數,還增加了一個new-Props引數,這樣高階元件的複用性更強,利用這樣的高階元件可以給不同的元件擴充新的屬性,程式碼如下:
    React總結篇之六_React高階元件


    上面的程式碼中,新創造的FooComponent被添加了名為foo的prop,BarComponent被添加了名為bar的prop,除此之外,兩者的功能也不一樣,因為一個是通過Demo-Component產生,另一個是通過OtherComponent產生。由此可見,一個高階元件可以重用在不同的元件上,減少程式碼的重複。

  2. 訪問ref
    訪問ref並不是React元件推薦的使用方式,此處不做詳細的介紹。

  3. 抽取狀態
    其實我們已經使用過”抽取狀態“的高階元件了,react-redux中的connect函式本身不是高階元件,但是connect函式執行的結果是另一個函式,這個函式是高階元件。
    在傻瓜元件和容器元件的關係中,通常讓傻瓜元件不要管理自己的狀態,只要做一個無狀態的元件就好,所有狀態的管理都交給外面的容器元件,這個模式就是”抽取狀態“。

  4. 包裝元件
    到目前為止,通過高階元件產生的新元件,render函式都是直接返回被包裹元件,修改的只是props部分。其實render函式的JSX中完全可以引入其他的元素,甚至可以組合多個React元件。
    一個實用的例子是給元件新增樣式style,程式碼如下:
    React總結篇之六_React高階元件
    把一個元件用<div />包起來,並且新增一個style來定製其css屬性,可以直接影響被包裹的元件對應DOM元素的展示樣式。程式碼如下:
    React總結篇之六_React高階元件

三、繼承方式的高階元件
繼承方式的高階元件採用繼承關係關聯作為引數的元件和返回的元件,假如傳入的元件引數是WrappedComponent,那麼返回的元件就直接繼承自WrappedComponent。
我們用繼承方式重新實現一遍removeUserprop這個元件,程式碼如下:
React總結篇之六_React高階元件
代理方式和繼承方式最大的區別,是使用被包裹元件的方式。
在代理方式下,render函式中的使用被包裹元件是通過JSX程式碼:
return <WrappedComponent {...otherProps}>
在繼承方式下,render函式中渲染被包裹元件的程式碼如下:
return super.render()
因為我們創造的新元件繼承自傳入的WrappedComponent,所以直接呼叫super.render就能夠得到渲染出來的元素。
注意:在代理方式下,WrappedComponent經歷了一個完整的生命週期,但在繼承方式下super.render只是一個生命週期中的一個函式而已,在代理方式下產生的新元件和引數元件是兩個不同的元件,一次渲染,兩個元件都要經歷各自的生命週期,在繼承方式下兩者合二為一,只有一個生命週期。
上面的例子直接修改了this.props,這種做法是不推薦的!
繼承方式的高階元件可以應用於下列場景:

  • 操縱prop
  • 操縱生命週期函式
  1. 操縱props
  2. 操縱生命週期函式

四、以函式為子元件

  1. 高階元件的缺點:對原有元件的props有了固化的要求。也就是說,能不能把一個高階元件作用於一個元件X,要先看一下這個元件X是不是能夠接受高階元件傳過來的props,如果元件X並不支援這些props,或者對這些props命名有不同,或者使用方式不是預期的方式,那也就沒有辦法應用這個高階元件。
    “以函式為子元件”的模式就是為了克服高階元件的這種侷限而生的。在這種模式下,實現程式碼重用的不是一個函式,而是一個真正的React元件,這樣的React元件有個特點,要求必須有子元件的存在,而且這個子元件必須是一個函式。在元件例項的生命週期函式中,this.props.children引用的就是子元件,render函式會直接把this.props.children當做函式來呼叫,得到的結果就可以作為render返回結果的一部分。
    程式碼如下:
    React總結篇之六_React高階元件
    AddUserProp首字母大寫,因為現在創造的是一個真正的元件類,而不是一個函式,這個類的程式碼,和被增強元件的唯一聯絡就是this.props.children,而且this.props.children是函式型別,在render函式中直接呼叫this.props.children,引數就是我們希望傳遞下去的user。
    使用這個AddUserProp的靈活之處在於它沒有對被增強元件有任何props要求,只是傳遞一個引數過去,至於怎麼使用,完全由作為子元件的函式決定。
    對於AddUserProp的使用,如下:
    React總結篇之六_React高階元件
    React總結篇之六_React高階元件
    React總結篇之六_React高階元件
    作為AddUserProp子元件的函式,成為了連線父元件和底層元件的橋樑。一個函式可以包含各種邏輯,這樣就給使用AddUserProp提供了最大的靈活性。“以函式為子元件”的模式沒有高階元件那麼多分類和應用場景,因為以函式為連線橋樑的方式已經提供了無數中用例。

  2. 效能優化問題
    (1)使用shouldComponentUpdate進行效能優化:
    React總結篇之六_React高階元件
    (2)不適用匿名函式