什麼是高階元件?
高階元件是什麼?乍一聽,感覺是個很高階的概念,但是不要被這個名詞嚇到,說簡單點 就是給已有的一個元件外面“包一層”。 我們知道 “高階函式” 是傳入函式作為引數, 高階元件 其實就是傳入 元件作為引數,並返回一個新元件。
高階元件的作用
高階元件的作用是什麼?專案開發當中,通常我們會把一些公用的邏輯抽離出來, 並且應用到很多元件上,給元件賦予一個新的能力,這時候就需要用到它。
常見的應用場景
- withRouter 方法
- Redux 的 connect方法
- ant design 表單的Form.create()方法
- 路由許可權控制, 同一個路由地址,根據不同的使用者角色或者登陸狀態, 展示不同的UI檢視
- 當你想要給很多元件賦予新的
props
時
簡而言之,如果你需要給很多元件都寫相同的判斷邏輯,那麼可以考慮提取出一個高階元件
實際場景: 路由許可權控制
記得以前開發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.
如圖,debugger打斷點 看到 props中 沒有 dictionary
要想拿到所有的props,請按照下圖這樣做:(最先執行的是Form.create()返回的函式,所以後面執行的函式以及傳遞的props都會被onFieldsChange
接收到):
需要注意的點
- 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…