Base

컴포넌트내의 코드의 분리, 코드의 분리

Dr.Mlem 2024. 11. 28. 16:30
Q1.
어떤 사용자 정의 함수의 위치할 곳이 컴포넌트 안이냐 밖이냐에 대한 판단의 핵심은 훅의 종류가 아니라, 재사용성과 복잡성인거야?
 
 
 
A1.

ChatGPT의 말:

 

정확합니다.
사용자 정의 함수가 컴포넌트 내부에 위치할지 외부에 위치할지 결정하는 데 가장 중요한 판단 기준은 훅의 종류가 아니라, 함수의 재사용성과 복잡성입니다.


왜 재사용성과 복잡성이 판단의 핵심인가?

1. 재사용성

  • 함수가 다른 컴포넌트에서도 사용될 가능성이 있으면, 외부에 위치하는 것이 좋습니다.
  • 외부에 위치하면 중복 코드를 방지하고, 로직을 독립적으로 관리할 수 있습니다.

예:

  • 데이터 처리 함수, API 호출 로직, 복잡한 계산 로직 등은 여러 컴포넌트에서 사용될 가능성이 큽니다.
// 외부에 정의
const formatDate = (date) => new Date(date).toLocaleDateString();

const ComponentA = ({ date }) => <p>{formatDate(date)}</p>;
const ComponentB = ({ date }) => <p>{formatDate(date)}</p>;

 

2. 복잡성

  • 함수가 길고 복잡하거나, 상태 로직과 렌더링 로직을 혼합하면 코드가 읽기 어려워질 수 있습니다.
  • 이 경우, 로직을 외부로 분리하면 컴포넌트의 가독성과 유지보수성이 높아집니다.

예:

  • Reducer 함수, 복잡한 조건 처리 로직 등은 컴포넌트 외부로 분리하는 것이 좋습니다.
// 외부 Reducer 함수
const counterReducer = (state, action) => {
    switch (action.type) {
        case 'increment': return state + 1;
        case 'decrement': return state - 1;
        default: return state;
    }
};

const CounterComponent = () => {
    const [count, dispatch] = useReducer(counterReducer, 0);
    return (
        <div>
            <button onClick={() => dispatch({ type: 'increment' })}>+</button>
            <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
        </div>
    );
};

훅의 종류가 판단의 기준이 아닌 이유

훅은 단지 함수의 연결 지점입니다.

  • 훅 자체(useState, useReducer, useEffect 등)는 특정한 상태 관리나 사이드 이펙트를 처리하는 도구이며, 함수의 위치는 로직의 재사용성, 복잡성, 그리고 상태/props와의 의존성에 따라 결정됩니다.

예시 1: useReducer Reducer 함수

  • useReducer의 Reducer 함수는 독립적인 상태 처리 로직이므로, 복잡한 경우 대부분 외부에 위치하는 것이 자연스럽습니다.

예시 2: useEffect와 함께 사용되는 로직

  • useEffect 내부의 로직이 간단하다면 내부에 둘 수 있지만, 복잡한 데이터 처리 로직이 있다면 외부로 분리해 재사용할 수 있습니다.

컴포넌트 안/밖에 둘 때의 판단 기준

기준 컴포넌트 안에 둠 컴포넌트 밖에 둠
재사용성 특정 컴포넌트에서만 사용하는 경우. 여러 컴포넌트에서 사용하거나 범용적으로 필요한 경우.
복잡성 로직이 단순하고 컴포넌트와 밀접한 관계가 있는 경우. 로직이 복잡하여 분리가 가독성에 도움이 되는 경우.
상태/props 의존성 상태와 props에 강하게 의존하여 외부에 분리할 수 없는 경우. 상태/props와 분리 가능하고 매개변수로 처리할 수 있는 경우.
렌더링과의 연관성 렌더링 로직과 밀접하게 연결된 경우. 렌더링과 독립적으로 동작하는 로직일 경우.

