- Published on
React Component Design - Higher-Order Component
- Authors
- Name
- Alex Yu
Higher-Order Component
的簡稱叫HOC
,設計概念主要是要把一些共用的邏輯抽取出來,讓其他元件透過HOC
的方式能獲得這些共用邏輯的功能,讓元件的使用方式不一定要侷限在這個HOC
。
HOC
的概念很重要的一點是利用composition
的方式來產生一個新的component
,而不是直接mutate傳入的component
。
我們拿之前介紹過的Provider
來改寫成HOC試試
import React, {Fragment} from 'react'
// copy static methods
import hoistNonReactStatics from 'hoist-non-react-statics'
import { Switch } from '../switch'
const ToggleContext = React.createContext()
class Toggle extends React.Component {
static Consumer = ToggleContext.Consumer
toggle = () =>
this.setState(
({ on }) => ({ on: !on }),
() => this.props.onToggle(this.state.on),
)
state = { on: false, toggle: this.toggle }
render() {
return (
<ToggleContext.Provider value={this.state} {...this.props} />
)
}
}
function withToggle(Component) {
function Wrapper(props, ref) {
return (
<Toggle.Consumer>
{toggleContext => (
<Component toggle={toggleContext} {...props} ref={ref} />
)}
</Toggle.Consumer>
)
}
Wrapper.displayName = `withToggle(${Component.displayName ||
Component.name})`
return hoistNonReactStatics(React.forwardRef(Wrapper), Component)
}
const Layer1 = () => <Layer2 />
const Layer2 = withToggle(({ toggle: { on }}) => (
<Fragment>
{on ? 'The button is on' : 'The button is off'}
<Layer3 />
</Fragment>
))
const Layer3 = () => <Layer4 />
const Layer4 = withToggle(({ toggle: { on, toggle }}) => (
<Switch on={on} onClick={toggle} />
))
function Usage({
onToggle = (...args) => console.log('onToggle', ...args),
}) {
return (
<Toggle onToggle={onToggle}>
<Layer1 />
</Toggle>
)
}
讓我們來看看HOC
幾個要注意的地方
displayName
需要設定- 傳入的
component
的static methods
需要copy到HOC
裡面新的component
,所以一般會透過hoist-non-react-statics
複製static methods
到新的component
- 如果有希望使用
ref
,需要透過React.forwardRef
的方式轉換
從上面幾點不難看出HOC
有滿多的缺點,而且在現在React 18的環境中,其實已經有一些方式可以替代HOC
,比如之前介紹過的Render Props
pattern,或者是hook
的方式。
讓我們先把上面的例子改寫成Render Props
的形式來看看
class ToggleWrapper extends React.Component {
render() {
return (
<Toggle.Consumer>
{toggleContext => (
this.childen({ toggle: toggleContext })
)}
</Toggle.Consumer>
)
}
}
Render Props
這樣的方式看起來差別不大,但優點其實很多
children
不受限制- 純粹是一個
React Component
,讓React tree
乾淨 - 可以寫
Prop Types
檢驗props
了! static methods
跟ref
的問題都同時解決了props
不用spred