# Routes

## 라우터란?

> 라우팅을 구현하는 도구
>
> URL과 컴포넌트 간의 매핑을 관리하고, URL 변경에 따라 컴포넌트를 렌더링 시키는 역할을 한다.

## React Router

> React에서 라우팅을 구현하는데 사용되는 라이브러리

### Install

```bash
npm i react-router-dom
```

### Browser Router

> 브라우저에 내장된 히스토리를 탐색 및 제어하기 위해 내부적으로 History API를 사용하여 CSR 라우팅을 구현

* URL과 매핑되는 페이지 컴포넌트를 렌더링
* history 객체를 사용해서 뒤로가기, 앞으로가기, 새로고침등의 동작을 처리

```tsx
declare function BrowserRouter(
  props: BrowserRouterProps
): React.ReactElement;

interface BrowserRouterProps {
  basename?: string;
  children?: React.ReactNode;
  window?: Window;
}
```

{% hint style="info" %}
테스트 환경에서는 window 객체에 접근할 수 없기 때문에, BrowserRouter를 사용하는 경우 테스트가 실패할 수 있다. 따라서 페이지 외부(main.js 등)에서 라우팅 처리를 해야 한다.
{% endhint %}

```tsx
// main.js
root.render(
    <StrictMode>
      <BrowserRouter>
        <App />
      </BrowserRouter>
    </StrictMode>,
  );
};
```

### Memory Router

> Test 환경에서 `BrowserRouter` 대신 `MemoryRouter`를 사용하여 라우팅 환경 구축한다.
>
> 말 그대로 메모리 내에서 라우팅

```tsx
declare function MemoryRouter(
  props: MemoryRouterProps
): React.ReactElement;

interface MemoryRouterProps {
  basename?: string;
  children?: React.ReactNode;
  initialEntries?: InitialEntry[];
  initialIndex?: number;
}
```

* **initialEntries** → 초기 엔트리 목록 설정, 즉 테스트할 페이지의 첫 렌더링 경로 설정
  * 배열인 이유는 브라우저 히스토리 스택과 동일, (뒤록가기, 앞으로가기 테스트를 하기 위함)
* **initialIndex** → 초기 엔트리 목록\[배열] 중 인덱스를 통해 경로를 선택

```tsx
import { MemoryRouter } from 'react-router-dom';

describe('App', () => {
  it('render correctly', () => {
    render(
      <MemoryRouter>
        <Routes>
          <Route path="/" element={<App />} />
          <Route path="/dashboard" element={<DashBoard />} />
        </Routes>
      </MemoryRouter>,
    );

    expect(screen.getByText(/이다잉/)).toBeInTheDocument();
  });
});
```

#### 함수로 추출해서 재사용성을 높혀보자

```tsx
export function withRouter(routes, initialEntries = '/') {
  return (
    <MemoryRouter initialEntries={[initialEntries]}>
      <Routes>
        {routes}
      </Routes>
    </MemoryRouter>
  )
}

describe('App', () => {
  it('render correctly', () => {
    render(withRouter(
    <>
      <Route path="/" element={<App />} />
      <Route path="/dashboard" element={<DashBoard />} />
    </>
    ));

    expect(screen.getByText(/이다잉/)).toBeInTheDocument();
  });
});
```

### Routes

> 여러 경로의 라우팅 규칙을 처리하는 컨테이너
>
> 자식으로 여러 `<Route />` 컴포넌트를 갖는다.

```tsx
interface RoutesProps {
  children?: React.ReactNode;
  location?: Partial<Location> | string;
}

<Routes location>
  <Route />
</Routes>;
```

* **children** → 자식으로 `<Route/>` 컴포넌트만 받을 수 있다.
* **location** → 현재 위치를 명시적으로 제공, 생략시 현재 브라우저 URL 기준으로 처리

```tsx
<Routes>
  <Route path="/" element={<HomePage />} />
  <Route path="/dashboard" element={<DashboardPage />} />
  <Route path="*" element={<NotFound />} />
</Routes>
```

* location이 변경될 때마다 하위 자식들의 경로를 확인하여 가장 일치하는 경로를 찾아 해당 분기를 렌더링한다
* 매칭되는 라우팅 규칙이 없을 때 특정 컴포넌트를 렌더링 할 수 있다. (catch-all 라우팅)
* 중첩하여 라우팅 규칙을 설정할 수 있다.

### Route

> 경로와 매칭되는 컴포넌트를 선언적으로 지정

```tsx
interface RouteObject {
  path?: string;
  index?: boolean;
  children?: React.ReactNode;
  caseSensitive?: boolean;
  id?: string;
  loader?: LoaderFunction;
  action?: ActionFunction;
  element?: React.ReactNode | null;
  Component?: React.ComponentType | null;
  errorElement?: React.ReactNode | null;
  ErrorBoundary?: React.ComponentType | null;
  handle?: RouteObject["handle"];
  shouldRevalidate?: ShouldRevalidateFunction;
  lazy?: LazyRouteFunction<RouteObject>;
}
```

* **path** → 컴포넌트와 연결할 경로 지정, `:`로 시작하는 경로를 `Dynamic Segments`라 한다.
* **index** → 인덱스 경로 여부 지정
* **caseSensitive** → 경로의 대소문자를 일치시킬지 여부 (default false)
* **element** / **Component** → 경로와 연결할 컴포넌트를 지정한다.

{% hint style="info" %}
**Dynamic Segmenets**란?

동적 경로의 매개변수(params)를 나타내는 라우트 경로의 일부, `:` 기호를 사용하여 매개변수를 표현한다.

```tsx
<Route
  path="/teams/:teamId"
  element={<Team />}
/>;

// Team
export default function TeamPage() {
const params = useParams();
  // path: /teams/hotspur 인경우 params.teamId => hospur
  // path: /teams/real 인경우 params.teamId => real
  return <p>{`params: ${params.teamId}`}</p>
}
```

{% endhint %}

{% hint style="info" %}
**Index Route**란?

index 경로이기 때문에 path를 설정하지 않아도 된다.

부모 경로가 일치하지만 자식 경로중 일치하는 것이 없을 때에 렌더링되는 기본 자식 경로이다.
{% endhint %}

### Outlet

> 라우터에 매칭된 하위의 다른 라우트 컴포넌트가 위치할 영역을 지정 (Slot 개념)

일반적으로 레이아웃 컴포넌트에 활용된다.

```tsx
export default function Layout() {

  return (
    <>
      <header>Header</header>
      <main>
        <Outlet />
      </main>
      <footer>Footer</footer>
    </>
  );
};

```

### &#x20;Layout Route

> 라우트 매칭에 참여하지 않는 껍데기용 라우트

```tsx
<Routes>
  <Route element={<PageLayout />}> // Layout
    <Route path="/privacy" element={<Privacy />} />
    <Route path="/tos" element={<Tos />} />
  </Route>
  <Route path="contact-us" element={<Contact />} />
</Routes>

// '/privacy' 경로에서 렌더링된 결과물
<PageLayout>
  <Privacy />
</PageLayout>
```

{% hint style="warning" %}
LayoutRoute Component는 반드시  하위 경로 요소를 렌더링할 \<Outlet> 을 추가 해주어야 한다.

Children을 사용하면 예상대로 동작하지 않을 수 있다.
{% endhint %}