결론

  • 훅의 종류는 판단 기준이 아니다.
    훅은 단지 상태 관리나 사이드 이펙트의 도구일 뿐, 함수의 위치를 결정하는 핵심 기준은 아니다.
  • 재사용성과 복잡성이 함수의 위치를 결정하는 핵심 기준이다.
    • 재사용 가능성이 높거나, 로직이 복잡하다면 컴포넌트 밖에 둔다.
    • 재사용 가능성이 없고 단순한 로직이라면 컴포넌트 안에 둔다.

이 기준을 따르면서 코드의 가독성과 유지보수성을 고려하는 것이 가장 중요합니다.

 
 
 
Q2.

나의 말:

이벤트 핸들러도 결국 마찬가지인가? 다른것보다도, 다시 쓰거나 로직이 복잡하면 밖에 두고, 1회성이거나 로직이 단순하면 안에 쓰고?
 
 
 
A2.

ChatGPT의 말:

 

정확히 그렇습니다.
이벤트 핸들러 역시 재사용성과 복잡성에 따라 컴포넌트 내부에 둘지 외부에 둘지 결정됩니다.
핵심은 "다른 데서 다시 쓸 일이 있는가?"와 "로직이 얼마나 복잡한가?"입니다.


이벤트 핸들러의 위치를 결정하는 기준

1. 재사용성

  • 컴포넌트 외부에 둘 때:
    • 이벤트 핸들러가 여러 컴포넌트에서 동일한 동작을 처리할 경우.
    • 공통적인 비즈니스 로직이 포함된 경우.
    예시: 여러 컴포넌트에서 공통적으로 사용하는 "폼 데이터 처리 함수":
// 공통 핸들러 정의 (외부)
const handleFormSubmit = (data) => {
    console.log("Submitting form:", data);
};

const FormA = () => (
    <form onSubmit={(e) => { e.preventDefault(); handleFormSubmit(dataA); }}>
        <button type="submit">Submit Form A</button>
    </form>
);

const FormB = () => (
    <form onSubmit={(e) => { e.preventDefault(); handleFormSubmit(dataB); }}>
        <button type="submit">Submit Form B</button>
    </form>
);
  • 컴포넌트 내부에 둘 때:
    • 이벤트 핸들러가 특정 컴포넌트에서만 사용되며, 재사용 가능성이 없는 경우.
    예시: 단순히 특정 컴포넌트에서 상태를 업데이트하는 핸들러:
const MyComponent = () => {
    const [count, setCount] = useState(0);

    const increment = () => setCount(count + 1);

    return (
        <div>
            <button onClick={increment}>Increment</button>
        </div>
    );
};

2. 복잡성

  • 컴포넌트 외부에 둘 때:
    • 이벤트 핸들러가 복잡한 로직(예: 여러 조건 분기, 데이터 가공 등)을 포함할 경우.
    • 외부로 분리하여 코드 가독성을 높이고, 로직을 테스트하기 쉽게 만듭니다.
    예시:
// 복잡한 핸들러 로직 (외부)
const handleComplexClick = (userData, actionType) => {
    if (actionType === 'SAVE') {
        console.log('Saving user data:', userData);
    } else if (actionType === 'DELETE') {
        console.log('Deleting user:', userData.id);
    } else {
        console.log('Unknown action');
    }
};

const UserComponent = ({ user }) => (
    <div>
        <button onClick={() => handleComplexClick(user, 'SAVE')}>Save</button>
        <button onClick={() => handleComplexClick(user, 'DELETE')}>Delete</button>
    </div>
);
  • 컴포넌트 내부에 둘 때:
    • 로직이 간단하고 컴포넌트의 상태나 props에 밀접하게 의존하는 경우.
    예시:
