Post

React에 SOLID 원칙 중 SRP 원칙 적용하기

React에 SOLID 원칙 중 SRP 원칙 적용하기

지난 OCP 원칙에 이어 이번엔 React에서 적용하면 좋을 SRP 원칙에 대해서 알아보려고 한다!

SRP는 단일 책임 원칙으로, 하나의 클래스는 하나의 책임만 가져야 한다는 원칙이다.

컴포넌트가 한 가지 작업을 수행하도록 하기 위해,

  1. 많은 작업을 수행하는 큰 컴포넌트를 더 작은 컴포넌트로 나눌 수 있고,
  2. 관련 있는 기능들을 custom hook으로 캡슐화를 할 수 있고,
  3. 주요 컴포넌트 기능과 관련 없는 함수들을 별도의 유틸리티 함수로 추출할 수 있다.

프로젝트를 진행하면서, 이벤트 발생 후 toast ui를 띄워주는 기능을 만들었는데, 이 Toast 컴포넌트에 SRP 원칙을 적용해볼 수 있을 거 같아서 공유를 해보려고 한다.

SRP를 적용하기 전 Toast 컴포넌트

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import { useEffect, useState } from "react";
import styles from "./styles.module.scss";

const Toast = ({ message, onClose }) => {
  const [progress, setProgress] = useState(100);
  const [isClosing, setIsClosing] = useState(false);

  useEffect(() => {
    const duration = 2000;
    const interval = 50;
    const decrement = (interval / duration) * 100;

    const timer = setTimeout(() => {
      onClose();
    }, duration);

    const progressInterval = setInterval(() => {
      setProgress((prev) => Math.max(prev - decrement, 0));
    }, interval);

    return () => {
      clearTimeout(timer);
      clearInterval(progressInterval);
    };
  }, [onClose]);

  const handleClick = () => {
    setIsClosing(true);
    setTimeout(onClose, 500);
  };

  return (
    <div
      aria-hidden="true"
      onClick={handleClick}
      className={`${styles["toast"]} ${isClosing && styles["toast-closing"]}`}
    >
      {message}
      <div className={styles["progressBar"]}>
        <div className={styles["progress"]} style={{ width: `${progress}%` }} />
      </div>
    </div>
  );
};

export default Toast;

현재 코드에 대하여

현재 코드는 메세지 표시와 동시에 타이머 관리, progress bar 업데이트, progress bar 닫기 이벤트 처리 등 여러가지 책임을 가지고 있다. 이로써 SRP 원칙에 위배되며, 또한 모든 로직이 하나의 컴포넌트에 포함되어 있어 가독성 측면에서 좋지 않다.

SRP 원칙을 적용하여 개선해보자.

SRP를 적용한 Toast 컴포넌트

하나의 Toast Component를 아래와 같이 3개의 훅과 컴포넌트로 나누어봤다.

SRP를 적용하여 개선된 코드에 대해

useTimer hook

1
2
3
4
5
6
7
8
9
10
import { useEffect } from "react";

const useTimer = (duration, onTimeUp) => {
  useEffect(() => {
    const timer = setTimeout(onTimeUp, duration);
    return () => clearTimeout(timer);
  }, [duration, onTimeUp]);
};

export default useTimer;
  • 타이머를 별도로 관리하는 훅을 별도로 분리하여, Toast 컴포넌트는 타이머 로직에서 분리가 되었다.

ProgressBar Component

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { useState, useEffect } from "react";
import styles from "./styles.module.scss";

const ProgressBar = ({ duration }) => {
  const [progress, setProgress] = useState(100);

  useEffect(() => {
    const interval = 50;
    const decrement = (interval / duration) * 100;
    const progressInterval = setInterval(() => {
      setProgress((prev) => Math.max(prev - decrement, 0));
    }, interval);
    return () => clearInterval(progressInterval);
  }, [duration]);

  return (
    <div className={styles["progressBar"]}>
      <div className={styles["progress"]} style={{ width: `${progress}%` }} />
    </div>
  );
};

export default ProgressBar;
  • progress bar를 독립적인 컴포넌트로 분리하여 Toast 컴포넌트는 메시지 표시와 닫기 이벤트 처리에만 신경 쓸 수 있다.

Toast Component

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import { useState } from "react";
import useTimer from "./useTimer";
import ProgressBar from "./ProgressBar";
import styles from "./styles.module.scss";

const Toast = ({ message, onClose }) => {
  const [isClosing, setIsClosing] = useState(false);

  useTimer(2000, onClose);

  const handleClick = () => {
    setIsClosing(true);
    setTimeout(onClose, 500);
  };

  return (
    <div
      aria-hidden="true"
      onClick={handleClick}
      className={`${styles["toast"]} ${isClosing && styles["toast-closing"]}`}
    >
      {message}
      <ProgressBar duration={2000} />
    </div>
  );
};

export default Toast;
  • 메세지 표시와 닫기 이벤트 처리에만 집중할 수 있고, timer와 progress bar 업데이트는 별도의 컴포넌트와 훅에서 처리한다.

SRP 적용한 코드의 결과

Toast 컴포넌트에 SRP를 적용하여 이렇게 개선한다면

  1. 코드의 각 부분이 단일 책임을 가지게 되고,
  2. 유지보수가 더 쉬워지며,
  3. 각 기능을 독립적으로 테스트 할 수 있게 된다.

단일 책임 원칙에 따라 큰 모놀리식 코드 덩어리를 효과적으로 가져와 더 모듈화하고, 모듈화를 하면 코드를 파악하기 쉬워지고, 의도치 않은 중복 코드를 작성할 가능성이 줄어든다. 또한 작은 모듈은 테스트 및 수정하기가 더 쉽기 때문에 결과적으로 코드를 보다 쉽게 유지 관리할 수 있어 좋다.

우리가 작성한 컴포넌트는 서로 다른 가동부들 사이에 의존성이 훨씬 더 얽혀 있다는 것을 알 수 있고, 대부분의 경우 이는 적절하지 못한 추상화, 다재다능한 전역 컴포넌트 생성, 데이터의 잘못된 스코프 설정 등 잘못된 설계를 선택했기 때문이다. 그리고 이는 광범위한 리팩토링으로 해결할 수 있다!

Reference

Applying SOLID principles in React

This post is licensed under CC BY 4.0 by the author.

© yongb2n. Some rights reserved.

-->