React-5/3,4,5

Gavri·2021년 8월 2일
0

05-3 하이어오더 컴포넌트 라이브러리 사용하기

recompose 라이브러리는 하이어오더 컴포넌트중 자주 사용하는 패턴을 모은 하이어오더 컴포넌트 입니다.
리액트 16.8버전 부터는 대부분 recompose 기능이 리액트 훅에서 구현되어 있습니다. 그럼에도 recompose 활용 방법을 알아야 하는 이유는 함수형 컴포넌트의 기능 확장하는 방법을 이해하는데 도움이 되기 때문입니다.

설치하기

yarn add recompose

recompose 모든 함수를 임포트하면 프로젝트가 무거워지니 다음과 같이 필요한 함수만 추출하여 임포트 하는 것이 좋습니다.
또한 with이름이 붙을경우 하이어오더 컴포넌트 역활을 합니다.

import { branch , withState} from 'recompose' //비효율적
import branch from 'recompose' //효율적

branch() 함수 : 조건식에 따라 다른 하이어오더 컴포넌트를 불러야 할 경우 사용합니다.

branch(
    condition: props => boolean, // boolean 값으로 참일경우 left 실행 거짓일경우 right실행
    left: HigherOrderComponent, 
    [right: HigherOrderComponent] // 생략가능 
)(WrappedComponent)
import React from 'react';
import branch from 'recompose/branch';
import Button from '../04/Button';

/*



*/

// function isLoading(props) {
//   return props.isLoading;
// }

// function LoadingButton(props) {
//   return <Button disabled>로딩 중</Button>;
// }
// export default branch(isLoading, () => LoadingButton)(Button);

// 화살표 함수로 짧은 코딩

export default branch({ isLoading }, () => () => <Button disabled>로딩 중</Button>)(Button);

withState() 함수로 상태 관리와 이벤트 처리하기

import React from 'react';
import withState from 'recompose/withState';
import Button from '../04/Button';

export const withCountState = withState('count', 'setCount', 0);

/*
    함수형 컴포넌트는 state를 사용할 수 없지만, 프로퍼티와 콜백 함수를 활용해 우회적으로 사용할 수 있습니다.
    withState() 함수는 함수형 컴포넌트를 클래스형 컴포넌트로 변환하지 않아도 state를 사용할 수 있게 해줍니다. 
    withState(
        stateName: string, // state를 전달할 프로퍼티 이름
        stateUpdater: string, // state를 변경할 수 있는 콜백 함수 프로퍼티 이름
        initialState: any, // state 초기값
    )(WrappedComponent)

*/
function Counter({ count, setCount }) {
  const increaseCount = () => setCount((value) => value + 1);
  return (
    <div>
      현재 카운트: {count}
      <Button onPress={increaseCount}>카운트 증가</Button>
    </div>
  );
}
/* 함수형 컴포넌트 Counter에는 state 관련 코드가 없지만 withState() 함수에 전달된 인자인 count 와 setCount를 프로퍼티로 받아 사용합니다. 
setCount(0) ===  count = 0;
setCount(c + 1) ===  count = +1;
*/

export const CounterWithCountState = withCountState(Counter);

lifecycle() 함수로 함수형 컴포넌트에 생명주기 함수 추가하기

import React from 'react';
import lifecycle from 'recompose/lifecycle';

function Page({ content }) {
  return (
    <div>
      페이지 로딩이 완료됬습니다.
      {content}
    </div>
  );
}

export const withLoadData = lifecycle({
  state: { isLoading: true, content: '' },
  componentDidMount: function () {
    if (this.props.loadData) {
      this.props.loadData().then((content) => this.setState({ isLoading: false, content }));
    }
  },
});

export const PageWithLoadData = withLoadData(Page);