const MyComponent = () => {
    const [isVisible, setIsVisible] = useState(false);

    const toggleVisibility = () => setIsVisible(!isVisible);

    return (
        <div>
            <button onClick={toggleVisibility}>
                {isVisible ? "Hide" : "Show"}
            </button>
        </div>
    );
};

이벤트 핸들러 위치 선택의 표

기준 컴포넌트 안에 두기 컴포넌트 밖에 두기
재사용성 특정 컴포넌트에서만 사용된다. 여러 컴포넌트에서 동일한 로직을 사용할 수 있다.
로직의 복잡성 로직이 간단하며, 상태나 props를 직접 처리한다. 로직이 복잡하여 코드 분리가 가독성을 높인다.
상태/props 의존성 해당 컴포넌트의 상태/props에 직접적으로 강하게 의존한다. 상태나 props와 독립적으로 동작한다.
유지보수성 코드가 간결해지고, 컴포넌트와 핸들러가 밀접하게 연결됨. 로직 분리가 유지보수와 테스트에 더 용이.

결론

이벤트 핸들러도 재사용성과 복잡성이 위치를 결정하는 주요 기준입니다:

  1. 재사용 가능성 없음 + 단순한 로직 → 컴포넌트 내부에 두는 것이 더 적합.
  2. 재사용 가능성 있음 + 복잡한 로직 → 컴포넌트 외부로 분리하여 관리.

이 원칙을 적용하면 코드의 가독성과 유지보수성이 높아집니다.



Q3.

결국 이벤트 핸들러건 훅에 걸리는 함수건, 심지어 그게 함수가 아니고 어떠한 변수든지 뭔지 다 상관없이, 복잡한 코드 덩어리나, 재사용될 가능성이 높은 코드라면, 밖에 따로 정리해두는 것이 좋은건가?
 
 
 
A3.

ChatGPT의 말:

 

정확히 그렇습니다.

어떤 코드가 함수, 변수, 혹은 객체 등의 형태를 띠고 있든, 다음 두 가지 기준을 충족한다면 컴포넌트 외부로 분리하는 것이 좋습니다:

  1. 재사용 가능성이 높은 코드
    • 다른 컴포넌트나 파일에서 동일한 로직, 값, 또는 데이터를 활용할 가능성이 있다면, 외부에 분리하여 관리하는 것이 합리적입니다.
    • 중복 코드를 방지하고, 유지보수성을 높이는 데 기여합니다.
  2. 복잡한 코드 덩어리
    • 코드가 길거나, 여러 조건 분기와 데이터 처리를 포함해 가독성이 떨어질 경우, 외부로 분리하여 컴포넌트를 더 간결하게 유지하는 것이 좋습니다.
    • 코드의 모듈성을 높여 테스트와 디버깅이 더 쉬워집니다.

적용 기준: 재사용성 + 복잡성

기준 컴포넌트 안에 두기 컴포넌트 밖에 두기
재사용성 코드가 해당 컴포넌트에서만 사용되며, 다른 곳에서 필요 없음 코드가 여러 컴포넌트/파일에서 재사용될 가능성이 있음
복잡성 로직이 간단하고 컴포넌트와 밀접하게 연결됨 코드가 길거나, 조건 분기가 많아 가독성을 해침
유지보수성 간단한 코드로 컴포넌트와 강하게 연관 코드 분리가 유지보수와 테스트를 용이하게 만듦

예시: 변수, 함수, 객체를 분리하는 경우

1. 재사용성 높은 변수 (외부로 분리)

  • 안 좋은 방식 (중복된 코드):
const ComponentA = () => {
    const COLORS = ["red", "green", "blue"];
    return <div>{COLORS.map(color => <p>{color}</p>)}</div>;
};

const ComponentB = () => {
    const COLORS = ["red", "green", "blue"];
    return <div>{COLORS.map(color => <span>{color}</span>)}</div>;
};

 

  • 좋은 방식 (외부 분리):
// constants.js
export const COLORS = ["red", "green", "blue"];

