간단한 신호등 만들기

Singsoong·2024년 9월 11일
0

greatFrontEnd

목록 보기
1/1
post-thumbnail

요구사항

Build a traffic light where the lights switch from green to yellow to red after predetermined intervals and loop indefinitely. Each light should be lit for the following durations:

  • Red light: 4000ms
  • Yellow light: 500ms
  • Green light: 3000ms

You are free to exercise your creativity to style the appearance of the traffic light.

내 코드

주어진 요구사항에 맞게 간단한 신호등 컴포넌트를 구현하면 된다. 빨간 불은 4초, 노란 불은 0.5초, 초록 불은 3초를 유지하면 되고 이 과정이 무한반복 된다.

나는 총 7.5초 과정을 무한반복 해주고, 그 사이에서 현재 불 색(red | yellow | green)을 변경해주었다.

// TrafficLight.jsx
import { useState, useEffect } from "react";

export default function TrafficLight() {
  const [light, setLight] = useState("red");

  useEffect(() => {
    setInterval(() => {
      setLight("red");

      setTimeout(() => {
        setLight("yellow");
      }, 4000);

      setTimeout(() => {
        setLight("green");
      }, 4500);
    }, 7500);
  }, []);

  return (
    <div className="traffic_light_body">
      <div className={`traffic_light_item ${light === "red" ? "red" : ""}`} />
      <div
        className={`traffic_light_item ${light === "yellow" ? "yellow" : ""}`}
      />
      <div
        className={`traffic_light_item ${light === "green" ? "green" : ""}`}
      />
    </div>
  );
}
body {
  font-family: sans-serif;
}

.traffic_light_body {
  width: 300px;
  height: 100px;
  background-color: black;
  border-radius: 16px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 20px;
}

.traffic_light_item {
  width: 80px;
  height: 80px;
  border-radius: 80px;
}

.traffic_light_item.red {
  background-color: red;
}

.traffic_light_item.yellow {
  background-color: yellow;
}

.traffic_light_item.green {
  background-color: green;
}

결과물

빨간 불이 4초, 노란 불이 0.5초, 초록 불이 3초 유지되면서 무한 반복되는 신호등이 구현되었다. 하지만 솔루션을 확인한 후 아쉬운 점이 발견되었다.

솔루션

데이터 구조 정의

먼저, 신호등에 대한 데이터 구조를 정의하였다.

const config = {
  red: {
    duration: 4000,
    next: 'green',
  },
  yellow: {
    duration: 500,
    next: 'red',
  },
  green: {
    duration: 3000,
    next: 'yellow',
  },
};

데이터 구조를 정의하게 되면 아래와 같은 장점을 얻을 수 있다.

  • 재사용성:
    config 객체는 각 신호등 상태의 지속 시간(duration)과 다음 상태(next)를 명시적으로 정의하므로, 이 구조를 통해 상태 전환 로직을 여러 신호등에 재사용할 수 있습니다. 새로운 신호등 상태나 시간 변경 시, 해당 상태만 수정하면 되기 때문에 유지보수가 쉽습니다.

  • 확장성:
    새로운 신호 상태가 추가되거나 특정 상태의 지속 시간이 변경되더라도, config만 업데이트하면 전체 시스템에 적용할 수 있습니다. 이로 인해 코드 확장 및 변경이 용이합니다.

  • 명확한 흐름 제어:
    각 상태에서 다음 상태를 지정(next)함으로써, 신호등의 흐름이 명확하게 정의됩니다. red → green → yellow → red처럼 상태 전환이 명확하게 설정되므로, 상태 전환 로직을 복잡하게 구현할 필요 없이 이 객체만 참고하여 처리할 수 있습니다.

  • 간결성:
    신호등 상태와 전환 로직이 단일 구조로 관리되기 때문에 코드가 간결해지고, 여러 곳에서 동일한 논리 흐름을 따를 수 있습니다. 코드를 분산시키지 않고 중앙에서 제어할 수 있어 직관적입니다.

  • 유연성:
    각 상태마다 duration과 next를 개별적으로 설정할 수 있기 때문에, 필요에 따라 상태의 지속 시간이나 전환 순서를 쉽게 커스터마이즈할 수 있습니다. 이를 통해 다양한 조건에 맞춘 신호등 시스템을 빠르게 만들 수 있습니다.

메모리 누수 문제

import { useEffect, useState } from 'react';

