! 제품 버전을 정확하게 입력해 주세요.
제품 버전이 정확하게 기재되어 있지 않은 경우,
최신 버전을 기준으로 안내 드리므로
더욱 빠르고 명확한 안내를 위해
제품 버전을 정확하게 입력해 주세요!

2021 React 개발자들을 위한 모범 사례 > 인사이트

본문 바로가기

MESCIUS 커뮤니티

인사이트

IT&개발 정보 2021 React 개발자들을 위한 모범 사례

페이지 정보

작성자 GrapeCity 작성일 2021-10-21 09:41 조회 3,489회 댓글 0건

본문

믿기 어려우실 수도 있지만 React가 출시된지 벌써 8년이 되었습니다. 기술 분야, 특히 클라이언트 측 웹 개발에서 8년이라는 기간은 상당히 주목할 만한 것입니다. 어떻게 UI 빌드를 위한 간단한 라이브러리인 React가 이렇게 오래 지속되고 여전히 관련성을 유지할 수 있었을까요?

이유는 React가 UI 빌드를 혁신했을 뿐만 아니라 UI 빌드를 위한 기능 패러다임을 보편화했기 때문입니다. 또한 React는 여기서 멈추지 않았습니다. 기존 코드를 훼손하지 않으면서 혁신적인 개념을 계속 발전시켰습니다. 따라서 React는 그 어느 때보다 더 안정적이며 간단하고 빠릅니다.

그러나 계속 발전하는 React 특성의 단점은 시간이 지나면서 모범 사례가 변경된다는 것입니다. 몇 가지 최신 성능의 이점을 활용하려면 새로 추가된 기능을 신중하게 연구해야 합니다. 이 작업이 항상 쉬운 것은 아니며, 간단하지 않은 경우도 있습니다.

이 문서에서는 2021년 React에 적용되는 모범 사례를 살펴보겠습니다.


규칙

React로 작업을 구성하려면 몇 가지 규칙을 따르는 것이 좋습니다. 일부 규칙은 툴의 원활한 작동에도 필요합니다. 예를 들어 camelCase로 컴포넌트 이름을 지정할 경우 다음 코드는 작동하지 않습니다.

const myComponent = () => <div>Hello World!</div>;
​
ReactDOM.render(<myComponent />, document.querySelector('#app'));


Babel의 표준 JSX 변환기(또는 TypeScript)에서 명명 규칙을 사용하여 React에 문자열을 전달할지 또는 식별자를 전달할지를 결정하기 때문입니다.

따라서 변환된 코드는 다음과 같습니다.

const myComponent = () => React.createElement("div", null, "Hello World!");
​
ReactDOM.render(React.createElement("myComponent", null), document.querySelector('#app'));


우리가 원한 것이 아니므로 PascalCase를 대신 사용할 수 있습니다. 아래 코드에서는 JSX 변환기가 사용자 정의 컴포넌트와 필요한 참조의 사용을 감지합니다.

const MyComponent = () => <div>Hello World!</div>;
​
ReactDOM.render(<MyComponent />, document.querySelector('#app'));


아래 코드에서는 모두 정상적으로 작동합니다.

ReactDOM.render(React.createElement(MyComponent, null), document.querySelector('#app'));


다른 규칙도 덜 엄격하지만 준수해야 합니다. 예를 들어, JSX 식 대신 따옴표 있는 문자열 속성을 사용하는 것이 좋습니다.

// avoid
<input type={'text'} />
​
// better
<input type="text" />


마찬가지로, 속성의 따옴표 스타일을 일관되게 유지하는 것이 좋습니다. 대부분의 가이드에서는 JS 식에 작은따옴표가 있는 문자열을 사용하고, React 속성에 큰따옴표가 있는 문자열을 사용하도록 설명합니다. 결국, 코드베이스 내에서 사용법이 일관되기만 하면 사용하는 스타일은 중요하지 않습니다.

규칙과 속성의 경우 camelCase 사용에 대한 표준 JS 명명 규칙도 따라야 합니다.

// avoid
const MyComponent = ({ is_valid, Value }) => {
    // ...
    return null;
};
​
// better
const MyComponent = ({ isValid, value }) => {
    // ...
    return null;
};


더욱이, 기본 제공 HTML 컴포넌트 속성(예: style 또는 className)의 이름을 잘못 사용하지 않도록 해야 합니다. 속성을 사용하는 경우 해당 기본 제공 컴포넌트로 전달합니다. 또한 원래 형식(예: style의 경우 CSS 스타일 개체, className의 경우 문자열)으로 유지합니다.

