-
Notifications
You must be signed in to change notification settings - Fork 1
특정 컴포넌트 위치에서 헤더 스타일 다르게 적용하는 방법! (”light” | “dark”)
<Header type="dark" />
기존에 Header 컴포넌트에서는 type을 Props로 따로 받아서 헤더 스타일을 따로 받아서 적용하도록 구현되어 있었다. 아예 type을 변경하는 부분을 ContextAPI를 사용하여 따로 훅으로 빼서 관리하기로 했다.
우선 총 3개의 랜딩 페이지마다 하나의 공통된 훅을 사용하기 위해 각 섹션에 해당하는 상수 객체와 타입 정의를 통해 섹션 키를 관리하도록 했다.
export const MAIN_SECTIONS = {
HEADLINE: "HEADLINE",
LOTTERY: "LOTTERY",
RUSH: "RUSH",
LEARN_MORE: "LEARN_MORE",
} as const;
export const LOTTERY_SECTIONS = {
HEADLINE: "HEADLINE",
INTRO: "INTRO",
HEADLAMP: "HEADLAMP",
PIXEL_DESIGN: "PIXEL_DESIGN",
WHEEL_DESIGN: "WHEEL_DESIGN",
CUSTOM_DESIGN: "CUSTOM_DESIGN",
NEW_COLOR: "NEW_COLOR",
SMILE_BADGE: "SMILE_BADGE",
SHORT_CUT: "SHORT_CUT",
} as const;
export const RUSH_SECTIONS = {
BALANCE_GAME: "BALANCE_GAME",
INTRO: "INTRO",
FAQ: "FAQ",
ELECTRIC_REASON: "ELECTRIC_REASON",
ELECTRIC_ADVANTAGE: "ELECTRIC_ADVANTAGE",
REASON_FIRST: "REASON_FIRST",
CASPER_FAR: "CASPER_FAR",
CASPER_FAST: "CASPER_FAST",
CASPER_COMFORTABLE: "CASPER_COMFORTABLE",
REASON_SECOND: "REASON_SECOND",
CASPER_WIDE: "CASPER_WIDE",
CASPER_CHARGE: "CASPER_CHARGE",
CASPER_SMART_KEY: "CASPER_SMART_KEY",
} as const;
그리고 이 상수들을 사용하여 타입을 다음과 같이 정의해줬는데, 각각의 페이지에 대한 타입과, 이들을 유니온 타입으로 선언하여 하나의 섹션 키로 통합하여 타입을 관리하도록 해주었다.
import {
LOTTERY_SECTIONS,
MAIN_SECTIONS,
RUSH_SECTIONS,
} from "@/constants/PageSections/sections.ts";
type MainSectionKey = keyof typeof MAIN_SECTIONS;
type LotterySectionKey = keyof typeof LOTTERY_SECTIONS;
type RushSectionKey = keyof typeof RUSH_SECTIONS;
export type SectionKey = MainSectionKey | LotterySectionKey | RushSectionKey;
export interface SectionKeyProps {
id: SectionKey;
}
헤더 타입의 경우에도 아래와 같이 “light”와 “dark”로 나누어서 정의하고, ScrollHeaderStyleContext 사용에 필요한 타입을 정의해주었다.
import { SectionKey } from "@/types/sections.ts";
export type HeaderType = "light" | "dark";
export interface ScrollHeaderStyleType {
activeSection: SectionKey; // 현재 활성화된 섹션 키
setActiveSection: (section: SectionKey) => void; // 활성될 섹션 설정하는 함수
headerType: HeaderType; // 현재 헤더 타입
setHeaderType: (type: HeaderType) => void; // 헤더 타입 설정 함수
}
그리고 useScrollHeaderStyleContext 라는 훅을 생성하였다. 본 훅은 앞서 말했다시피 각 섹션마다 헤더 타입을 다르게 설정해주기 위한 훅이다.
import { useContext } from "react";
import { ScrollHeaderStyleContext } from "../contexts/scrollHeaderStyleContext.tsx";
export default function useScrollHeaderStyleContext() {
const context = useContext(ScrollHeaderStyleContext);
if (context === null) {
throw new Error(
"scrollHeaderStyleContext must be used within a useScrollHeaderStyleProvider"
);
}
return context;
}
우선 useContext 훅을 사용하여 ScrollHeaderStyleContext의 현재 값을 가져온다. 여기서 만약 ScrollHeaderStyleContext가 null이면, 이 훅이 ScrollHeaderStyleProvider 내부에서 사용되지 않았다는 의미이므로 에러를 던진다. 만약 null이 아닌 경우 context 값을 반환하는데, 이를 통해 컴포넌트에서는 ScrollHeaderStyleContext의 값을 쉽게 접근할 수 있다.
아래 파일에서는 우선 createContext를 사용하여 ScrollHeaderStyleContext를 생성하고, null로 초기화해준다. 아까 정의해준 ScrollHeaderStyleType 타입을 사용하여 생성해주기 때문에 해당 컨텍스트는 ScrollHeaderStyleType 또는 null 타입을 가질 수 있다.
import { ReactNode, createContext, useMemo, useState } from "react";
import { HeaderType, ScrollHeaderStyleType } from "@/types/scrollHeaderStyle.ts";
import { SectionKey } from "@/types/sections.ts";
export const ScrollHeaderStyleContext = createContext<ScrollHeaderStyleType | null>(null);
export const ScrollHeaderStyleProvider = ({ children }: { children: ReactNode }) => {
const [activeSection, setActiveSection] = useState<SectionKey>("HEADLINE");
const [headerType, setHeaderType] = useState<HeaderType>("light");
const value = useMemo(
() => ({
activeSection,
setActiveSection,
headerType,
setHeaderType,
}),
[activeSection, headerType]
);
return (
<ScrollHeaderStyleContext.Provider value={value}>
{children}
</ScrollHeaderStyleContext.Provider>
);
};
그리고 ScrollHeaderStyleProvider를 사용하여 자식 컴포넌트들에게 컨텍스트 값을 제공해줄 수 있는데, 여기서 아까 ScrollHeaderStyleType 타입에서 정의한 activeSection과 headerType 총 2개의 상태들을 useState로 관리한다.
여기서 value 객체에 대하여 메모이제이션 해주기 위해 useMemo를 사용했는데, 이를 통해 activeSection 또는 headerType이 변경되지 않는 한, value 객체가 재생성 되지 않도록 해주었다.
새로운 훅을 하나 더 생성해주었는데, 이 훅은 useHeaderStyleObserver 라는 이름으로 생성해주었다. 훅 이름처럼 IntersectionObserver라는 웹 API를 사용하여 특정 섹션에 도달했을 때를 감지하여 해당 섹션에서 헤더 스타일을 light / dark 모드 중 어느 것을 적용할지 분기처리 해주도록 하는 훅이다.
IntersectionObserver 객체 생성할 때 기본 구조는 다음과 같다.
const observer = new IntersectionObserver(callback, options);
-
callback
: 교차 상태가 변경될 때 실행될 함수 -
options
: 관찰자의 동작을 제어하는 옵션 객체
실제로 각 섹션마다 section 태그가 달려있어서 해당 section에 각 섹션에 대한 id를 부여하고 querySelectorAll를 사용해서 각 섹션에 observe를 설정하여 모두 관찰 대상으로 등록했다.
<Headline id={MAIN_SECTIONS.HEADLINE} />
<Lottery id={MAIN_SECTIONS.LOTTERY} />
<Rush id={MAIN_SECTIONS.RUSH} />
<LearnMore id={MAIN_SECTIONS.LEARN_MORE} />
const sections = document.querySelectorAll("section");
sections.forEach((section) => observer.observe(section));
그리고 dark 모드를 적용할 section들만 따로 Props 배열로 받아서 if 문으로 처리를 통해 헤더 스타일을 지정해주었다.
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
// 항목이 화면에 보이는 경우 실행
if (entry.isIntersecting) {
// 현재 활성 섹션으로 설정
const sectionId = entry.target.id as SectionKey;
setActiveSection(sectionId);
/* 해당 섹션 id가 darkSections 배열에 포함되는 경우
헤더 타입을 "dark"로 설정하고,
그렇지 않으면 "light"로 설정 */
const newHeaderType: HeaderType = config.darkSections.includes(sectionId)
? "dark"
: "light";
setHeaderType(newHeaderType);
}
});
},
{
root: containerRef.current,
threshold: 0.8,
}
);
- 페이지가 렌더링 된 후 스크롤을 최상단으로 올리기
- DOM을 이미지로 저장하기
- Context API에서 불필요한 리렌더링을 줄이는 방법
- 무한 Transition 애니메이션
- Github Action 워크플로우 정의하기
- 무한 Transition 애니메이션 최적화하기
- 무한 Transition 애니메이션 최적화하기 2
- 무한 Transition 애니메이션 최적화하기 3
- fetch timeout 구현하기
- ErrorBoundary 구현하기
- 뒤로가기 confirm 로직 구현하기
- 선착순 밸런스 게임 상태 관리
- Modal 내부 컴포넌트에서 무한 스크롤이 제대로 동작 안되는 문제
- useToggleContents 훅 기본값 설정 및 조건부 사용법
- 폰트 굵기 적용 이슈
- SVG 내부 stroke 속성 값 제어를 위한 SVGIcon Util 함수 및 SVGR 사용 과정
- tailwindCSS의 @apply를 cva로 바꾸기
- 스크롤 내려갈 때 해당 섹션의 요소들 인터렉션 동작
- 공통 컴포넌트 내부에 애니메이션을 넣는 것에 대한 고민
- 특정 컴포넌트 위치에서 헤더 스타일 다르게 적용하는 방법
- 스크롤 거꾸로 올릴 때 IntersectionObserver가 뷰포트 감지 못하는 현상
- 선착순 밸런스 게임 최종 결과 계산에 대한 고민 (08.14)
- 프로그래스바 공통 컴포넌트로 분리
- (08.23 기준) 선착순 서버 시간 연동 문제
- 게임 종료된 상태에서 사용자 게임 참여 여부에 따른 FinalResult 분기 처리
- FinalResult 컴포넌트의 “당신의 선택” 카테고리 설정 이슈
- 게임 접속 시 게임 현재 진행 상태 초기화 및 카운트 다운 설정
- 선착순 밸런스 게임 UX 개선