반응형 구성하기 - 블로그 이사 준비 2편

hojoon·2024년 4월 20일
1

1편에 이어서 2편이다. zz(v2)

많은 일이 있었다. 열심히 만들었던 메인페이지를 싹 다 갈아엎고 다시 만들기도 했고 (디자인이 너무 맘에 안들었음) 유명한 사이트들을 많이 보면서 참고도 하고 영감(?)을 얻어보려고 노력했음

1. 골치아픈 사이드 바

블로그를 만드는데 사이드바가 필요한 이유?

우선 내가 만들 웹사이트는 웹 애플리케이션이다.

단순히 정적이고 블로그 글만을 보여주는 곳이 아닌 글도 작성하고 더 나아가 상호작용 가능한 웹 애플리케이션을 만드는 것이 목표이기 때문이다. 한두 달짜리 포트폴리오만을 위한(물론 포폴목적도 있음) 내가 개인 공부를 위한 다양한 애플리케이션들을 만들어보기 위함이다.(채팅, 대쉬보드, 이메일, 페어프로그래밍 등등 기획만 김칫국임)

사이드바 진짜 골치 아프다.

내가 필요한 기능들

  • 사이드바의 상태는 확장,축소,안보임 이다.
  • 상태에 따른 메인컨텐츠 영역 또한 반응해서 레이아웃이 달라져야 한다.
  • 축소일때는 아이콘들만 보여야 함.

써놓고 보니 별거아닌거 같은데 0에서 시작하려니 디자인 하기가 너무 어려웠고 효율적인 퍼블리싱을 많이 고민했다. 근데 진짜 많이 힘들었음.

확장

축소

안보임

이렇다.

골치아프게 했던 것들

1 .사이드바에서 down icon이 컨텐츠 영역 오른쪽 끝에 일정하게 붙어야 한다.

CSS가 진짜 생각보다 골치 아픔. 처음엔 가상 선택자로 after속성을 활용했으나 이벤트를 붙이기가 어려워서 margin-left : auto를 줘서 해결했다. CSS는 진짜 정답이 없고 활용을 어떻게 하는지가 중요한데 많은 경험이 필요한거 같다.

2. 사이드바가 축소 상태이면 아이콘만 보여야 하고, 아이콘에 위치 또한 어느정도 중앙정렬이어야 하며 레이아웃 시프트가 없어야 어색해보이지 않음.

그니까 사이드바가 축소되면서 아이콘들이 왼쪽으로, 오른쪽으로 밀리면 엄청 버벅대는것처럼 보임. 그래서 왼쪽에 고정된 여백값(margin,padding)을 주고 오른쪽만 줄어들면서 넘치는 컨텐츠들은 안보이게 해야 한다. 그과정에서 어차피 사이드바는 고정된 가로 값을 가지고 있기 때문에 pixel단위로 수정할 수 있었음.
축소된 사이드바의 상태는 overflow:hidden과 max-width값을 주면 된다.

3. 여러 아이콘중 하나만 클릭해도 모든 아이콘이 다 깜빡거림.

  • lucide-icons 라이브러리가 문제임. 이거 쓰지마세요 여러분들

이걸 내가 썼던 이유가 뭐나면 우선 공식문서에 소개된 내용을 보자면 제네릭한 아이콘 컴포넌트를 쉽게 만들어서 name속성으로 여러 아이콘들을 재사용가능하게 사용할 수 있다.(컬러,사이즈 등과 같은 다양한 속성도 조절 가능)

공식문서

import dynamic from 'next/dynamic'
import { LucideProps } from 'lucide-react';
import dynamicIconImports from 'lucide-react/dynamicIconImports';

interface IconProps extends LucideProps {
  name: keyof typeof dynamicIconImports;
}

const Icon = ({ name, ...props }: IconProps) => {
  const LucideIcon = dynamic(dynamicIconImports[name])

  return <LucideIcon {...props} />;
};

export default Icon;

문제 상황