// ComponentA.js
import { COLORS } from "./constants";
const ComponentA = () => (
    <div>{COLORS.map(color => <p>{color}</p>)}</div>
);

// ComponentB.js
import { COLORS } from "./constants";
const ComponentB = () => (
    <div>{COLORS.map(color => <span>{color}</span>)}</div>
);

2. 복잡한 로직을 가진 함수 (외부로 분리)

  • 안 좋은 방식 (복잡한 로직이 컴포넌트 내부에 존재):
const UserComponent = ({ users }) => {
    const sortedUsers = users
        .filter(user => user.isActive)
        .sort((a, b) => a.name.localeCompare(b.name));

    return <ul>{sortedUsers.map(user => <li>{user.name}</li>)}</ul>;
};
  • 좋은 방식 (외부 분리):
// utils.js
export const getSortedActiveUsers = (users) => {
    return users
        .filter(user => user.isActive)
        .sort((a, b) => a.name.localeCompare(b.name));
};

// UserComponent.js
import { getSortedActiveUsers } from "./utils";

const UserComponent = ({ users }) => {
    const sortedUsers = getSortedActiveUsers(users);
    return <ul>{sortedUsers.map(user => <li>{user.name}</li>)}</ul>;
};

3. 객체 데이터 (외부로 분리)

  • 안 좋은 방식 (객체 데이터가 여러 컴포넌트에 중복):
const ComponentA = () => {
    const USER_INFO = { name: "John", age: 30 };
    return <p>{USER_INFO.name}</p>;
};

const ComponentB = () => {
    const USER_INFO = { name: "John", age: 30 };
    return <p>{USER_INFO.age}</p>;
};
 
  • 좋은 방식 (외부 분리):
// data.js
export const USER_INFO = { name: "John", age: 30 };

// ComponentA.js
import { USER_INFO } from "./data";
const ComponentA = () => <p>{USER_INFO.name}</p>;

// ComponentB.js
import { USER_INFO } from "./data";
const ComponentB = () => <p>{USER_INFO.age}</p>;

결론

  • 재사용 가능하거나 복잡한 코드는 컴포넌트 외부로 분리하세요.
    • 재사용성이 높은 상수, 함수, 객체 데이터 등은 외부에 두어 중복을 줄이고 유지보수를 쉽게 합니다.
    • 복잡한 로직은 외부로 분리하여 코드 가독성을 높이고 컴포넌트를 간결하게 유지합니다.
  • 단순하고 재사용 가능성이 낮은 코드는 컴포넌트 내부에 두세요.
    • 짧고 직관적인 코드가 컴포넌트와 강하게 연관된 경우, 내부에 두는 것이 더 효율적입니다.

이 원칙은 이벤트 핸들러, 훅에 연결된 함수, 변수, 객체 등 코드의 모든 요소에 적용됩니다.

 

 

 

Q4.

그런데 이건 비단 React 만의 해당사항은 아니겠네?

 

 

 

A4.

ChatGPT의 말:

 

맞습니다.
이 원칙은 React에 국한되지 않고, 대부분의 프로그래밍 패러다임이나 언어에서 코드 구조화 및 유지보수를 고려할 때 적용되는 일반적인 소프트웨어 개발 원칙입니다.


