본문 바로가기

JavaScript/React.js

HOC (Higher Order Component)

리액트 Higher-order Component(HOC)

코드를 작성하다보면, 자주 반복해서 작성하게 되는 코드들이 있다. 우리는 주로 그러한 것들을 함수화해서 재사용 하곤 한다. 컴포넌트 또한 비슷하다. 같은 UI 관련 코드가 재사용 될 수 있다면 우리는 컴포넌트를 만들어서 컴포넌트를 재사용한다.그런데 컴포넌트 기능 상에도, 자주 반복되는 코드들이 나타날 수 있다. 소프트웨어 개발 원리 중에서 DRY 라는 개념이 있다. - 같은 작업을 반복하지 마라 (Don't repeat yourself)

리액트 컴포넌트를 작성하게 될 때 반복될 수 있는 코드들은, HOC를 만들어서 해결해줄 수 있다. HOC는, 하나의 함수인데, 함수를 통하여 컴포넌트에 우리가 준비한 특정 기능을 부여한다. 직접 만들어볼 때 까지는 감이 잡히지 않을 수도 있다. 직접 한번 만들어보자.

*코드 : https://codesandbox.io/s/qzj9kw265w

# 반복되는 코드 발견하기

Post.js 코드를 읽어보자.

import React, { Component } from 'react';
import axios from 'axios';

class Post extends Component {
  state = {
    data: null
  }
  
  async initialize() {
    try {
      const response = await axios.get('https://jsonplaceholder.typicode.com/posts/1');
      this.setState({
        data: response.data
      });
    } catch (e) {
      console.log(e);
    }
  }

  componentDidMount() {
    this.initialize();  
  }


  render() {
    const { data } = this.state;
    
    if (!data) return null;

    return (
      <div>
        { JSON.stringify(data) }    
      </div>
    );
  }
}


export default Post;

이 컴포넌트 에서는, 특정 주소에 GET 요청을 날리고, 결과물을 state의 data안데 담는다. 그래고, 해당 data를 JSON 형태로 그대로 렌더링 해준다.

Comment.js도 확인해보자.

import React, { Component } from 'react';
import axios from 'axios';

class Comments extends Component {
  state = {
    data: null
  }

  async initialize() {
    try {
      const response = await axios.get('https://jsonplaceholder.typicode.com/comments?postId=1');
      this.setState({
        data: response.data
      });
    } catch (e) {
      console.log(e);
    }
  }

  componentDidMount() {
    this.initialize();
  }


  render() {
    const { data } = this.state;

    if (!data) return null;

    return (
      <div>
        {JSON.stringify(data)}
      </div>
    );
  }
}


export default Comments;

그냥 거의 똑같다. 우선, 이렇게 반복되는 코드를 발견하는것이 첫번째 단추이다.

# HOC 작성하기

우리는 이 반복되는 코드를 없애기 위해서 하나의 함수를 작성한다. 주로 HOC 의 이름을 만들땐 with____ 형식으로 짓는다. 예를들어, 우리는 웹요청을 하는 HOC를 만들테니 withRequest 라고 지어주도록 하겠다.

HOC의 원리는, 파라미터로 컴포넌트를 받아오고, 함수 내부에서 새 컴포넌트를 만든 다음에 해당 컴포넌트 안에서 받아온 컴포넌트를 렌더링하는 것이다. 그리고, 자신이 받아온 props 들은 그대로 파라미터로 받아온 컴포넌트에게 다시 주입해주고, 필요에 따라 추가 props도 넣어준다. (예를 들어 우리의 경우엔 웹 요청 결과물이다.)

우선 HOC의 틀을 작성해보자.

withRequest.js

import React, { Component } from 'react';

const withRequest = (url) => (WrappedComponent) => {
  return class extends Component {
    render() {
      return (
        <WrappedComponent {...this.props}/>
      )
    }
  }
}

export default withRequest;

위 코드를 보면, 함수에서 또 다른 함수를 리턴하도록 했다. (url, WrappedComponent) 형식이 아니라, (url) => (WrappedComponent)로 한 이유는, 나중에 여러개의 HOC를 합쳐서 사용하게 될때 더욱 편하게 사용하기 위함이다. - compose같은 함수를 통하여 호출을 간소화 할 수 있다.

그럼, HOC에 기능을 붙여보자.

import React, { Component } from 'react';
import axios from 'axios';

const withRequest = (url) => (WrappedComponen) => {
  return class extends Component {

    state = {
      data: null
    }

    async initialize() {
      try {
        const response = await axios.get(url);
        this.setState({
          data: response.data
        });
      } catch (e) {
        console.log(e);
      }
    }

    componentDidMount() {
      this.initialize();
    }

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

export default withRequest;

axios를 통하여 받은 data를 파라미터로 받은 컴포넌트에 넣어주도록 설정을 했다.

# HOC 사용하기

HOC를 다 만들었다면, 사용하는 일만 남았다. Post와 Comments에서 한번 사용해보자.

Post.js

import React, { Component } from 'react';
import withRequest from './withRequest';

class Post extends Component {
  render() {
    const { data } = this.props;
    
    if (!data) return null;

    return (
      <div>
        { JSON.stringify(this.props.data) }    
      </div>
    );
  }
}


export default withRequest('https://jsonplaceholder.typicode.com/posts/1')(Post);

컴포넌트를 내보낼때 이렇게 사용하면 된다.

혹은,

const PostWithData = withRequest('https://jsonplaceholder.typicode.com/posts/1')(Post)
export default PostWithData;

의형식으로 해도 된다.

Comments도 마찬가지로 withRequest를 사용해보자.

Comments.js

import React, { Component } from 'react';
import withRequest from './withRequest';

class Comments extends Component {
  render() {
    const { data } = this.props;

    if (!data) return null;

    return (
      <div>
        {JSON.stringify(data)}
      </div>
    );
  }
}


export default withRequest('https://jsonplaceholder.typicode.com/comments?postId=1')(Comments);

# 정리

이렇게 HOC를 사용함으로서, 반복되는 코드들이 많이 사라졌다. 이렇게 웹요청을 하는 외에도, LifeCycle 메소드를 붙여준다던지, Redux에서 특정 값을 받아와서 주입해준다던지, 다국어지원을 한다던지 여러가지 일들을 할 수 있다.

recompose라는 라이브러리는, 페이스북 개발자가 만든 유용한 HOC 컬렉션 라이브러리인데, 활용하면 매우 쓸모있다. 종류는 굉장히 많은데, 이제 HOC가 어떤 역할을 하는지 알았으니, recompose의 API 문서를 쭉 훑어보면 어떤 것들을 할 수 있는지 감을 잡을 수 있다.

출처 : https://velopert.com/3537

 

컴포넌트에 날개를 달아줘, 리액트 Higher-order Component (HoC) | VELOPERT.LOG

리액트 Higher-order Component (HOC) 코드를 작성하다보면, 자주 반복해서 작성하게 되는 코드들이 있습니다. 우리는 주로 그러한 것들을 함수화하여 재사용 하곤 하죠. 컴포넌트 또한 비슷하죠. 같은 UI 관련 코드가 재사용 될 수 있다면 우리는 컴포넌트를 만들어서 컴포넌트를 재사용합니다. 자, 그런데 컴포넌트 기능 상에서도, 자주 반복되는 코드들이 나타날 수 있습니다. 소프트웨어 개발 원리 중에서 DRY 라는 개념이 있죠 – 같은 작업을

velopert.com

'JavaScript > React.js' 카테고리의 다른 글

리액트 16.3 Context API 파헤치기  (0) 2019.04.22
Reselect를 이용하여 React와 Redux 최적화하기  (0) 2019.04.17
Redux  (0) 2019.04.09
VELOPERT 리액트 강의  (0) 2019.04.03
webpack 설정  (0) 2019.04.01