NEXT.js의 Dynamic import를 통해서 성능적으로도 최적화를 시도할 수 있다. 근데 다만 상호작용이 없는 정적인 컴포넌트(서버 컴포넌트)에서는 유용할 수 있다. 근데 상호작용이 필요한 사이드바, 네비게이션 바에서는 리렌더링이 발생하고 그러면 깜빡임이 생기기 때문에 매우 이상함.!

그래서 구글링을 해봤다. 나랑 같은 문제가 있는 사람은 없는지, 넥스트 다이나믹 임포트가 문제인건지. 또 공식문서도 다시 잘 봐봄

  • 나랑 같은 문제가 있는 사람들 이미 있었음. 그래서 다들 갈아탄다고 하시더라
  • 다이나믹 임포트는 문제가 없음
  • 공식문서도 보면 클라이언트에서 사용하더라도 정적인 콘텐츠에 적용되는 것을 기대하고 있는거 같음

첫번째 해결 방법

원인이 뭘까

1.아이콘 컴포넌트를 다시 보자

  • 어차피 가져오는 아이콘들은 똑같은데 매번 이벤트가 발생할 때마다 다시 가져오고 리렌더링이 발생하니까 깜빡임이 생긴다.
  • 그러면 React.memo를 활용해서 리렌더링을 막을 수 있겠다고 생각했음
import dynamic from 'next/dynamic';
import { memo } from 'react';
import { LucideProps } from 'lucide-react';
import dynamicIconImports from 'lucide-react/dynamicIconImports';

interface IconProps extends LucideProps {
  name: keyof typeof dynamicIconImports;
}

const Icon = memo(({ name, ...props }: IconProps) => {
  const LucideIcon = dynamic(dynamicIconImports[name]);

  return <LucideIcon {...props} />;
});

export default Icon;

오 잘먹힘. 안깜빡거려서 속이 시원했음

다른 문제가 터짐ㅋㅋ

npm run dev를 해서 dev서버를 키면 hmr이라고 개발자가 바꾼 코드가 막 실시간으로 반영이 되어야 하는데 memo를 쓰니까 import해오는 모듈을 찾아서 다시 새로 가져와야 함. 그러니까 0.2초 걸리던게 11초, 12초,22초 이렇게 걸린다. 얼마나 답답함. console.log 하나 찍는데도 11초 걸리니까 진짜 개발을 할 수가 없음.

해결 방법

  • lucide 쓰지 말고 내가 제네릭한 icon컴포넌트를 만들자.
    일단 폰트어썸이나 여러 사이트에서 무료로 제공되는 svg파일들을 복사할 수 있다.그리고 각각 컴포넌트를 만들어주면 됨.

task icon

export default function Task() {
  return (
    <svg
      className="w-[20px] h-[20px] text-slate-700 dark:text-white"
      aria-hidden="true"
      xmlns="http://www.w3.org/2000/svg"
      fill="none"
      viewBox="0 0 24 24"
    >
      <path
        stroke="currentColor"
        strokeLinecap="round"
        strokeWidth="2"
        d="M9 8h10M9 12h10M9 16h10M4.99 8H5m-.02 4h.01m0 4H5"
      />
    </svg>
  );
}

이렇게 여러 아이콘 컴포넌트들을 만들어주고 아이콘 컴포넌트들을 매핑해준다.

import PairProgramming from './assets/PairProgramming';
import Payment from './assets/Payment';
import Task from './assets/Task';
import Chat from './assets/Chat';
import Posts from './assets/Posts';
import Dashboard from './assets/Dashboard';
import Email from './assets/Email';
import User from './assets/User';

export const iconMap = {
  home: Posts,
  dashboard: Dashboard,
  chat: Chat,
  email: Email,
  user: User,
  task: Task,
  pairProgramming: PairProgramming,
  payment: Payment,
};

export type IconType = keyof typeof iconMap;

제네릭 아이콘 컴포넌트

import { IconType, iconMap } from './IconMap';

export type Props = {
  name: IconType;
  size?: number;
};

