놀고와서 밤에 부랴부랴 해볼려고 하니까 졸리고 힘드네 오늘은 Header와 Sidebar를 어떻게 동기화 해서 사용할지 좀 정하고 적용해보는 시간 가져보자
조건을 명세해볼까?
1. Header의 Menu 항목과 Sidebar의 Title 요소가 같아야한다. 2. SubMemu와 Sidebar의 section 요소가 같아야한다.
우선 layout.js에서 Header와 Sidebar를 동시에 상태관리 해줄 수는 없어 왜냐면 layout.js 자체가 초기 한번만 렌더링 될 뿐만 아니라 ssr이거든
그래서 생각해볼 수 있는건 전역상태관리를 하는거지
전역상태관리
1. Recoil 2. Zudstand 3. useContext
이 3개를 일단 선정했고 나는 이중에 Recoil을 사용했어 이유는 없어 그냥 셋다 사용하는건 비슷해보이고 Recoil의 selector에서 가공된 상태를 렌더링 할 수 있다는게 맘에 들었음
근데 글을 작성하다가 치명적인 문제가 생겼다.
Recoil이든 뭐든 상태관리를 하려면 CSR환경에서 동작이 되어야함
하지만 지금 내가 하려는 방식은 Header와 Sidebar를 동기화하는거지? 그럼 Header와 Sidebar가 csr로 되어야하는데 sidebar, Header 도 csr이고 main 요소들도 대다수가 csr이면 그냥 layout.js 자체가 csr인것과 별로 다른게 없다고 판단됨
이럴경우 SEO 최적화의 문제나, UX관점에서 느린 네트워크로 동작하는곳에서는 빈화면만 바라보며 기다릴 수 있다는 뜻이지 그냥 이럴경우 facebook이나 instgram 처럼 스켈레톤 ui를 적용하는 수 밖에 없는거 같네
아니 이러면 동적으로 동작하는 사이트는 다 SEO에서 멀어지는거아냐? 모르겠다....
입사하게 되면 물어봐라 내 머리로는 모르겠다 머리부술까 ㅎ.. 1시간 30분정도 고민한듯
그냥 일단 layout.js전체 csr로 잡고 걍 csr환경에서 진행 할게 ~ 어차피 교회사이트인데 뭐
자 그럼 우선 기존에 작성된 Header, navbar, sidebar를 보여줄게
1. Header
import Image from "next/image" ;
import Navbar from "@/components/Navbar" ;
export default function Header () {
return (
< div >
< header className = "bg-white shadow-sm sticky top-0 w-full z-50" >
< div className = "container mx-auto px-4 flex items-center justify-between" >
{ /* 로고 영역 */ }
< div className = "w-auto mt-4 mb-4" >
< a href = "/" >
< Image
src = "/images/header/church_name.png"
alt = "새에덴 교회"
width = { 150 }
height = { 50 }
/>
</ a >
</ div >
{ /* 내비게이션 메뉴 */ }
< Navbar />
{ /* 예배 시간 정보 */ }
< div className = "hidden md:flex items-center text-gray-600 font-bold" >
< span > 주일예배: 오전 11:00 </ span >
</ div >
</ div >
</ header >
</ div >
);
}
2. Navbar
"use client" ;
import { useState } from "react" ;
function NavItem ({ text , href = "#" , children }) {
const [ isHovering , setIsHovering ] = useState ( false );
return (
< div
className = "relative"
onMouseEnter = { () => setIsHovering ( true ) }
onMouseLeave = { () => setIsHovering ( false ) }
>
< a
href = { href }
className = "text-gray-600 px-3 py-2 font-bold hover:text-blue-500 transition-colors"
>
{ text }
</ a >
{ children && (
< div
className = { `absolute left-0 mt-2 w-48 bg-white shadow-lg rounded-md py-2 z-50 transition-all duration-200 ${
isHovering ? "opacity-100 visible" : "opacity-0 invisible"
} ` }
>
{ children }
</ div >
) }
</ div >
);
}
function SubNavItem ({ text , href = "#" }) {
{
/* 서브 드롭다운 메뉴 */
}
return (
< a
href = { href }
className = "block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-blue-500"
>
{ text }
</ a >
);
}
export default function Navbar () {
return (
< nav className = "flex-1 flex justify-center" >
< div className = "hidden md:flex md:space-x-8" >
< NavItem text = "우리교회" >
< SubNavItem text = "교회소개" href = "/about" />
< SubNavItem text = "담임목사" href = "/pastor" />
< SubNavItem text = "섬기는 사람들" href = "/leadership" />
< SubNavItem text = "예배안내" href = "/worshipInfo" />
</ NavItem >
< NavItem text = "예배" >
< SubNavItem text = "주일예배" href = "/sunSermons" />
< SubNavItem text = "수요예배" href = "/wedSermons" />
< SubNavItem text = "새벽예배" href = "/dawnSermons" />
</ NavItem >
< NavItem text = "교회게시판" >
< SubNavItem text = "자유게시판" href = "/freeBoard" />
< SubNavItem text = "선교자소식" href = "/missionaryNews" />
< SubNavItem text = "주일만나" href = "/slunch" />
< SubNavItem text = "기도제목" href = "/prayerTopic" />
< SubNavItem text = "주보" href = "/weeklyNews" />
</ NavItem >
< NavItem text = "교회소식" >
< SubNavItem text = "공지사항" href = "/notices" />
< SubNavItem text = "교회일정" href = "/schedule" />
</ NavItem >
< NavItem text = "목회달력" href = "/calendar" />
< NavItem text = "오시는길" href = "/location" />
</ div >
</ nav >
);
}
3. Sidebar
"use client" ;
import Link from "next/link" ;
import { usePathname } from "next/navigation" ;
export default function Sidebar () {
const pathname = usePathname ();
const hideSidebarPaths = [ "/" , "/login" ];
if ( hideSidebarPaths . includes ( pathname )) {
return null ;
}
return (
< div className = "w-72 mt-18 mr-20 p-4 min-h-screen" >
< h2 className = "text-xl font-bold text-blue-700 mb-6 text-center" >
우리교회
</ h2 >
< div className = "flex flex-col space-y-2" >
< Link
href = "#"
className = "p-3 border border-gray-200 hover:bg-gray-50 transition-colors"
>
교회소개
</ Link >
< Link
href = "#"
className = "p-3 border border-gray-200 hover:bg-gray-50 transition-colors"
>
담임목사
</ Link >
< Link
href = "#"
className = "p-3 border border-gray-200 hover:bg-gray-50 transition-colors"
>
< div className = "border-l-4 border-blue-700 pl-2 font-medium text-blue-800" >
섬기는사람들
</ div >
</ Link >
< Link
href = "#"
className = "p-3 border border-gray-200 hover:bg-gray-50 transition-colors"
>
예배안내
</ Link >
</ div >
</ div >
);
}
자 보면 Header/Navbar 와 Sidebar의 요소들에서 접점이 안보이지? 이제 이걸 Recoil을 통해서 전역으로 상태를 관리해볼거야
우선 전역상태를 관리해주는 Store를 만들어야겠지? 그전에 Rocoil 한번 간단하게 설명할게 React 공부가면 있기는 해
Rocoil에서는 Store 관리를 Atom과 Selector로 관리해
Atom : global state를 생성하게 해줌 Selector : global state를 가공하고 반환함
useRecoilState, useRecoilValue, useSetRecoilState
atom을 사용하는 데에는 3가지 방법이 있다.
첫 번째 useRecoilState 는 useState와 유사한 방법으로 atom을 사용한다.
import { useRecoilState } from "recoil";
import { Category } from "../atoms/atoms";
const [category, setCategory] = useRecoilState(Category);
생성한 Atom을 가져오고,
useRecoilState의 인자로 전달한다.
반환 값은 useState와 동일하게 atom의 현재 값, atom에 대한 수정 함수 이다.
useRecoilValue
import { useRecoilValue } from "recoil";
import { Category } from "../atoms/atoms";
const category = useRecoilValue(Category);
useRecoilValue는 위에서 atom의 현재 값만 반환 하는 메서드이다.
useSetRecoilState
import { useSetRecoilState } from "recoil";
import { Categories } from "../atoms/atoms";
const setCategory = useSetRecoilState(Categories);
useSetRecoilState는 atom의 수정 함수 만 반환하는 메서드이다.
이처럼 Recoil 라이브러리를 사용하면, 여러 global state에 대해 atom을 만들긴 하지만 RecoilRoot 하나의 컴포넌트 만으로도 생성한 global state를 모두 useState와 유사한 익숙한 방법으로 사용 할 수 있다는 장점이 있즤
자 이제 적용가보자
아 망했음 ㅎ.. 계속 알수 없는 에러가나서 Reddit이나 뭐 git 같은 블로그들을 좀 봤거든?
ㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎ
Recoil은 현재 업데이트 되고 있지 않으며 최신리액트에서 지원되지 않을 가능성이 많다고 하네
아 지금까지 뭐한건데
다음글로 돌아올게 잠깐 쉬어야겟어