關於React的高階元件

giovanni發表於2018-07-29

什麼是高階元件?

高階元件是什麼?乍一聽,感覺是個很高階的概念,但是不要被這個名詞嚇到,說簡單點 就是給已有的一個元件外面“包一層”。 我們知道 “高階函式” 是傳入函式作為引數, 高階元件 其實就是傳入 元件作為引數,並返回一個新元件。

高階元件的作用

高階元件的作用是什麼?專案開發當中,通常我們會把一些公用的邏輯抽離出來, 並且應用到很多元件上,給元件賦予一個新的能力,這時候就需要用到它。

常見的應用場景

簡而言之,如果你需要給很多元件都寫相同的判斷邏輯,那麼可以考慮提取出一個高階元件

實際場景: 路由許可權控制

記得以前開發vue 專案的時候也遇到過類似的問題,那時用的vue-router,實現方式是在全域性的router.beforeEach方法中獲取使用者資訊,根據使用者的角色和許可權跳轉到不同的頁面。參考官方文件

一個實現路由許可權控制的高階元件:

const LodingUserTip = () => {
  return (
    <p>
      <Icon type="loading-3-quarters" />
      正在獲取使用者資訊,稍後...
    </p>
  )
}
const UnAuthoriedTip = () => {
  return (
    <p>
      <Icon type="lock" />
      <h5 style={{color: 'red'}}>抱歉,您沒有訪問該頁面的許可權...</h5>
    </p>
  )
}

const authDecorator = WrappedComponent => {
// auth 元件是最終返回的高階元件
  const Auth = props => {
    const {
      match: {path, params},
      currentUser: {role_type: roleType},
      userLoading,
      history,
      location,
    } = props
    const {match} = props
    // 使用者資訊正在載入
    if (userLoading) {
      return <LodingUserTip />
    }

    // 當前登入使用者沒有訪問許可權
    if (!roles.includes(roleType)) {
      return <UnAuthoriedTip />
    }

    // 如果沒有找到對應頁面,則跳轉至該許可權對應的預設頁面
    const {allowedUrl, defaultUrl} = AUTH_MAP[roleType]
    if (!allowedUrl.includes(path)) {
      history.push(defaultUrl)
      return null
    }
    
    // 還可以把額外的 props傳遞給你使用的元件
    return (
      <WrappedComponent
        params={params}
        match={match}
        location={location}
        history={history}
        roleType={roleType}
        {...props}
      />
    )
  }

  const mapStateToProps = ({
    users,
    loading: {models: {users: userLoading}},
  }) => {
    return {
      currentUser: users.currentUser,
      userLoading,
    }
  }

  return withRouter(connect(mapStateToProps)(Auth))
}

export default authDecorator
複製程式碼

上面這段程式碼便是一個高階元件, 通常我們會請求後端介面返回給我們一個當前使用者的roleType, 我們在前端會存一個map,根據不同的roleType對映到的不同的路由, map結構如下:

export const AUTH_MAP = {
  admin: {
    allowedUrl: [
      '/a/b',
    ],
    defaultUrl: '/f',
  },
  superAdmin: {
    allowedUrl: [
      '/a/b',
      '/c/d',
    ],
    defaultUrl: '/e',
    }
}
複製程式碼

使用的時候可以配合裝飾器,更簡單方便:

@authDecorator
class PageOneComponent extends React.Component {
...
複製程式碼

高階元件中會判斷 當前的使用者角色與當前的路由是否匹配,若匹配不成功則顯示預設資訊,或者跳轉到預設url(或者登陸頁)

遇到的問題

在使用antd的Form.create(onFieldsChange:(props, fields) => {}) 我很疑惑這裡 onFieldsChange 為什麼能獲取到元件的props, 最後明白: 其實Form.create()返回了一個高階元件,會接收到所有傳給元件的props, 當fields變化的時候,antd執行onFieldsChange並把props傳進去便可以。

但有一個要注意,如果一個元件有多個裝飾器(需要被多個高階元件包裹),需要注意順序,比如下面這個圖片,我希望在onFieldsChange 中獲取 從 redux 拿到的 dictionary資訊,這樣是獲取不到的,因為 最先執行的connect, 然後才執行的 Form.create()方法,Form.create()返回的高階元件只能獲取到之後傳遞給元件的props.

關於React的高階元件

如圖,debugger打斷點 看到 props中 沒有 dictionary

要想拿到所有的props,請按照下圖這樣做:(最先執行的是Form.create()返回的函式,所以後面執行的函式以及傳遞的props都會被onFieldsChange接收到):

關於React的高階元件

需要注意的點

  • ref 無法傳遞的問題: 由於你使用的元件其實被包裹了一層, 所以上層元件獲取的ref, 實際上是獲取的高階元件。 具體詳情請看我的另外一篇部落格關於React的ref
  • 被包裹的元件自身的靜態方法預設是不會出現在高階元件中的,要想解決這個問題,我們可以 MyHOC.staticMethod = WrappedComponent.staticMethod 這樣明確的將靜態方法傳遞給高階元件,但是這樣太不嚴謹了,很容易漏掉。官方推薦的最佳實踐是使用 hoist-non-react-statics 第三方庫 自動copy 靜態方法
  • 高階元件(HOC)應該是無副作用的純函式,且不應該修改原元件
  • 給高階元件函式傳參除了傳入 一個元件 還想傳入 其他引數怎麼辦? 我覺得可以寫成 類似 connect的柯里化的形式比較好。 本文中使用到的高階元件基本都是單引數,如果想傳遞多個引數也是沒問題的,可以看官網的例子, 例子中selectData 便是第二個引數,作為獲取資料的回撥,獲取資料的邏輯便可以從高階元件中解耦。

參考連結

hcysun.me/2018/01/05/… segmentfault.com/a/119000000… segmentfault.com/a/119000000… www.zhihu.com/question/58… hacpai.com/article/151…

相關文章