// avoid
const MyComponent = ({ style, cssStyle }) => {
    if (style === 'dark') {
        // ...
    }
​
    // ...
    return <div style={cssStyle}>...</div>;
};
​
// better
const MyComponent = ({ kind, style }) => {
    if (kind === 'dark') {
      // ...
    }
​
    // ...
    return <div style={style}>...</div>;
};


이렇게 하면 속성의 의도가 훨씬 더 명확해지고 대규모 컴포넌트 컬렉션의 효율적인 사용에 중요한 일관성 수준이 설정됩니다.


컴포넌트 분리

React의 가장 큰 장점은 컴포넌트를 쉽게 테스트하고 추론할 수 있다는 것입니다. 그러나 이 기능은 컴포넌트가 작고 해당 기능을 지원하는 전용인 경우에만 가능합니다.

이전에 React가 처음 인기를 얻기 시작했을 때는 대규모 컴포넌트를 효율적으로 구성하기 위해 컨트롤러와 보기 컴포넌트의 개념을 도입했습니다. 오늘날에는 전용 state 컨테이너와 훅(hook)이 있지만 어떤 방식으로든 컴포넌트를 구성하고 분류하는 것이 여전히 효율적입니다.

일부 데이터를 로드하는 간단한 예제를 살펴보겠습니다.

const MyComponent = () => {
    const [data, setData] = React.useState();
​
    React.useEffect(() => {
      let active = true;
​
      fetch('...')
        .then(res => res.json())
        .then(data => active && setData(data))
        .catch(err => active && setData(err));
​
​
      return () => {
        active = false;
      };
    }, []);
​
    return (
      data === undefined ?
        <div>Loading ...</div> :
        data instanceof Error ?
          <div>Error!</div> :
          <div>Loaded! Do something with data...</div>
    );
};


물론, 여기서는 컴포넌트 없는 작업이 더 적합합니다. 그러나 요점은 작성된 컴포넌트가 데이터를 수집하고 표시해야 한다는 것입니다.

더 깔끔한 모델은 다음과 같은 분리를 의미합니다.

const MyComponent = ({ error, loading, data }) => {
    return (
      loading ?
        <div>Loading ...</div> :
        error ?
          <div>Error!</div> :
          <div>Loaded! Do something with data...</div>
    );
};
​
const MyLoader = () => {
    const [data, setData] = React.useState();
​
    React.useEffect(() => {
      let active = true;
​
      fetch('...')
        .then(res => res.json())
        .then(data => active && setData(data))
        .catch(err => active && setData(err));
​
      return () => {
        active = false;
      };
    }, []);
​
    const isError = data instanceof Error;
​
    return (
      <MyComponent
        error={isError ? data : undefined}
        loading={data === undefined}
        data={!isError ? data : undefined} />
    );
};


모델을 더욱 향상시키기 위해 가장 이상적인 분리는 다음과 같이 사용자 정의 훅(hook)으로 추출하는 것입니다.

function useRemoteData() {
    const [data, setData] = React.useState();
​
    React.useEffect(() => {
      let active = true;
​
      fetch('...')
        .then(res => res.json())
        .then(data => active && setData(data))
        .catch(err => active && setData(err));
​
      return () => {
        active = false;
      };
    }, []);
​
    const isError = data instanceof Error;
​
    return [data === undefined, !isError ? data : undefined, isError ? data : undefined];
}
​
    const MyComponent = () => {
    const [loading, data, error] = useRemoteData();
​
    return (
      loading ?
        <div>Loading ...</div> :
        error ?
          <div>Error!</div> :
          <div>Loaded! Do something with data...</div>
    );
};


훅(Hook)

React 훅은 프론트 엔드 영역에서 가장 많이 논의된 기술 기능 중 하나입니다. 처음 소개되었을 때는 멋지고 혁신적인 것으로 간주되었습니다. 반면, 해가 지날수록 비판도 증가했습니다.

찬반 양론과 별도로, 훅 사용은 일반적으로 시나리오에 따라 모범 사례가 될 수 있습니다.

일부 훅은 다음과 같이 성능 최적화에 도움이 된다는 것에 유의하십시오.

  • useMemo는 다시 렌더링할 때마다 상당한 부담이 되는 계산을 피하는 데 도움이 됩니다.

  • useCallback은 useMemo와 유사하게 안정적인 처리기를 생성하지만 콜백에 보다 편리하게 맞춰져 있습니다.

일례로, useMemo를 사용하지 않는 다음 코드를 살펴보겠습니다.