const Icon = ({ name }: Props) => {
  const IconSVGComponent = iconMap[name];

  return <IconSVGComponent />;
};

export default Icon;

이렇게 만들면 사용하는 곳에서는

 <SidebarItem name="home" title="Posts" />
 <Icon name={name} size={20} />

이전과 동일하게 name만 프롭스로 주면 된다.
이랬더니 다시 0.2초로 돌아옴 속이 시원해짐 ㅋㅋ
사이드바는 이렇게 마무리 지었다.

2. 답이 없는 포스트 카드

  • 아 일단 사진부터 봐야 내가 원하던 동작이 뭔지, 뭘 고민한건지 이해가 쉽다.

내가 포스트카드 ui에 기대하는 것들

이미지에 보이는 것처럼, 또 이전에 내가 설명했던 것을 참고하면 사이드바의 상태는 세가지이다. 확장, 축소, 안보임인데 이에 따른 오른쪽 메인컨텐츠 영역이 바뀐다.

  • 카드 ui의 가로, 세로 값이 고정적일 수 없다.
  • 컨텐츠 영역 전체 가로 값이 변하게 되는데 그러면 그리드 컬럼수도 달라진다. -> 개별 아이템 가로 값(포스트 카드 ui)도 달라지게 될 거임 -> 그러나 가로 세로는 고정적이고 일관된 비율을 유지해야 어색하지 않음
  • 그럼 이미지, 카테고리, 제목, 서브타이틀(description), 포스팅 날짜, 작성자 프로필 이미지, 작성자 이름, 북마크, 좋아요 아이콘 등등 달라지는 가로 값이랑 세로값마다 어떻게 유지할 껀데??

특히 서브타이틀(description)영역은 한 줄, 두 줄, 세 줄, 네 줄 작성자마다 게시글마다 다를 텐데 고정된 높이를 어떻게 가질껀데??

  • 그니까 단순 고정된 높이 뿐만이 아니라 변하는 카드 ui에서 일관된 비율로 고정된 높이를 가져야 한다. 무슨 말이냐면 카드 ui의 가로, 세로가 270, 300일때는 서브타이틀 높이가 50%를 가지면서-> 높이 값은 150정도여야 하고 메인컨텐츠 영역의 전체 가로가 또 달라지거나 그리드 컬럼수가 바뀌면서 카드 ui에 가로가 450,500이 되었을 때는 일관된 비율-> 50프로로 세로가 250이 되어야 한다. 또 넘치는 텍스트는 알아서 숨겨져야 함.

해결 방법

1. margin, padding 퍼센트 값 이해하기

  • 이미지 영역 또한 일관된 비율을 유지해야 한다. 카드 ui에서 padding bottom 52%를 주니까 내가 원하는 이미지 영역의 높이 값을 가지게 되었고 카드 ui의 가로,세로가 변하더라도 일관된 비율을 유지할 수 가 있었음

2. aspect-ratio

  • 그리드 컬럼 수, 메인 컨텐츠 영역의 전체 가로에 따라 카드 ui는 엄청 가변적인 크기 값을 가지게 됨. 하지만 일관된 비율을 유지하는 것이 목표였는데 구글링을 잘하다가 'aspect-ratio'라는 속성 하나로 해결하게 되었음. 비율을 명시하면 가로 세로를 그에 맞는 비율을 가지게 하는데 내가 원하는 모든 시나리오에 대응이 가능했다.

3. flex 이해하기

아니 나 flex 잘 씀. 근데 이건 잘 몰랐음

  • flex:1 뭔지 제대로 아는 사람??
    나는 여태 flex를 가로 세로 방향을 바꾸는데 정도로만 잘 썼다. 그리고 바꾸면 뭐 align-item:center이거 맞나 이거랑 justfy 어쩌구 저쩌구 들로 수평,수직 정렬 하는데 활용했고 또 between, around? 요런거로 레이아웃 잡았다. 이정도만 해도 잘 활용한다고 생각했는데 사실 진짜 유용한건 아래의 MDN 첫 문장이 아닐까 생각한다.