/*
lifecycle() 함수의 경우 함수형 컴포넌트에 우회적으로 생명주기 함수를 사용가능하게 만들어 줍니다. 
클래스형 컴포넌트에 사용할 경우 생명주기 함수에서 사용할 반복 코드를 해당 함수에 묶어 재사용할 수도 있습니다. 
lifecycle({
    [lifeCycleMethod: string]: function, // 생명주기 함수 이름 componentDidMount 등
    state: object; state 초깃값 
})(WrappedComponent)
lifecycle 특징 : this.props 접근 가능하여 커링을 통해 따로 인자를 전달 받지 않아도 됩니다. 이러한 특징 때문에
        화살표 함수를 사용하지 못합니다. (화살표 함수는 this의 범위가 선언된 구문의 범위로 고정되기 때문입니다.)
        setState 함수로 설정된 state 값은 WrappedComponent에 프로퍼티로 전달 됩니다.
*/

StoryBook에 예제 생성하기

import React from 'react';
import { storiesOf } from '@storybook/react';

import { PageWithLoadData } from '../../05/lifecycle';

storiesOf('Lifecycle', module).addWithJSX('loadData예제', () => (
  <PageWithLoadData loadData={() => fetch('/').then(() => 'helloE')} />
));

recompose 라이브러리는 만능이 아닙니다.

해당 라이브러리를 사용하면 쉽고 간단하게 하이어오더 컴포넌트를 다룰수는 있지만, 실제 구현에 필요 없는 코드가 입력될 수도 있습니다. 이는 리액트 앱의 성능을 저하시킵니다. recompose는 반복해서 입력해야 하는 불필요한 코드의 양을 줄여야 하는 경우 사용해야 합니다. 적절한 경우에만 최소한으로 사용하는 것이 좋습니다.

05-4 다중 하이어오더 컴포넌트 사용하기

다중 하이어오더 컴포넌트 개념 알아보기

Page 컴포넌트에 서버 데이터 호출 기능과 로딩 메시지 출력이 필요하면 각 하이어오더 컴포넌트 생성 함수를 조합하는 식입니다. 기능에 기능을 추가하며 다중 하이어오더 컴포넌트를 만드는 것이지요.

import compose from 'recompose/compose';
import withLoading from './withLoading';
import withState from './recompose/withState';

const withLoadData = withState('isLoading', 'setIsLoading', false);

const Component = () => '완료(컴포넌트 출력';
const ComponentWithLoading = withLoading('로딩 중')(Component);
const ComponentWithLoadData = withLoadData(ComponentWithLoading);

const withLoadDataAndLoading = compose(withLoadData, withLoading('로딩 중'));
//로딩상태 (withLoadData) 를 정의한 후 isLoading 프로퍼티를 withLoading 하이어오더 생성 함수에 전달하여 로딩 메시지를 출력할 수 있도록 확장

// 조합이 올바르지 못한 예 : compose(withLoadData, withLoading)
// 순서가 올바르지 못한 예 : compose(withLoading('로딩 중'),withLoadData)
//withLoading을 먼저 조합하면 withLoadingData의 isLoading 프로퍼티가 withLoading 하이어오더 컴포넌트에 전달되지 않으므로 주의

const ComponentWithLoadData = withLoadDataAndLoading(ComponentWithLoading);
//compose로 만든 증강된 하이어오더 컴포넌트 생성 함수를 사용하니  하이어오더 컴포넌트 생성 함수 코드를 추가로 작성하지 않아도 됩니다.

// compose함수의 인자는 같은 요소가 전달되어야 합니다 withLoading은 커링 함수이고 withLoadData는 하이어오더 컴포넌트 이므로
// 같은 요소가 아니네요 그렇지만 withLoading이 반환한 값이 하이어오더 컴포넌트 이므로 조합할 수 있게 되었습니다.

로딩 상태 표시 다중 하이어오더 컴포넌트로 쉽게 구현하기

이전 lifecycle 컴포넌트는 생성이 완료 된후 content를 넣어 값을 추가 했다면 이번에는 서버에서 많은 데이터양을 보낼 경우 로딩 상태 표시를 해야 할 경우 매번 반복적인 코딩을 할것이 아니라 다중 하이어오더 컴포넌트로 쉽게 구현 해볼것입니다.
1. withLoading() 함수로 만든 컴포넌트의 로딩 상태 확인하기