const MyComponent = ({ items, region }) => {
    const taxedItems = items.map(item => ({
        ...item,
        tax: getTax(item, region),
    }));
​
    return (
        <>
          {taxedItems.map(item => <li key={item.id}>
            Tax: {item.tax}
          </li>)}
        </>
    );
};


배열에 많은 항목이 있을 수 있고 getTax 작업이 실제로 상당한 부담이 된다는 점을 고려할 때 최소한의 항목과 영역만 변경한다고 가정하더라도 다시 렌더링하는 데 오랜 시간이 걸립니다.

따라서 useMemo를 사용하면 코드에 큰 도움이 됩니다.

const MyComponent = ({ items, region }) => {
    const taxedItems = React.useMemo(() => items.map(item => ({
        ...item,
        tax: getTax(item, region),
    })), [items, region]);
​
    return (
        <>
          {taxedItems.map(item => <li key={item.id}>
            Tax: {item.tax}
          </li>)}
        </>
    );
};


useMemo의 장점은 거의 보이지 않는다는 것입니다. 보시다시피 계산을 함수로 래핑하기만 하면 됩니다. 다른 변경 사항은 필요하지 않습니다.

더 미묘한 문제는 useCallback이 없는 경우입니다. 다음과 같은 일반적인 코드를 살펴봅시다.

const MyComponent = () => {
    const save = () => {
      // some computation
    };
    return <OtherComponent onSave={save} />;
};


지금은 OtherComponent에 대해 아무것도 모르지만 여기서 다음과 같이 변경될 수 있습니다.

  • 순수 컴포넌트로, 모든 속성이 그대로 유지되는 한 다시 렌더링할 수 없습니다.

  • 메모이제이션(Memoization) 또는 효과 훅에서 콜백을 사용합니다.

  • 속성 중 하나를 사용하는 컴포넌트에 콜백을 전달합니다.

어떤 방식으로든, 기본적으로 변경되지 않은 속성으로 값을 전달하면 값도 변경되지 않아야 합니다. 함수가 렌더링 함수 내에서 선언된다는 사실이 문제가 됩니다.

쉬운 해결 방법은 useCallback을 사용하여 동일하게 작성하는 것입니다.

const MyComponent = () => {
    const save = React.useCallback(() => {
      // some computation
    }, []);
    return <OtherComponent onSave={save} />;
};


이제 다시 컴퓨팅된 콜백은 배열에 제공된 종속성 중 하나가 변경된 경우에만 수행됩니다. 그렇지 않으면 이전 콜백(예: 안정적인 참조)이 반환됩니다.

이전과 마찬가지로, 이 최적화에 필요한 코드 변경은 거의 없습니다. 따라서 항상 useCallback을 사용하여 콜백을 래핑해야 합니다.


컴포넌트

순수 컴포넌트의 경우 클래스 컴포넌트에는 PureComponent 추상화가 있었던 반면, 기능 순수 컴포넌트는 명시적으로 메모를 사용하여 React로 도입할 수 있습니다.

// no memoed component
const MyComponent = ({ isValid }) => (
    <div style={{ color: isValid ? 'green' : 'red' }}>
      status
    </div>
    );
​
    // memoed component
    const MyComponent = React.memo(({ isValid }) => (
    <div style={{ color: isValid ? 'green' : 'red' }}>
      status
    </div>
));


React 설명서에서는 메모에 대해 다음과 같이 설명합니다.

“컴포넌트가 동일한 속성이 제공될 때 동일한 결과를 렌더링하는 경우 특정 사례에서 결과를 메모이제이션하여 성능을 향상시키기 위해 컴포넌트를 React.memo 호출로 래핑할 수 있습니다. 이렇게 하면 React에서 컴포넌트 렌더링을 건너뛰고 마지막으로 렌더링된 결과를 재사용합니다.”

React에서 수행하는 다른 비교와 마찬가지로, 속성은 얕은 수준으로만 비교됩니다. 따라서 이 최적화는 전달할 항목을 신중하게 선택한 경우에만 적용됩니다. 예를 들어 배열, 객체, 함수 등의 복잡한 속성에 useMemo와 기타 기법을 사용하는 경우입니다.

기능 컴포넌트를 배타적으로 사용한 것을 눈치챘을 수도 있습니다. 사실상, 훅이 도입된 이후에는 클래스 컴포넌트 없이 작업할 수 있습니다.

클래스 컴포넌트를 계속 사용하는 이유는 다음 두 가지뿐입니다.

  1. 보다 정교한 수명 주기 이벤트에 액세스하려는 경우입니다. 예를 들어, shouldComponentUpdate입니다.

  2. 오류 경계를 도입하려고 하는 경우입니다.

