리액트 라우터의 Outlet 컴포넌트
리액트 라우터의 Outlet 컴포넌트는 라우트를 중첩하여 내비게이션 메뉴나 바닥글처럼 컴포넌트마다 공통으로 사용하는 부분의 코드를 줄여준다. Outlet 컴포넌트는 다른 컴포넌트들이 렌더링되는 위치를 지정해 주는 역할을 한다.
Outlet 컴포넌트를 Layout에 추가한다. 여기서 Outlet 컴포넌트는 RoutesSetup에서 설정한 라우트 경로의 컴포넌트가 여기에 위치하는 역할을 한다.
import {Outlet} from 'react-router-dom'
export default function Layout() {
return <Outlet />
}
그리고 RoutesSetup에 LayOut을 추가해 Outlet 컴포넌트에 다른 컴포넌트가 위치하게 하기 위해 중첩 라우트를 통해 설정한다. 즉 Board, NoMatch는 Layout의 자식으로 설정된다.
리액트에서 부모 컴포넌트는 자식 컴포넌트 안에서 렌더링 될 수 없고, 형제 컴포넌트가 다른 형제 컴포넌트 안에서 렌더링 될 수 없다. 따라서 NoMatch가 Outlet안에서 렌더링되려면 라우터 설정은 부모/자식 관계의 라우팅, 중첩 라우팅 형태로 설정해야 한다.
import {Routes, Route} from 'react-router-dom'
import NoMatch from './NoMatch'
import Board from '../pages/Board'
import Layout from './Layout'
export default function routesSetup() {
return (
<Routes>
<Route path="/" element={<Layout />}> // 중첩라우트
<Route path="/board" element={<Board />} />
<Route path="*" element={<NoMatch />} />
</Route>
</Routes>
)
}
색인 라우트 알아보기
Route 컴포넌트는 index란 이름의 boolean 타입의 속성을 제공하는데 <Route index /> 형태로 사용하는 Route를 색인 라우트(index route)라고 한다.
import {Routes, Route} from 'react-router-dom'
import NoMatch from './NoMatch'
import LandingPage from './LandingPage'
import Board from '../pages/Board'
import Layout from './Layout'
export default function routesSetup() {
return (
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<LandingPage />} />
<Route path="/board" element={<Board />} />
<Route path="*" element={<NoMatch />} />
</Route>
</Routes>
)
}

네비게이션 메뉴와 바닥글 만들기
outlet과 중첩라우트를 통해 네비게이션 메뉴와 바닥글을 공통적으로 사용할 수 있도록 구성해보자. 우선 NavigationBar와 Footer 코드를 추가한다.
// navigationBar
import {Link} from 'react-router-dom'
export default function NavigationBar() {
return (
<div className="flex p-2 navbar bg-base-100">
<Link to="/">Home</Link>
<Link to="/board" className="ml-4">Board</Link>
</div>
)
}
// footer
import * as D from '../../data'
export default function Footer() {
return (
<footer className="p-4 footer footer-center bg-primary text-primary-content">
<div>
<p>Copyright @ 2022 - All right reserved by {D.randomCompanyName()}</p>
</div>
</footer>
)
}
마지막으로 outlet을 설정한 Layout 컴포넌트에 NavigationBar와 Footer를 추가한다.
import {Outlet} from 'react-router-dom'
import NavigationBar from './NavigationBar'
import Footer from './Footer'
export default function Layout() {
return (
<>
<NavigationBar />
<Outlet />
<Footer />
</>
)
}

랜딩 페이지 만들기
사용자가 특정 웹 사이트를 접속 시 처음 보게 되는 페이지를 랜딩 페이지(landing page)라고 부른다. 이 랜딩페이지에 표시할 히어로 영역과 프로모션 영역을 추가해보자.
히어로 영역 만들기
히어로 영역은 웹 사이트를 대표하는 이미지를 히어로(hero)이미지라고 부르는 경향이 있다. 또한 웹 사이트를 대표하는 슬로건과 대표 메뉴로 가는 버튼을 함께 제공하는 영역을 히어로 영역이라고 한다.
import {Link} from 'react-router-dom'
import {Div} from '../../components'
import {Button} from '../../theme/daisyui'
import * as D from '../../data'
export default function Hero() {
return (
<div className="flex items-center p-4">
<Div minWidth="30rem" width="30rem" maxWidth="30rem">
<div className="flex flex-col justify-center p-4 font-bold">
<p className="text-3xl italic text-center line-clamp-5">{D.randomSentence()}</p>
<div className="flex justify-center mt-4 item-center">
<Link to="/board">
<Button className="btn-primary btn-outline">go to Board</Button>
</Link>
</div>
</div>
</Div>
<Div
src={D.randomImage(2000, 1600, 100)}
className="w-full ml-4"
minHeight="20rem"
height="20rem"
/>
</div>
)
}