flex CSS 속성은 하나의 플렉스 아이템이 자신의 컨테이너가 차지하는 공간에 맞추기 위해 크기를 키우거나 줄이는 방법을 설정하는 속성입니다. flex는 flex-grow, flex-shrink, flex-basis의 단축 속성입니다.

결론적으로 부모 컨테이너에서 남는 공간을 어떻게 차지할 것인지, 아니면 내가 지정한 DOM요소가 얼마나 공간을 차지하고 남는 공간을 다른애한테 줘버릴 것인지 등등 이런걸 효과적으로 제어할 수 있음. 여튼 잘 몰랐던 flex를 활용해서 문제를 해결할 수 있었음.

3.뷰포트 말고 특정 레이아웃 요소에는 미디어쿼리 어떻게 하는데요?

문제가 뭐냐면

그니까 무슨 말이냐면 내가 위에도 언급했지만 사이드바 상태에 따른 메인 컨텐츠 레이아웃 영역의 가로가 매우 가변적임. 닫았다, 열었다, 없어졌다 할 때마다 계속 바뀌는데?? 브라우저 뷰포트로 미디어 쿼리를 잡으면 대응이 안됨. 사이드바 상태가 바껴서 메인컨텐츠 레이아웃 영역이 바뀌더라도 뷰포트 크기가 바뀌는건 아니기 때문.

근데 내가 알고 있는건 미디어쿼리 밖에 없는데요?? 이런거 하라고 미디어쿼리 만든거 아닌가요

  • 진짜 고민을 많이 했다. resize 이벤트를 잡아서 제어하는 방법을 생각하기도 했고 비스무리한 해결 방법들을 떠올렸지만 배보다 배꼽이 큰 느낌이었고 뭐가 좋은걸까 고민을 많이했다.

해결 방법

  • 바로 해결방법이 나와서 간단해보이지만 폭풍 검색하고 진짜 고민 많이함. 헬스하면서도 계속 고민함. 스쿼트하다가 깔릴뻔햇음(ㅋ)

resize observer

intersection observer와 같은 관찰자 api라고 보면 되는데, 이걸 알게되어서 활용했다.

1. 사용법은 intersection observer와 거의 비슷하다.

2. 특정 요소를 지정하고 관찰?시키면 된다.

3. 콜백함수로 내가 하려는걸 하게 하면됨.

4. 리액트에서는 그럼 ref와 useEffect, useState를 활용하면 되겠다.

커스텀 훅 만들어보자

useResizeObserver.ts

import { RefObject, useLayoutEffect, useReducer } from 'react';

interface Options<T extends HTMLElement = HTMLElement> {
  ref: RefObject<T>;
}

interface ResponsiveClassName {
  sm: string;
  md: string;
  lg: string;
  xl: string;
  '2xl': string;
}

interface Action {
  type: keyof typeof responsiveClassNames;
  payload: (typeof responsiveClassNames)[keyof typeof responsiveClassNames];
}

interface State {
  className: string;
}

const responsiveClassNames: ResponsiveClassName = {
  '2xl': 'grid-cols-5 gap-6',
  //1536
  xl: 'grid-cols-4 gap-[23px]',
  // 1280
  lg: 'grid-cols-3 gap-4',
  //1024
  md: 'grid-cols-2 gap-6',
  //786
  sm: 'grid-cols-1 gap-6',
  //640
};

function getContainerWidth_returnClassName(width: number): {
  type: keyof ResponsiveClassName;
  payload: (typeof responsiveClassNames)[keyof typeof responsiveClassNames];
} {
  if (width >= 1620) {
    return { type: '2xl', payload: responsiveClassNames['2xl'] };
  } else if (width >= 1240) {
    return { type: 'xl', payload: responsiveClassNames['xl'] };
  } else if (width >= 1024) {
    return { type: 'lg', payload: responsiveClassNames['lg'] };
  } else if (width >= 730) {
    return { type: 'md', payload: responsiveClassNames['md'] };
  } else {
    return { type: 'sm', payload: responsiveClassNames['sm'] };
  }
}

