Higher-Order Components

September 27, 2020

HOC란?

  • 컴포넌트의 공통 기능을 관리하기 위해 사용되는 함수
  • 컴포넌트를 입력으로 받아서 컴포넌트를 출력해주는 함수이다.
  • HOC를 사용하여 코드의 중복을 줄일 수 있다.

HOC를 사용하여 코드의 중복을 줄일 수 있다.

  1. componentDidMount
  2. componentWillUnmount
  3. handleChange
class CommentList extends React.Component {
    constructor(props) {
        super(props)
        this.handleChange = this.handleChange.bind(this)
        this.state = {
            comments: DataSource.getComments(),
        }
    }

    componentDidMount() {
        DataSource.addChangeListener(this.handleChange)
    }

    componentWillUnmount() {
        DataSource.removeChangeListener(this.handleChange)
    }

    handleChange() {
        this.setState({
            comments: DataSource.getComments(),
        })
    }

    render() {
        return (
            <div>
                {this.state.comments.map((comment) => (
                    <Comment comment={comment} key={comment.id} />
                ))}
            </div>
        )
    }
}

class BlogPost extends React.Component {
    constructor(props) {
        super(props)
        this.handleChange = this.handleChange.bind(this)
        this.state = {
            blogPost: DataSource.getBlogPost(props.id),
        }
    }

    componentDidMount() {
        DataSource.addChangeListener(this.handleChange)
    }

    componentWillUnmount() {
        DataSource.removeChangeListener(this.handleChange)
    }

    handleChange() {
        this.setState({
            blogPost: DataSource.getBlogPost(this.props.id),
        })
    }

    render() {
        return <TextBlock text={this.state.blogPost} />
    }
}

HOC 활용

function withSubscription(WrappedComponent, selectData) {
    return class extends React.Component {
        constructor(props) {
            super(props)
            this.handleChange = this.handleChange.bind(this)
            this.state = {
                data: selectData(DataSource, props),
            }
        }

        componentDidMount() {
            DataSource.addChangeListener(this.handleChange)
        }

        componentWillUnmount() {
            DataSource.removeChangeListener(this.handleChange)
        }

        handleChange() {
            this.setState({
                data: selectData(DataSource, this.props),
            })
        }

        render() {
            return <WrappedComponent data={this.state.data} {...this.props} />
        }
    }
}

const CommentListWithSubscription = withSubscription(
    CommentList,
    (DataSource) => DataSource.getComments()
)

const BlogPostWithSubscription = withSubscription(
    BlogPost,
    (DataSource, props) => DataSource.getBlogPost(props.id)
)

주의사항

  1. Don’t Mutate the Original Component

    • logProps의 문제점
      • 다른 HOC의 input으로 입력되면 componentDidUpdate가 override된다
      • 함수형 컴포넌트는 componentDidUpdate가 없어 작동하지 않는다.
    function logProps(InputComponent) {
        InputComponent.prototype.componentDidUpdate = function (prevProps) {
            console.log('Current props: ', this.props)
            console.log('Previous props: ', prevProps)
        }
        // The fact that we're returning the original input is a hint that it has
        // been mutated.
        return InputComponent
    }
    
    // EnhancedComponent will log whenever props are received
    const EnhancedComponent = logProps(InputComponent)
    • 해결책
    function logProps(WrappedComponent) {
        return class extends React.Component {
            componentDidUpdate(prevProps) {
                console.log('Current props: ', this.props)
                console.log('Previous props: ', prevProps)
            }
            render() {
                // Wraps the input component in a container, without mutating it. Good!
                return <WrappedComponent {...this.props} />
            }
        }
    }
  2. HOC내에서 render 생명주기 사용하지 않기

    • 매번 새로운 값이라고 판단(React's diffing alogirithm 구분하지 못함)
    render() {
      // A new version of EnhancedComponent is created on every render
      // EnhancedComponent1 !== EnhancedComponent2
      const EnhancedComponent = enhance(MyComponent);
      // That causes the entire subtree to unmount/remount each time!
      return <EnhancedComponent />;
    }
  3. Static Method 전달

    function enhance(WrappedComponent) {
        class Enhance extends React.Component {
            /*...*/
        }
        // Must know exactly which method(s) to copy :(
        Enhance.staticMethod = WrappedComponent.staticMethod
        return Enhance
    }
    
    // hoist-not-react-static 이용
    import hoistNonReactStatic from 'hoist-non-react-statics'
    function enhance(WrappedComponent) {
        class Enhance extends React.Component {
            /*...*/
        }
        hoistNonReactStatic(Enhance, WrappedComponent)
        return Enhance
    }
    
    // 정적메소드만 따로 export하여 사용하기
    // Instead of...
    MyComponent.someFunction = someFunction
    export default MyComponent
    
    // ...export the method separately...
    export { someFunction }
    
    // ...and in the consuming module, import both
    import MyComponent, { someFunction } from './MyComponent.js'
  4. Ref는 전달

    function logProps(Component) {
        class LogProps extends React.Component {
            render() {
                const { forwardedRef, ...rest } = this.props
                return <Component ref={forwardedRef} {...rest} />
            }
        }
        return React.forwardRef((props, ref) => {
            return <LogProps {...props} forwardedRef={ref} />
        })
    }

© 2023, Customized by Joon