./src/05/lifecycle.jsx
import React from 'react';
import lifecycle from 'recompose/lifecycle';
import compose from 'recompose/compose';
import withLoading from './withLoading';
function Page({ content }) {
  return (
    <div>
      페이지 로딩이 완료됬습니다.
      {content}
    </div>
  );
}

export const withLoadData = lifecycle({
  state: { isLoading: true, content: '' },
  componentDidMount: function () {
    if (this.props.loadData) {
      this.props.loadData().then((content) => this.setState({ isLoading: false, content }));
    }
  },
});

export const PageWithLoadData = withLoadData(Page);
export const PageWithLoadDataAndLoading = compose(withLoadData, withLoading('서버 요청 중'))(Page);
// PageWithLoadData의 경우 화면이 생성이 완료 된뒤 무언가 데이터를 넣고 싶을때 사용
// PageWithLoadDataAndLoading의 경우 서버에 데이터 요청 할때 로딩 메시지를 띄워주고 화면 생성완료후 데이터 삽입

//storybook
import React from 'react';
import { storiesOf } from '@storybook/react';

import { PageWithLoadData } from '../../05/lifecycle';
import { PageWithLoadDataAndLoading } from '../../05/lifecycle';

storiesOf('Lifecycle', module)
  .addWithJSX('loadData예제', () => (
    <PageWithLoadData loadData={() => fetch('/').then(() => 'helloE')} />
  ))
  .addWithJSX('로딩 메시지 예제', () => (
    <PageWithLoadDataAndLoading loadData={() => fetch('/').then(() => 'hello')} />
  ));

05-5 필수 입력 항목 표시 기능 추가하며 하이어오더 컴포넌트 복습

src\05\withError.jsx
import React from 'react';
import withStyles, { css } from '../04/withStyles';

export default function (defaultMessage) {
  //커링을 통해 withError(Message전달)(Input 컴포넌트 전달)
  return (WrappedComponent) => {
    const { displayName, name: componentName } = WrappedComponent;
    //WrappedComponent에서 displayName과 name 값을 빼옴
    const wrappedCompoentName = displayName || componentName;
    // displayName 이 존재하면 그값을 아니면 뒷값을

    function ComponentWithError({ hasError, errorMessage, styles, ...props }) {
      //필요한 속성만 따로 인자로 받고 나머지는 넘기기
      return (
        <React.Fragment>
          //Fragment 태그 react는 하나의 노드만 반환 가능
          <WrappedComponent {...props} />
          {hasError && <div {...css(styles.error)}>{errorMessage}</div>}
          // 에러가 존재할경우 css 와 styles.error 속성 적용
        </React.Fragment>
      );
    }

    ComponentWithError.defaultProps = {
      errorMessage: defaultMessage,
    };
    //defaultProps으로 기본값 지정

    ComponentWithError.displayName = `withError(${wrappedCompoentName})`;
    //ComponentWithError 의 displayName 정의
    return withStyles(({ color }) => ({
      // 하이어오더 컴포넌트 안에서 사용된 스타일을 스타일 컴포넌트 생성 함수를 이용하여 정의
      error: {
        color: color.error,
      },
    }))(ComponentWithError);
  };
}

storybook
src\stories\WithErrorStory.jsx
import React from 'react';
import { storiesOf } from '@storybook/react';

import Input from '../04/InputWithStyle';
import withError from '../05/withError';

const InputWithError = withError('올바르지 못한 값입니다.')(Input);

storiesOf('WithError', module)
  .addWithJSX('기본 설정', () => <InputWithError name="name" hasError />)
  .addWithJSX('errorMessage예제', () => (
    <InputWithError name="name" hasError errorMessage="필수 항목입니다." />
  ));
profile
모든건 기록으로

0개의 댓글