const reducer: React.Reducer<State, Action> = (state, action) => {
  if (!action) {
    throw new Error('디스패치 함수에 액션이 정의되지 않았습니다??');
  }
  const { className } = state;
  const { type, payload } = action;

  switch (type) {
    case '2xl':
      return { className: payload };
    case 'xl':
      return { className: payload };
    case 'lg':
      return { className: payload };
    case 'md':
      return { className: payload };
    case 'sm':
      return { className: payload };
    default:
      return { className };
  }
};

export function useResizeObserver({ ref }: Options) {
  const [state, dispatch] = useReducer(reducer, { className: '' });

  useLayoutEffect(() => {
    if (!ref.current) return;
    if (typeof window === 'undefined' || !('ResizeObserver' in window)) return;

    const observer = new ResizeObserver(([entry]) => {
      const { type, payload } = getContainerWidth_returnClassName(
        entry.borderBoxSize[0].inlineSize,
      );

      if (state.className !== payload) {
        dispatch({ payload, type });
      }
    });
    observer.observe(ref.current);

    return () => {
      observer.disconnect();
    };
  }, [state.className]);
  return state;
}

간단한 코드 설명

  • 타입스크립트 부분은 건너 뛰겠음. 중요한건 아닌듯?

useState보단 useReducer

컨텐츠 영역의 레이아웃 크기가 각각의 케이스별로 있다. sm,md,lg,xl,2xl 등등 그렇기 때문에 useReducer가 useState보다 상태를 관리하는데 있어서 더 효율적이고 가독성도 좋아짐.

useEffect보단 useLayoutEffect

CSS, 즉 스타일링을 주입하기 때문에 useEffect같은 경우에는 브라우저 렌더링 동작 순서를 생각해보았을 때 페인트 과정이 끝나고 나서 JS를 실행한다. 그렇지만 useLayoutEffect는 그보다 좀 더 빠른 단계에서 페인트를 멈춰놓고 JS를 실행하는데 이 경우엔 내가 스타일링을 직접적으로 제어하기 때문에 useLayoutEffect가 맞다고 생각했음.

간단하게 resize observer 인스턴들을 보자면

인스턴스 속성

  • ResizeObserverEntry.borderBoxSize 읽기 전용
    콜백이 실행될 때 관찰된 요소의 새 테두리 상자 크기를 포함하는 객체 배열입니다.

  • ResizeObserverEntry.contentBoxSize 읽기 전용
    콜백이 실행될 때 관찰된 요소의 새 콘텐츠 상자 크기를 포함하는 객체 배열입니다.

  • ResizeObserverEntry.devicePixelContentBoxSize 읽기 전용
    콜백이 실행될 때 관찰된 요소의 장치 픽셀 단위로 새 콘텐츠 상자 크기를 포함하는 객체 배열입니다.

  • ResizeObserverEntry.contentRect 읽기 전용
    DOMRectReadOnly콜백이 실행될 때 관찰된 요소의 새 크기를 포함하는 객체입니다 . 이는 이제 이전 버전과의 호환성을 위해서만 사양에 유지되는 레거시 속성입니다.

  • ResizeObserverEntry.target 읽기 전용
    관찰 중인 Element또는 에 대한 참조입니다 .SVGElement

즉, margin영역을 포함하는지, padding까지만 볼 건지, border-box를 기준으로 볼 것인지 등등 정할 수 있다.

비효율적인 리렌더링 방지하기

  • 테일윈드에 클래스네임들을 미리 오브젝트에 키와 밸류로 선언해두고 문자열로 삽입하고 있다. 즉 dispatch또한 이전 클래스네임과 같다면 실행되지 않도록 비교하도록 했고 의존성 배열안에도 className을 잘 넣어놨기 때문에 의도한대로 동작은 하는 중 이다.(나중에 문제 생기면 바꿔야 할 수도)

