개인 프로젝트

프로젝트를 진행하며 시행착오

학습하는 청년 2025. 1. 4. 14:21

최종 수정 : 25.1.7

아직 개발중이기에 지속적으로 기록할 예정입니다.


알게 된 지식

1. 특정 문장을 반복하는 기능

const phrases = ['무지(無知)를 끊다.', '지식의 체계화', '인생의 마중물'];

const Search = () => {
  const [currentIndex, setCurrentIndex] = useState([phrases[0]]);

  useEffect(() => {
    let currentIndex = 0;
    const interval = setInterval(() => {
      currentIndex = (currentIndex + 1) % phrases.length;
      setCurrentIndex([phrases[currentIndex]]);
    }, 3000);

    return () => clearInterval(interval);
  }, []);

currentIndex = (currentIndex + 1) % phrases.length;

 

처음에는 currentIndex++; 만 했다. 그랬더니 Index값이 계속 커져서 추후에는 문장이 나오지 않았다. 그래서 내가 작성한 문장들의 배열의 길이만큼 나머지 연산자(%)를 사용하여 처리했다. 생각해보면, 나머지 연산자를 사용하면 계속 순환하도록 만들 수 있다.


화살표 함수와 함수 선언식

일반 함수 선언식은 호이스팅이 되어 코드의 어느 위치에서든 접근이 가능하다.

일반적으로 커스텀 훅은 재사용 가능한 로직의 캡슐화가 목적이므로, 호이스팅을 통한 더 유연한 사용이 가능한 함수 선언식을 사용하는 것이 좋다.

화살표 함수는 자신만의 this 바인딩을 갖지 않는데, 훅에서는 this 컨텍스트가 중요하지 않아서 굳이 화살표 함수를 사용할 필요가 없다.

 

3. .globals.css

layout.tsx 파일에 globals.css를 import하지 않아 다른 페이지에서 계속 레이아웃이 적용되지 않는 문제가 있었다.

layout.tsx에 globals.css import 전 후

Q. 왜 이렇게 동작하지? 어떤 원리가 있는 걸까??


3, 꼬여버린 git...

지금 개발을 여러 브랜치를 만들어서 작업하고 있다. 그러다가 순서를 혼동해서 데이터들이 사라지는 불참사가 발생했다. 나름 revert도 해보고 했으나 어려웠다. 그래서 일단, 괜찮은 부분으로의 커밋으로 돌아가서 작업을 진행했다. 이렇게 했을 때의 단점은 orgin에 남는다는 사실이었는데 좀 더 찾아봤어야 하는데 개발 흐름만 3-4시간 끊긴 터라 그냥 진행했다. 좀 더 찾아보니 origin에서도 삭제할 수 있는 방법이 있었다. 이렇게 했더라면 git graph가 더 깔끔했을 텐데...

 

그래도 알게 됐으니 조금은 더 마음이 편해졌다. 대처할 수 있는 방법을 알게 되어 좋다. 개발 커밋을 잘 하는 게 최우선이겟지만!

1. git log를 통해 삭제할 commit 찾기
2. git reset을 통해 commit 삭제
     최근의 commit을 삭제하고 싶을 땐 git reset HEAD^
     최근의 n개의 commi을 삭제하고싶을땐 git reset HEAD~n 
3. git push -f origin "branch name"을 통해 github에 commit 삭제 동기화

4. Props Driling과 Props Lifting

카테고리를 누르면 그에 맞는 주제의 내용이 나오도록 하는 과정에서 고민하게 됐다.

page에서는 단순히 컴포넌트의 조합으로만 구성하고 싶었다. LetcutreSection 안에 Category 컴포넌트를 종속시켜도 됐지만 기존 방식대로 진행하기로 했다. props lifting을 해도 큰 지정이 없을거라는 판단이 들었다. 카테고리를 클릭함에 따라 해당하는 내용만 나오는 것이므로 별반 차이가 없었다. 또한, LectureSection 안에는 Filter, Search, Dropdown, Pagination

 컴포넌트가 존재하기에 큰 문제가 없을 것 같았다.

 

Jotai나 ContextAPI로 해야 하나 싶었지만, Props Driling이 깊지 않기에 Props Lifting 하는 방법으로 결정했다.

'use client';

import LectureSection from '@/components/knowledge/LectureSection';
import Category from '@/components/knowledge/Category';
import { useState } from 'react';

const KnowledgePage = () => {
  const [selectedCategory, setSelectedCategory] = useState('all');

  return (
    <div>
      <Category
        selectedCategory={selectedCategory}
        onCategoryClick={setSelectedCategory}
      />
      <LectureSection selectedCategory={selectedCategory} />
    </div>
  );
};

export default KnowledgePage;

5. 스크롤 바 제거

아래의 클래스를 추가해주면, 스크롤 바가 제거된다.

[-ms-overflow-style:'none'] [scrollbar-width:'none'] [&::-webkit-scrollbar]:hidden

스크롤바 제거 전

 

6. 객체 선언 뒤 as const

 

 


?. 

  useEffect(() => {
    const quotesToUse = category
      ? quotes[category]
      : Object.values(quotes).flat();

    const updateQuote = () => {
      setCurrentQuote(quotesToUse[index]);
      setIndex((prevIndex) =>
        prevIndex === quotesToUse.length - 1 ? 0 : prevIndex + 1
      );
    };

    updateQuote();
    const interval = setInterval(updateQuote, 100000);

    return () => clearInterval(interval);
  }, [category, index]);

문제

1. 상태 업데이트 순환

index가 변경될 때마다 useEffect가 재실행됨

재실행 시 updateQuote() 함수가 즉시 호출되어 index를 변경

이로 인해 무한 루프가 발생한 것처럼 나타남(반복을 원했으므로 무한 루프라 할 수 있긴 하다.)

 

2. interval 관리 문제

매 index 변경마다 이전 interval이 제거되고 새로운 interval이 생성됨

이에 따라, 5초를 기다리지 않고 즉시 다음 명언으로 전환됨

 

해결

 

  const getQuotes = useCallback(() => {
    return category ? quotes[category] : Object.values(quotes).flat();
  }, [category]);

  useEffect(() => {
    const quotesToUse = getQuotes();
    let currentIndex = 0;

    const updateQuote = () => {
      setCurrentQuote(quotesToUse[currentIndex]);
      currentIndex = (currentIndex + 1) % quotesToUse.length;
    };

    updateQuote();
    const interval = setInterval(updateQuote, 5000);

    return () => clearInterval(interval);
  }, [category, getQuotes]);

1. 클로저 활용

currentIndex는 클로저 내부에서만 존재

React의 렌더링 사이클과 무관하게 독립적으로 동작

interval 함수가 동일한 currentIndex 변수를 계속 참조 가능

 

클로저를 사용하면 React의 상태관리 시스템 밖에서 인덱스를 관리한다. 클로저는 함수가 선언될 때의 환경을 기억하는 JavaScript의 특성을 활용한다. useEffect 내부에서 선언된 currentndex 변수는 해다 useEffect의 실행 컨텍스트 안에서 살아있게 되는 것이다. 즉, currentIndex는 React의 상태가 아닌 일반 변수이므로, 이 값이 변경되어도 React의 리렌더링을 유발하지 않는다.

 

2. useCallback 메모이제이션

category가 변경될 때만 quotes 배열을 새로 생성

불필요한 재계산 방지

 

3. 안정적인 interval

카테고리가 변경될 때만 interval이 재설정된

5초마다 한 번씩 정확하게 명언이 변경됨