관련된 소프트웨어 공학 원칙

  1. 단일 책임 원칙 (Single Responsibility Principle)
    • 각 코드 요소(함수, 클래스, 모듈 등)는 하나의 명확한 역할만 가져야 합니다.
    • 재사용 가능한 코드나 복잡한 로직은 별도의 모듈로 분리하는 것이 좋습니다.
    적용 사례:
    • 함수가 특정 데이터를 가공하고 반환하는 역할만 하도록 분리.
    • UI 렌더링 로직과 데이터 처리 로직을 명확히 분리.
  2. DRY 원칙 (Don't Repeat Yourself)
    • 중복된 로직이나 데이터는 한 곳에서만 정의하고 재사용해야 합니다.
    • 중복 코드는 유지보수 비용을 증가시키므로, 공통 부분을 별도로 분리하여 관리합니다.
    적용 사례:
    • 상수, 유틸리티 함수, 공통 API 호출 로직 등을 외부 모듈로 분리.
    • React 컴포넌트뿐만 아니라 일반 자바스크립트/파이썬/자바 코드에도 동일하게 적용.
  3. 코드 가독성 및 모듈화
    • 코드는 읽히기 쉬워야 한다. 함수가 너무 길거나 여러 역할을 수행하면 가독성이 떨어지므로, 적절히 분리해야 합니다.
    • 모듈화(Modularization)는 기능별로 코드를 나눠 유지보수와 확장을 용이하게 합니다.
    적용 사례:
    • React의 컴포넌트 분리, 함수 로직 분리.
    • Express.js에서 라우터와 비즈니스 로직을 분리.
    • 파이썬의 함수와 클래스를 모듈로 분리.

비 React 환경에서의 적용 예시

1. Python

  • 복잡한 로직을 함수로 분리하고, 재사용 가능하다면 외부 모듈로 관리.
# utils.py
def process_data(data):
    return [d.upper() for d in data if isinstance(d, str)]

# main.py
from utils import process_data

data = ["apple", "banana", 123, "cherry"]
print(process_data(data))

2. Node.js (Express)

  • 중복된 API 호출 로직이나 미들웨어를 분리하여 재사용.
// middleware/auth.js
const verifyToken = (req, res, next) => {
    const token = req.headers['authorization'];
    if (token) {
        // Token verification logic
        next();
    } else {
        res.status(401).send("Unauthorized");
    }
};

module.exports = verifyToken;

// routes/user.js
const express = require('express');
const verifyToken = require('../middleware/auth');

const router = express.Router();
router.get('/profile', verifyToken, (req, res) => {
    res.send("User Profile");
});

module.exports = router;

3. Java

  • 공통된 비즈니스 로직을 유틸리티 클래스로 분리.
// Utility.java
public class Utility {
    public static String toUpperCase(String input) {
        return input != null ? input.toUpperCase() : null;
    }
}

// Main.java
import Utility;

public class Main {
    public static void main(String[] args) {
        String result = Utility.toUpperCase("hello world");
        System.out.println(result);
    }
}

어디서나 적용 가능한 원칙

  1. 재사용성:
    공통된 로직, 데이터, 또는 기능은 재사용할 수 있도록 적절히 분리.
  2. 가독성:
    • 코드의 목적이 명확히 드러나도록 구성.
    • 복잡한 로직은 별도의 함수/모듈로 분리해 코드 길이를 줄이고 의도를 명확히 표현.
  3. 유지보수성:
    • 변경이 필요할 때, 최소한의 코드만 수정할 수 있도록 모듈화.
    • 중복을 없애고 단일 책임을 지향.
  4. 성능 최적화:
    • 불필요한 재생성을 방지하기 위해 정적 데이터, 함수, 객체 등을 외부로 분리.
    • React, Node.js, Java 등에서 동일하게 적용 가능.

결론

React뿐만 아니라 모든 프로그래밍에서:

  • 복잡하거나 재사용 가능한 코드외부로 분리.
  • 단순하고 한 번만 사용하는 코드는 해당 컨텍스트 내부에 작성.

이 원칙은 특정 기술 스택에 국한되지 않으며, 가독성, 유지보수성, 확장성을 높이는 소프트웨어 설계의 기본입니다.

 

 

 

PS. 

24/11/28

강사님 왈 : 재사용성에 따라서 나뉜다. 다시 쓰일것 같다면 밖에, 아니면 안에. 훅에 걸릴 함수냐, 이벤트 핸들러냐 이런 것은 중요하지 않다.