추가적인 코드 설명

  • 나름 대로 코드를 효율적으로 짜보려고 노력했음
    dispatch에 보내줄 type과 payload를 리턴해줄 함수를 하나 만듬
function getContainerWidth_returnClassName(width: number): {
  type: keyof ResponsiveClassName;
  payload: (typeof responsiveClassNames)[keyof typeof responsiveClassNames];
} {
  if (width >= 1620) {
    return { type: '2xl', payload: responsiveClassNames['2xl'] };
  } else if (width >= 1240) {
    return { type: 'xl', payload: responsiveClassNames['xl'] };
  } else if (width >= 1024) {
    return { type: 'lg', payload: responsiveClassNames['lg'] };
  } else if (width >= 730) {
    return { type: 'md', payload: responsiveClassNames['md'] };
  } else {
    return { type: 'sm', payload: responsiveClassNames['sm'] };
  }
}

디스패치에서

const { type, payload } = getContainerWidth_returnClassName(
        entry.borderBoxSize[0].inlineSize,
      );

      if (state.className !== payload) {
        dispatch({ payload, type });
      }

해주면 된다.

실제 커스텀훅을 사용하는 컴포넌트

export default function PostFeed() {
  const ref = useRef(null);
  const { className } = useResizeObserver({ ref });

  return (
    <div className={clsx(`grid py-5 px-6 ${className}`)} ref={ref}>
      {MOCK_DATA.map((post, idx) => (
        <PostCard
          key={idx}
          title={post.title}
          subTitle={post.subTitle}
          userName={post.userName}
          date={post.date}
        />
      ))}
    </div>
  );
}

이렇게 사용해주면 된다.

4. 상태관리 하는 법.

리액트에서는 전역 상태와 지역 상태 두 가지로 구분할 수 있다.

지역 상태

  • useState와 같은 훅을 사용한 컴포넌트내에서 상태 관리하는 것,
  • 상태를 전달하려면 props로 내려주거나 끌어올리기를 해야 하고 복잡한 컴포넌트 관계가 생긴다면 상태를 관리하기 어려울 수 있다.(추적이 힘들거나, 렌더링 최적화 등등)

전역 상태

  • redux, recoil, zustand, valtio, jotai 등등 상태 관리 라이브러리를 사용하는 것.
  • 그냥 쉽게 말하면 자식 컴포넌트 어디서든 상태를 구독할 수 있는 것.

내가 이번에 기록할 것은 꼭 전역상태관리가 좋은 것은 아니다.

코드의 복잡성, 효율성, 간단한 상태 제어 등등 다 떠나서 전역 상태 관리 라이브러리는 리액트의 지역 상태관리를 다 대체할 수 있다. useState,useReducer를 써도 되지만 전역 상태 관리로 스토어에서도 가져다 쓸 수 있다. 이건 확실하다. Context API는 애매하지만 이것도 어찌되었든 다 대체 가능함.

꼭 전역 상태 관리가 필요한 것만 전역 상태 관리를 하고 지역 상태 관리를 할 수 있는 것을 하는 이유는 뭘까??에 대해서 고민했던 적이 있다. 그 당시에는 뭐 가독성? 명시적인 코드? 배보다 배꼽이 더 큰거? 이런거 아닐까? 왜냐면 전역 상태관리도 렌더링 최적화를 해주는 라이브러리가 많기 때문에 전역 상태 관리가 지역 상태 관리를 다 대체할 수 있지만 굳이 안쓰는건 이런 이유때문이다고 생각했음

근데 이번에 전역 상태, 지역 상태를 관리하는 좋은 예시를 만나서 글을 적어보고자 한다.

전역으로 상태를 관리해야 하는 경우

