React Component Design - Higher-Order Component

發佈時間

Higher-Order Component#

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需要設定
  • 傳入的componentstatic methods需要copy到HOC裡面新的component,所以一般會透過hoist-non-react-statics複製static methods到新的component
  • 如果有希望使用ref,需要透過React.forwardRef的方式轉換

從上面幾點不難看出HOC有滿多的缺點,而且在現在React 18的環境中,其實已經有一些方式可以替代HOC,比如之前介紹過的Render Propspattern,或者是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 methodsref的問題都同時解決了
  • props不用spred

Reference#