프로모션 영역 만들기
랜딩 페이지의 마케팅적인 측면을 강하게 하기 위해 고객평가의 구체적인 사례를 보여줄 때가 많기에 화면에 고객평가 데이터(customerCommet.ts) 를 만들고 고객평가 컴포넌트(customerComment.tsx)를 추가하자.
import * as C from './chance'
import * as I from './image'
export type CustomerComment = {
uuid: string
name: string
jobTitle: string
company: string
avatar: string
comment: string
}
export const makeCustomerComment = (
uuid: string,
name: string,
jobTitle: string,
company: string,
avatar: string,
comment: string
): CustomerComment => ({uuid, name, jobTitle, company, avatar, comment})
export const makeRandomCustomerComment = () =>
makeCustomerComment(
C.randomUUID(),
C.randomName(),
C.randomJobTitle(),
C.randomCompanyName(),
I.randomAvatar(),
C.randomParagraphs(1)
)
import type {FC} from 'react'
import type {CustomerComment as CustomerCommentType} from '../../data'
import {Div, Avatar} from '../../components'
export type CustomerCommentProps = {
customerComment: CustomerCommentType
}
const CustomerComment: FC<CustomerCommentProps> = ({customerComment}) => {
const {name, jobTitle, company, avatar, comment} = customerComment
return (
<Div
className="relative p-2 mx-2 mt-8 rounded-lg shadow-lg boarder-2 border-primary"
minWidth="20rem"
width="20rem"
minHeight="20rem"
height="1-rem">
<div className="absolute flex items-center justify-center w-full -top-7">
<Avatar src={avatar} className="border-2 border-primary" />
</div>
<div className="flex flex-col">
<div className="flex flex-col p-4 font-bold">
<p>{name}</p>
<p>{jobTitle}</p>
<p>{company}</p>
</div>
<p className="mt-4">{comment}</p>
</div>
</Div>
)
}
export default CustomerComment
마지막으로 프로모션 영역에 CustomerComment 컴포넌트를 추가한다.
import {useMemo} from 'react'
import CustomerComment from './CustomerComment'
import {Div} from '../../components'
import * as D from '../../data'
export default function Promotion() {
const comments = useMemo(() => D.makeArray(3).map(D.makeRandomCustomerComment), [])
const children = useMemo(
() =>
comments.map(comment => (
<CustomerComment key={comment.uuid} customerComment={comment} />
)),
[comments]
)
return (
<section className="w-full mt-4">
<h2 className="ml-4 text-5xl font-bold">What our customers say:</h2>
<div className="flex justify-between w-full p-4">
<Div
width="15%"
minWidth="15%"
className="flex items-center justify-center text-white bg-primary">
Your message here
</Div>
<div className="flex flex-wrap justify-center p-4 mt-4">{children}</div>
<Div
width="15%"
minWidth="15%"
className="flex items-center justify-center text-white item-center bg-primary">
Your advertizement Here
</Div>
</div>
</section>
)
}

커스텀 Link 컴포넌트 만들기
현재 우리 화면에서 네비게이션의 메뉴는 선택 되었을 때 활성화 된 것처럼 표현되질 않는다. daisyui의 버튼 컴포넌트는 버튼 활성화를 시각적으로 보여주는 btn-active 클래스를 제공하며 이를 통해 Link 컴포넌트를 구현하자.
import type {FC} from 'react'
import type {LinkProps as RRLinkProps} from 'react-router-dom'
import {Link as RRLink} from 'react-router-dom'
export type LinkProps = RRLinkProps & {}
export const Link: FC<LinkProps> = ({className: _classname, to, ...props}) => {
const className = [_classname].join(' ')
return <RRLink {...props} to={to} className={className} />
}
네비게이션바의 기존 Link를 새로 구현한 Link 컴포넌트로 변경한다.
// import {Link} from 'react-router-dom'
import {Link} from '../../components'
export default function NavigationBar() {
return (
<div className="flex p-2 navbar bg-base-100">
<Link to="/">Home</Link>
<Link to="/board" className="ml-4">
Board
</Link>
</div>
)
}
useResolvedPath와 usemeatch 훅 알아보기
위의 Link 컴포넌트 구현으로는 현재 페이지에 밑줄이 생기진 않는다. 추가적으로 useResolvedPath와 useMatch 훅을 사용해 현재 페이지일 때 메뉴명에 밑줄을 그어보자.
import type {FC} from 'react'
import type {LinkProps as RRLinkProps} from 'react-router-dom'
import {useResolvedPath, useMatch} from 'react-router-dom'
import {Link as RRLink} from 'react-router-dom'
export type LinkProps = RRLinkProps & {}
export const Link: FC<LinkProps> = ({className: _classname, to, ...props}) => {
const resolved = useResolvedPath(to)
console.log('resolved', resolved)
const match = useMatch({path: resolved.pathname, end: true})
console.log('match', match)
const className = [_classname, match ? 'btn-active' : ''].join(' ')
return <RRLink {...props} to={to} className={className} />
}


'FrontEnd > React' 카테고리의 다른 글
[React] 프로그래밍으로 몽고DB 사용하기 (1) | 2025.01.03 |
---|---|
[React] 공개 라우트와 비공개 라우트 구현하기 (2) | 2024.12.30 |
[React] 처음 만나는 리액트 라우터 (1) | 2024.12.27 |
[React] 트렐로 따라 만들기 (2) (1) | 2024.12.20 |
[React] 트렐로 따라 만들기 (1) (1) | 2024.12.17 |