문제 상황

  • 사이드바에서 확장 상태를 제어할 때 자식 요소인 카테고리, 아이템에게 expand에 대한 상태를 props로 내려줘야 했고 거기서 다시 사이드바가 축소 상태이면 hover했을 때 툴팁이 나오게 했는데 툴팁 컴포넌트까지 2단계를 거쳐서 expand 상태를 내려줘야 했다.
  • 더군다나 툴팁 컴포넌트는 기본적으로 expand가 확장일때만 렌더링 되어야 했기 때문에 show라는 상태 또한 관리해야 했음.

해결 방법

  • zustand 전역 상태 관리 라이브러리

    쉽다. 전역으로 상태를 관리하게 되고 props로 사이드바에서 아이템, 카테고리, 툴팁 컴포넌트에 내려줄 필요 없이 하위 컴포넌트에서 상태를 읽어오기만 하면 된다.

지역으로 상태를 관리해야 하는 경우

이건 진짜 경험하기 힘든 좋은 케이스였다고 생각함

문제 상황

  • zustand를 사용하면서 툴팁에서 관리하는 show도 전역으로 상태를 관리하려고 했음.

뭐가 문제였냐면

전역으로 관리하는 상태는 어쨌든 하나의 상태다.
음.. 설명이 매우 어려울 거 같은데 이미지와 코드를 첨부하면서 설명해보겠습니다.

사이드바에 호버했을 때 툴팁이 나타나는 상황

  • 이렇게 pair Programming이 뜨게 되는게 내가 말한 상황이다.

코드를 보자면

SidebarItem 컴포넌트


export default function SidebarItem({ name, title }: SidebarItemProps) {
  const expand = useSidebarStore((state) => state.expand);
  const [showToolTip, setShowTooltip] = useState<boolean>(false);

  const handleTooltip = () => {
    setShowTooltip((show) => !show);
  };

 {!expand ? <HoverTooltip title={title} show={showToolTip} /> : null}
}

HoverTooltip 컴포넌트

export default function HoverTooltip({ title, show }: Props) {
  const expand = useSidebarStore((state) => state.expand);

  return (
    <div
      className={clsx(
        `${
          !expand && show ? 'flex' : 'hidden'
        } items-center justify-center bg-slate-200 rounded-md absolute top-[50%] left-[100%] -translate-y-[50%] z-[9999] p-[6px] tracking-wide dark:bg-postcard/90`,
      )}
    >
      <p className="text-xs font-light dark:text-primary">{title}</p>
    </div>
  );
}

이렇게 상태를 관리하게 되는데 코드상에서는 show가 지역 상태로 관리하지만 원래는 zustand로 관리했었다. 그럼 뭐가 다르고 뭐가 문제여서 내가 useState로 바꿨을까??

리액트의 reconciliation

리액트 딥 다이브 스터디하면서 공부했던 내용이 갑자기 스쳐지나감.아래는 내가 공식문서 보면서 정리했던 내용인데

결론

state는 두 개의 개별 상태이고 각 트리에서 고유한 위치에 렌더링된다. 즉, 각 컴포넌트는 완전히 분리된 state이다.

근데 전역 상태 관리를 하면?

분리된 state가 아니라 하나의 state를 가지기 때문에 내가 마우스를 호버하게 된다면 툴팁 컴포넌트가 모두 show 상태를 true로 구독하고 있어서 모두 툴팁 컴포넌트가 나타나게 된다 .

그래서 독립적이고 각각의 개별 상태를 가지려면 지역 상태 관리를 해야 한다.!

좋은 싸움 이었다.!

끝!

이사준비? 아직 멀었다. 언제 다 끝낼지 모르겠지만 열심히 해보자 3편 포스팅도 꼭 하겠다는 결심을 하면서.... 안녕히 계세요.!

profile
프론트? 백? 초보 개발자의 기록 공간

2개의 댓글

comment-user-thumbnail
2024년 4월 29일

호준님이 하고픈 말이 이거였었군요!! 가볍게 읽으려다가 정독 했습니다 & 많이 배우고 갑니다유 ㅎㅎㅎ 그나저나 포스트 진짜 잘쓰시네여!! 근데 이걸 이제 말로만 잘 옮기시면.... XD

1개의 답글