關於React的高階組件

NO IMAGE

什麼是高階組件?

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

高階組件的作用

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

常見的應用場景

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

實際場景: 路由權限控制

記得以前開發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…

相關文章

GoPlay原理詳解

前端面試總結

基於React的表單開發的分析(下)

基於React的表單開發的分析(上)