프로필

프로필 사진
Popomon
Frontend Developer
(2020/12 ~)

    카테고리

    포스트

    [Frontend/React] 18. 재사용성을 고려한 컴포넌트의 분리

    2020. 10. 5. 16:03

    꿈가게: To Do List - iOS

    꿈가게: To Do List - Android

    컴포넌트를 왜 분리해야 하는가?

    코드의 재사용성과 가독성을 올리려면 관심사의 분리가 필요합니다. 프로그래밍에서 관심사의 분리란 복잡한 코드를 비슷한 기능을 하는 코드끼리 별도로 관리를 하는 것을 말합니다. UI를 처리하는 코드나 서버 API를 호출하는 코드 또는 DB를 관리하는 코드 같은 것들을 서로 관심사가 다르다고 보고 파일을 분리해서 관리하는 것이 좋습니다.

     

    코드를 작성하다가 어느 순간 코드가 복잡하다고 느껴진다면, 관심사의 분리가 필요한 순간인지 생각해보는게 좋습니다. 리액트에서도 마찬가지로 하나의 컴포넌트에서 모든 기능을 구현할 수 없기 때문에, 여러 개의 컴포넌트를 만들어서 조립을 합니다. 이 때, 하나의 폴더 안에 모든 컴포넌트를 만들어서 관리를 하면 시간이 흐를수록 컴포넌트가 많아져서 원하는 컴포넌트를 찾기가 힘들어질 것입니다. 그래서 연관된 컴포넌트끼리 폴더를 만들어서 관리는 것이 컴포넌트를 찾기에 수월합니다.

     

    하지만,  컴포넌트가 분리되다보니 상태값이 여기저기 흩어지고, 중복되는 현상들이 발생할 수 있습니다. 이러한 현상을 개선하기 위해서는 모든 컴포넌트가 상태값을 가지는 것이 아니라 몇개의 컴포넌트로 한정해서 관리를 하는 것이 좋습니다. 그리고 컴포넌트가 상태값이나 비지니스 로직 코드를 가지고 있으면 재사용하기가 힘들기 때문입니다.

     


    컴포넌트를 분리하는 기준

    비즈니스 로직과 상태값의 유무로 컴포넌트를 분리하는 것을 추천합니다. 재사용성이 좋은 컴포넌트와 그렇지 않은 컴포넌트로 분리를 하는 것입니다.

     

    재사용성이 좋은 컴포넌트의 조건은 아래와 같습니다.

    1. 비즈니스 로직이 없다.

    2. 상태값이 없다. (단, 마우스 오버와 같은 UI 효과를 위한 상태값은 제외한다.)

     

    컴포넌트를 분리하는 예를 들어보도록 하겠습니다. 아래의 코드는 상태값/비즈니스로직이 모두 담겨있는 컴포넌트입니다.

     

    import React, { useState } from 'react';
    import { getNextFriend } from './data';
    
    export default function App() {
      const [friends, setFriends] = useState();
      const [ageLimit, setAgeLimit] = useState(MAX_AGE_LIMIT);
    
      const friendsWithAgeLimit = friendds.filter(item => item.age <= ageLimit);
      function onAdd() {
        const friend = getNextFriend();
        setFriends([...friends, friend]);
      }
      function onChangeOption(e) {
        const value = Number(e.currentTarget.value);
        setAgeLimit(value);
      }
      return (
        <div>
          <button onClick={onAdd}>친구추가</button>
          <div>
            <select onChange={onChangeOption} value={ageLimit}>
              {AGE_LIMIT_OPTIONS.map(option => {
                <option key={option} value={option}>{option}</option>
              })}
            </select>
          </div>
          <ul>
            {friendsWithAgeLimit.map(friend => {
                <li key={friend.id}>{`${friend.name} (${friend.age})`}</li>
            })}}
          </ul>
        </div>
      )
    }
    
    const MAX_AGE_LIMIT = 100;
    const AGE_LIMIT_OPTIONS = [15, 20, 25, MAX_AGE_LIMIT];
    

     

    친구 목록을 렌더링 하는 컴포넌트를 따로 빼서 적용을 해보겠습니다. 아래의 코드와 같이 friends 라는 배열을 받아서 단순히 렌더링만 해주는 컴포넌트입니다. 여기에는 비즈니스 로직도 없고, 상태값도 없습니다.

     

    import React from 'react';
    
    export default function FriendList({ friends }) {
      return (
        <ul>
          {friends.map(friend => {
              <li key={friend.id}>{`${friend.name} (${friend.age})`}</li>
          })}}
        </ul>
      )
    }

     

    그러면 이제 FriendList 컴포넌트를 아래와 같이 적용할 수 있습니다.

     

    import React, { useState } from 'react';
    import { getNextFriend } from './data';
    import FriendList from './component/FriendList';
    
    export default function App() {
      const [friends, setFriends] = useState();
      const [ageLimit, setAgeLimit] = useState(MAX_AGE_LIMIT);
    
      const friendsWithAgeLimit = friendds.filter(item => item.age <= ageLimit);
      function onAdd() {
        const friend = getNextFriend();
        setFriends([...friends, friend]);
      }
      function onChangeOption(e) {
        const value = Number(e.currentTarget.value);
        setAgeLimit(value);
      }
      return (
        <div>
          <button onClick={onAdd}>친구추가</button>
          <div>
            <select onChange={onChangeOption} value={ageLimit}>
              {AGE_LIMIT_OPTIONS.map(option => {
                <option key={option} value={option}>{option}</option>
              })}
            </select>
          </div>
          <FriendList friends={friendsWithAgeLimit} />
        </div>
      )
    }
    
    const MAX_AGE_LIMIT = 100;
    const AGE_LIMIT_OPTIONS = [15, 20, 25, MAX_AGE_LIMIT];
    

     

    다음으로 숫자를 선택하는 NumberSelect 컴포넌트를 작성해 보겠습니다.

     

    import React from 'react';
    
    export default function NumberSelect({ value, options, onChange }) {
      function onChangeOption(e) {
        const value = Number(e.currentTarget.value);
        onChange(value);
      }
      
      return (
        <div>
          <select onChange={onChangeOption} value={value}>
            {options.map(option => {
              <option key={option} value={option}>{option}</option>
            })}
          </select>
        </div>
      );
    }

     

    이제  App 컴포넌트에 다시 NumberSelect 컴포넌트를 추가로 적용해 보겠습니다.

     

    import React, { useState } from 'react';
    import { getNextFriend } from './data';
    import FriendList from './component/FriendList';
    import NumberSelect from './component/NumberSelect';
    
    export default function App() {
      const [friends, setFriends] = useState();
      const [ageLimit, setAgeLimit] = useState(MAX_AGE_LIMIT);
    
      const friendsWithAgeLimit = friendds.filter(item => item.age <= ageLimit);
      function onAdd() {
        const friend = getNextFriend();
        setFriends([...friends, friend]);
      }
      
      return (
        <div>
          <button onClick={onAdd}>친구추가</button>
          <NumberSelect 
            value={ageLimit}
            options={AGE_LIMIT_OPTIONS}
            onChange={setAgeLimit}
          />
          <FriendList friends={friendsWithAgeLimit} />
        </div>
      )
    }
    
    const MAX_AGE_LIMIT = 100;
    const AGE_LIMIT_OPTIONS = [15, 20, 25, MAX_AGE_LIMIT];
    

     

    실제로 App 컴포넌트는 모든 컴포넌트의 루트 컴포넌트이기 때문에 현재 Friend 에 관한 페이지 컴포넌트를 FriendPage 라는 별도의 컴포넌트로 분리해 두도록 하겠습니다.

     

    import React, { useState } from 'react';
    import { getNextFriend } from './data';
    import FriendList from './component/FriendList';
    import NumberSelect from './component/NumberSelect';
    
    export default function FriendPage() {
      const [friends, setFriends] = useState();
      const [ageLimit, setAgeLimit] = useState(MAX_AGE_LIMIT);
    
      const friendsWithAgeLimit = friendds.filter(item => item.age <= ageLimit);
      function onAdd() {
        const friend = getNextFriend();
        setFriends([...friends, friend]);
      }
      
      return (
        <div>
          <button onClick={onAdd}>친구추가</button>
          <NumberSelect 
            value={ageLimit}
            options={AGE_LIMIT_OPTIONS}
            onChange={setAgeLimit}
          />
          <FriendList friends={friendsWithAgeLimit} />
        </div>
      )
    }
    
    const MAX_AGE_LIMIT = 100;
    const AGE_LIMIT_OPTIONS = [15, 20, 25, MAX_AGE_LIMIT];
    

     

    아래와 같이 App 컴포넌트는 나중에 라우팅에 대한 처리만 가져가게 됩니다.

     

    import React from 'react';
    import FriendPage from './container/FriendPage';
    
    export default function App() {
      return (
        <div>
          <FriendPage />
        </div>
      )
    }

     

    지금 예시로 분리한 코드를 보면 FriendList, NumberSelector 코드는 비즈니스로직과 상태값을 가지고 있지 않습니다. 이러한 컴포넌트를 재사용 가능한 컴포넌트로 분리하여 components 폴더에 넣어두고, FriendPage 컴포넌트와 같이 상태값과 비즈니스 로직을 관리하고 있는 컴포넌트를 containers 폴더에 넣어두어서 별도로 관리를 하는 것을 추천합니다.

     

     

     

     


    출처 : 실전 리액트 프로그리맹 - 인프런 (추천합니다 !!)

     

    실전 리액트 프로그래밍 - 인프런

    [실전 리액트 프로그래밍] 책의 저자 직강! 리액트의 기초부터 실전 활용법까지 익힐 수 있습니다. 초급 프레임워크 및 라이브러리 웹 개발 Front-End React Redux 웹 개발 온라인 강의 리액트(React)를 �

    www.inflearn.com