TIL_71_재사용 컴퍼넌트

JIEUN·2021년 4월 26일
0
post-thumbnail

컴퍼넌트 재사용

"재사용"이 가능한 UI 단위..
UI? User Interface
컴퍼넌트 재사용을 통해 컴퍼넌트 구조를 "효율적으로" 설계.
부모 컴퍼넌트에서 자식 컴퍼넌트한테 여러 프롭스를 보낸다.
프롭스의 내용은 고정된 값이 아니다.
State는 변경이 가능한 상태.
컴퍼넌트가 넘겨받는 프롭스에 따라, 서로 다른 UI를 보여줄 수 있다.

// before -> 두 개의 내용을 표현하기 위한 두 개의 파일
isMyID ? <ProfileMyID /> : <ProfileOtherID />

// after -> 두 개의 내용을 표현하기 위한 하나의 파일
const MY_ID = '12345'
const OTHER_ID = '54321'

isMyID ? <Profile userID={MY_ID} /> : <Profile userID={OTHER_ID} />

위와 같이 props를 통해 각각 달리 표시해주고 싶은 값들을 해당 컴퍼넌트에 넘겨주면 하나의 컴퍼넌트로 여러가지 형태로 사용할 수 있는 재사용성 높은 컴퍼넌트가 되는 것.

// Modal.js
export default class Modal extends Component {
  state={
    isLogin: false,
  }

  handlePropsData = () => {
    this.setState({isLogin: !this.state.isLogin})
  };

  render() {
  const {isLogin} = this.state;
    return (
      <div className="Modal">
        <Form
         isLogin={isLogin}
         handlePropsData={this.handlePropsData} 
         format={isLogin ? signInProps : signUpProps} 
        />

//Form 이란 컴퍼넌트를 불러왔고 format 이란 props 이름으로 
 //signUpProps 객체 데이터를 넘겨주고 있는 것.

      </div>
     );
   }
 }

  const signUpProps = {
    type: "signUp",
    text: "회원가입",
    data: [
      {
        type: "name",
        text: "이름",
      },
      {
        type: "email",
        text: "이메일",
      }
      {
        type: "password",
        text: "비밀번호",
      }
    ],
    msg: "이미 가입하셨나요?",
    btn: "로그인",
  };

  const signInProps = {
    type: "signIn",
    text: "로그인",
    data: [
      {
        type: "email",
        text: "이메일",
      },
      {
        type: "Password",
        text: "비밀번호",
      },
   ],
   msg: "계정이 없으신가요?",
   btn: "회원가입",
 };
//  Form.js
export default class Form extends Component {
  render() {
    const {format, handlePropsData} = this.props;

    return(
      <FormLayout>
        <h2>{format.text}</h2>
        <div>
          {format.data.map((input, idx) => (
            <Input key={idx} type={input.type} text={input.text} />
          ))}
         </div>
         <Button value={format.text} />
         <p className="isAlreadyLogin">
           {format.msg} <span onClick={handlePropsData}>{format.btn}</span>
           </p>
         </formLayout>
      //<formLayout> 이란 컴퍼넌트로 감싸고 있음을 알 수 있다.
      //감싸고 있는 내용이 바로 props.children인 것.
        );
      }
    }
export default class FormLayout extends Component {
  render() {
    return (
      <div className="Form">
        <header>
          <div className="logo" />
         </header>
         {this.props.children}
//위에서 감싸준 내용들을 여기에서 props.children 으로 받고있다.
         <button>Facebook</button>
         </div>
        );
      }
   }
// Input.js
export default class Input extends Component {
  render() {
    const { type, text } = this.props;

  return (
    <div className="Input">
      <div className="inputWrapper">
        <input type={type} placeholder={text} />
      </div>
    </div>
  );
 }
}
// Button.js
export default class Button extends Component {
  render() {
    return <div className="Button">{this.props.value}</div>;
  }
 }

Input, Button 을 다 컴퍼넌트로 분리하여 관리하고 있음을 알 수 있다.

컴퍼넌트는 왜 분리할까?

코드의 재사용성과 가독성을 올리려면 관심사의 분리가 필요 -> 복잡한 코드를 비슷한 기능을 하는 코드끼리 별도로 관리를 하는 것.
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 = friends.filter(item => item.age <= ageLimit);
  function odAdd() {
      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 = friends.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 컴퍼넌트와 같이 상태값과 비즈니스 로직을 관리하고 있는 컴퍼넌트를 containerf 폴더에 넣어두어 별도로 관리하는 것을 추천한다.

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

0개의 댓글