function Light({ backgroundColor }) {
  return (
    <div
      aria-hidden={true}
      className="traffic-light"
      style={{ backgroundColor }}
    />
  );
}

export default function TrafficLight({
  initialColor = 'green',
  config,
  layout = 'vertical',
}) {
  const [currentColor, setCurrentColor] =
    useState(initialColor);

  useEffect(() => {
    const { duration, next } = config[currentColor];

    const timerId = setTimeout(() => {
      setCurrentColor(next);
    }, duration);

    return () => {
      clearTimeout(timerId);
    };
  }, [currentColor]);

  return (
    <div
      aria-live="polite"
      aria-label={`Current light: ${currentColor}`}
      className={[
        'traffic-light-container',
        layout === 'vertical' &&
          'traffic-light-container--vertical',
      ]
        .filter((cls) => !!cls)
        .join(' ')}>
      {Object.keys(config).map((color) => (
        <Light
          key={color}
          backgroundColor={
            color === currentColor
              ? config[color].backgroundColor
              : undefined
          }
        />
      ))}
    </div>
  );
}

그리고, 메모리 누수를 신경썼다. 먼저 타이머를 설정하고,

const timerId = setTimeout(() => {
  setCurrentColor(next);
}, duration);

컴포넌트가 언마운트 되거나 업데이트 될 때 타이머를 정리하였다. 불필요한 타이머가 쌓이는 것을 방지하였다.

return () => {
  clearTimeout(timerId);
};

웹 접근성

<div aria-live="polite" >

역할: 이 속성은 화면 판독기(screen reader)가 신호등의 상태 변화를 사용자에게 알리도록 합니다.

polite: aria-live="polite"는 상태 변경을 즉시 읽지 않고, 사용자가 현재 작업을 끝낸 후에 신호등 상태를 알립니다. 급하지 않은 정보에 적합한 방식입니다.

이점: 시각장애인 사용자들이 화면 판독기를 통해 신호등 상태 변경을 확인할 수 있게 해줍니다.

aria-label={`Current light: ${currentColor}`}

역할: 현재 신호등의 상태(빨간색, 노란색, 초록색)를 사용자에게 알려주는 역할을 합니다.

이점: 화면 판독기는 이 aria-label을 통해 현재 신호등이 어떤 상태인지 정확하게 읽어주므로, 사용자는 신호등의 시각적 상태를 알지 못해도 정보를 확인할 수 있습니다.

<div aria-hidden={true}  />

역할: 신호등의 시각적 표현 부분인 Light 컴포넌트에 aria-hidden={true}를 적용하여, 화면 판독기가 불필요하게 신호등의 시각적 요소를 읽지 않도록 방지합니다.

이점: 화면 판독기가 중복된 정보를 읽지 않게 하여, 사용자 경험을 향상시킵니다. 이미 aria-label로 상태를 전달했기 때문에 시각적 요소 자체는 굳이 읽을 필요가 없습니다.

이로 인해 시각장애인 사용자들도 신호등 상태를 명확하게 인식할 수 있고, 중복된 정보가 제공되지 않으므로 깔끔한 접근성 경험을 제공합니다.

느낀점

간단한 신호등 컴포넌트를 만들면서 아래와 같은 점이 아쉬웠다.

  1. 요구사항 미충족
    출제자가 의도한 요구사항은 빨간 불 -> 노란 불 -> 초록 불 -> 노란 불 -> 빨간 불 이 무한반복되는 것을 의도했는데 (일반적인 신호 체계), 나는 빨간 불 -> 노란 불 -> 초록 불 이 과정이 반복됨으로써 초록 불 이후 노란 불이 들어오지 않는다. 요구사항을 완벽하게 파악하지 못했던 점이 아쉬웠다.

  2. 데이터 구조 정의
    데이터 구조 정의하는 습관을 길러 명확하게 컨텍스트를 파악할 수 있게 하고, 코드 가독성을 높혀야겠다.

  3. 메모리 누수 문제
    타이머 함수 같은 경우에는 클린업 함수에서 정리하는 습관을 길러야겠다.

  4. 웹 접근성
    시멘틱 태그 말고도, 웹 접근성을 지킬 수 있는 요소들을 확인했다. 신호등 같은 경우에는 더더욱 중요한 aria-label 같은 요소를 작성하여 웹 접근성을 지키자.

출처

여기에서 코드를 직접 작성해볼 수 있습니다.
(greatfrontend)

profile
Frontend Developer

0개의 댓글