그러나 이 경우에도 React 클래스 컴포넌트 한 개만 작성하면 요구를 충족할 수 있습니다.

export class Boundary extends React.Component {
    state = {
      error: undefined,
    };
​
    componentDidCatch(error) {
      this.setState({
        error,
      });
    }
​
    render() {
      const { error } = this.state;
      const { children, ShowError } = this.props;
​
      if (error) {
        return <ShowError error={error} />;
      }
​
      return children;
    }
}


컴포넌트는 자식에 표시될 수 있는 모든 오류를 catch할 뿐만 아니라 전달된 대체 컴포넌트를 단일 속성(오류)를 받는 ShowError로 표시합니다.


연산자

일부 연산자를 사용하여 React의 트리 구성을 간소화할 수 있습니다. 예를 들어, 3항 연산자를 사용하면 다음과 같은 코드를 작성할 수 있습니다.

<div>
    {currentUser ? <strong>{currentUser}</strong> : <span>Not logged in</span>}
</div>


&& 및 || 같은 부울 연산자도 유용할 수 있지만 몇 가지 주의할 사항이 있습니다. 다음 코드를 확인해 보겠습니다.

<div>
    {numUsers && <i>There are {numUsers} users logged in.</i>}
</div>


numUsers가 항상 0과 총 사용자 수 사이의 숫자라고 가정할 경우 numUsers가 양수이면 예상 출력을 얻게 됩니다.

<div>
    <i>There are 5 users logged in.</i>
</div>


그러나 사용자 수가 0인 경우 다음과 같이 출력됩니다.

<div>
    0
</div>


원하는 출력이 아닐 수 있으므로 여기서는 부울 변환이나 더 명시적인 비교가 도움이 될 수 있습니다. 일반적으로 다음 코드가 더 읽기 쉽습니다.

<div>
    {numUsers > 0 && <i>There are {numUsers} users logged in.</i>}
</div>


이제 사용자 수가 0인 경우 다음과 같이 출력됩니다.

<div>
</div>


3항 연산자를 배타적 부울 연산자로 사용하면 문제가 완전히 해결됩니다. 그러나 아무것도 렌더링하지 않으려는 상태는 어떻게 해야 할까요? 다음과 같이 false 또는 빈 조각을 사용할 수 있습니다.

<div>
    {numUsers ? <i>There are {numUsers} users logged in.</i> : <></>}
</div>


빈 조각을 사용할 경우 나중에 콘텐츠를 추가할 수 있는 장점이 있습니다. 그러나 React를 잘 모르는 사용자에게는 약간 이상해 보일 수 있습니다.


결론

이 블로그에서는 React 코드베이스 작업을 더 용이하게 하는 몇 가지 모범 사례를 알아보았습니다. 클래스형 컴포넌트에서 함수형 컴포넌트로 전환하여 훅을 자세히 살펴볼 수 있습니다. 이렇게 하면 동작 측면은 함수에서 모두 수행되고 렌더링은 컴포넌트 내에서 정의되는 문제 분리를 자동으로 도입할 수 있습니다.

적절한 연산자, 훅, 문제 분리 사용과 같은 몇 가지 기법과 함께 일련의 유용한 규칙을 따르면 쉽게 유지 보수하고 확장할 수 있는 깔끔한 코드베이스를 갖게 됩니다.

물론, 모범 사례를 사용하는 최상의 방법은 이미 규칙을 따르는 컴포넌트를 배타적으로 사용하는 것입니다. 예를 들어, GrapeCity의 React 호환 제품을 사용하면 항상 안심할 수 있습니다. 아래 두가지 제품에 대해 자세히 알아보세요!


 

 
  • 페이스북으로 공유
  • 트위터로  공유
  • 링크 복사
  • 카카오톡으로 보내기

댓글목록

등록된 댓글이 없습니다.

메시어스 홈페이지를 통해 제품에 대해서 더 자세히 알아 보세요!
홈페이지 바로가기

인기글

더보기
  • 인기 게시물이 없습니다.
메시어스 홈페이지를 통해 제품에 대해서 더 자세히 알아 보세요!
홈페이지 바로가기
이메일 : sales-kor@mescius.com | 전화 : 1670-0583 | 경기도 과천시 과천대로 7길 33, 디테크타워 B동 1107호 메시어스(주) 대표자 : 허경명 | 사업자등록번호 : 123-84-00981 | 통신판매업신고번호 : 2013-경기안양-00331 ⓒ 2024 MESCIUS inc. All rights reserved.