Remix 문서 리뷰 (v2.4.0)
튜토리얼
기본 페이지
app/routes/_index.tsx
스타일 시트 가져오기
- LinksFunction 을 이용해서 css파일을 내보내면 스타일이 적용된다.
import type { LinksFunction } from "@remix-run/node";
// existing imports
import appStylesHref from "./app.css";
export const links: LinksFunction = () => [
{ rel: "stylesheet", href: appStylesHref },
];
라우터 연결
routes밑에 해당 파일(contacts.$contactId.tsx)을 만들면http://localhost:3000/contacts/1과 같이 경로와 연결 할 수 있다. ($가 붙으면 변수처럼 동작)contacts.\$contactId_.edit.tsx라는 파일의 뒤 언더바는 앞에서 정한contacts.$contactId.tsx파일의 경로는 중첩되지만 레이아웃 상에서는 중첩되지 않는다는걸 의미한다.Link를 가져와서a태그 대신 쓰도록 변경해준다.<Outlet />은 하위 경로 페이지에 해당하는 컴포넌트를 그려준다.
데이터 가져오기
- 데이터를 가져오려면 두가지 api를 사용해야 한다. (
useLoaderData와loader)
- 데이터를 리턴하는
loader함수를 만들고 내보낸다. - 컴포넌트에서
useLoaderData<typeof loader>()를 이용해 데이터를 가져온다.
Loader 에서 params 사용하기
- 페이지 경로의 contactId 와 $contactId 를 매칭하여 loader의 params 객체를 통해 값을 가져올 수 있다.
export const loader = async ({ params }) => {
const contact = await getContact(params.contactId);
return json({ contact });
};
- loader 함수의 파라미터의 타입은 아래처럼 가져올 수 있다.
import type { LoaderFunctionArgs } from "@remix-run/node";
import invariant from "tiny-invariant"; //사용자의 값에 문제가 있을 경우 예외처리 해준다.
export const loader = async ({
params,
}: LoaderFunctionArgs) => {
invariant(params.contactId, "Missing contactId param");
const contact = await getContact(params.contactId);
if (!contact) {
throw new Response("Not Found", { status: 404 });
}
return json({ contact });
};
Form 과 submit
- 밑의 예제에서 Edit 버튼을 누르면 현재 페이지의 /edit 경로의 path로 이동한다.
- 마치 전통적인 브라우저의 form 태그처럼 동작한다.(Remix가 인터페이스만 흉내냈다)
- 만약 Javascript가 허용안된 페이지에서는 전통적인 FormData가 동작하는 방식대로 실행된다.
- From 컴포넌트에도 onChange 이벤트를 걸수 있다.
<Form action="edit">
<button type="submit">Edit</button>
</Form>
- useActionData를 사용하면 액션에 대한 서버의 응답을 컴포넌트에 전달받을 수 있다.
- action이 생략되면 해당 페이지에서 내보내기한 action 함수가 실행된다.
- 완성후 다른페이지로 리다이랙트 시킴 (리다이렉트는 히스토리에 안남기고 페이지를 이동시킨다)
import type { ActionFunctionArgs } from "@remix-run/node";
import { json, redirect } from "@remix-run/node";
import { getContact, updateContact } from "../data";
export const action = async ({
params,
request,
}: ActionFunctionArgs) => {
invariant(params.contactId, "Missing contactId param");
const formData = await request.formData();
const updates = Object.fromEntries(formData);
await updateContact(params.contactId, updates);
return redirect(`/contacts/${params.contactId}`);
};
NavLink
- Link 대신 사용할수 있으면 현재 url 과 일치하는 to가 있으면 isActive를 반환해준다.
<NavLink
className={({ isActive, isPending }) =>
isActive ? "active" : isPending ? "pending" : ""
}
to={`contacts/${contact.id}`}
>
useNavigation
const navigation = useNavigation();
navigation.state
- 현재 페이지의 로딩 상태를 반환한다.
- idle
- loading
- submitting
- navigation.location 속성은 loading 중일때만 값을 가지고 있다.
useNavigate
- 백버튼 사용 예
const navigate = useNavigate();
return (
<button => navigate(-1)} type="button">
Cancel
</button>
);
url의 쿼리스트링에서 정보 가져오기
- request.url 로 가져와서 아래처럼 직접 파싱한다.
- Form에 메서드가 get일 경우 submit되도 액션은 일어나지 않는다.
export const loader = async ({
request,
}: LoaderFunctionArgs) => {
const url = new URL(request.url);
const q = url.searchParams.get("q");
const contacts = await getContacts(q);
return json({ contacts });
};
useSubmit
- 인수에 Form에 해당하는 DOM 을 넣고 함수를 실행시킬 수 있다.
- submit이 되면 히스토리에 쌓인다
- 두번째 옵션의 객체에 replace 옵션을 주면 히스토리에 쌓이지 않게 할 수 있다.
useFetcher
- useFetcher 를 사용하면 navigation에 영향을 주지않고 loader나 action과 통신할수 있다.
- useFetcher 를 사용하려면 액션과 함께 사용해야 한다.
// existing imports
import {
Form,
useFetcher,
useLoaderData,
} from "@remix-run/react";
// existing imports & exports
// existing code
const Favorite: FunctionComponent<{
contact: Pick<ContactRecord, "favorite">;
}> = ({ contact }) => {
const fetcher = useFetcher();
const favorite = contact.favorite;
return (
<fetcher.Form method="post">
...
</fetcher.Form>
);
};
소개
- 리믹스는 핸들러일뿐이므로 Vercel , Netlify , Architect 등과 같은 모든 Node.js 서버는 물론 Cloudflare Workers 및 Deno Deploy 와 같은 Node.js가 아닌 환경 에서도 실행할 수 있습니다.
- 리믹스는 “뷰” 이자 "컨트롤러"다. 모델 구축은 사용자에게 달려있다.
- 경로에 UI와 모델과 상호작용이 모두 포함된다.
- 라우터 모듈은
loader,action,default의 세 가지를 내보낼 수 있다.- loader 는 서버에서만 실행되며 데이터를 요청한다.
- default 는 기본 내보내기이며 경로가 일치될때 렌더링된다.
컴포넌트는 중첩되는 하위 경로를 렌더링한다.
- action은 서버에서만 실행되며 post를 처리한다.
- 리믹스의 라우터는 하나의 api 경로다.
- 리믹스 템플릿을 사용하면 다양한 환경에서 시작하기 좋은 개발 환경 보일러플레이트를 제공받을수 있다.
- 리믹스 클라이언트는 자바스크립트 없이 동작할 수 있도록 개발되었다(브라우저에 자바스크립트사용을 비활성화 시켜도 기본적인 동작을 한다.)
- 점진적인 향상 기법을 이용해 미리 페이지를 로드하므로 페이지 체감속도가 빠르다.
경로 구성
-
app/routes/의 경로가 기본 컨트롤러의 의 경로다. -
기본 규칙은 듀토리얼에서 학습한 내용이 전부지만 폴더링을 하여 스캐폴딩 하고 싶을경우 아래의 예처럼 하면 된다
app/ ├── routes/ │ ├── _index/ │ │ ├── signup-form.tsx │ │ └── route.tsx │ ├── about/ │ │ ├── header.tsx │ │ └── route.tsx │ ├── concerts/ │ │ ├── favorites-cookie.ts │ │ └── route.tsx │ ├── concerts.$city/ │ │ └── route.tsx │ ├── concerts._index/ │ │ ├── featured.tsx │ │ └── route.tsx │ └── concerts.trending/ │ ├── card.tsx │ ├── route.tsx │ └── sponsored.tsx └── root.tsx -
세그컨트를 괄호로 묶으면 선택적 세그먼트가 된다 (있어도 되고 없어도 되는 세그먼트)
- /categories -> app/routes/($lang).categories.tsx
- /en/categories -> app/routes/($lang).categories.tsx
-
remix.config.js 에 규칙을 정의하면 수동으로 경로를 구성할수 있다.
/** @type {import('@remix-run/dev').AppConfig} */ export default { routes(defineRoutes) { return defineRoutes((route) => { route("/", "home/route.tsx", { index: true }); route("about", "about/route.tsx"); route("concerts", "concerts/layout.tsx", () => { route("", "concerts/home.tsx", { index: true }); route("trending", "concerts/trending.tsx"); route(":city", "concerts/city.tsx"); }); }); }, };
데이트 흐름
- Loader -> Component -> Action
클라이언트빌드
- 클라이언트 빌드에서는 아래의 서버 전용 코드가 제거된다.
- action
- headers
- loader
*.client.tsx나*.server.tsx를 사용하면 특정 코드를 클라이언트나 서버빌드에서 뺄수 있다.
보류중인 UI
- 아래 기능들을 이용하면 보류중인 UI를 고려하여 더 좋은 사용자 경험 페이지를 만들 수 있다.
- useNavigate
- useFetcher
- useParam
- NavLink
상태관리
- Redux, ReactQuery, Apollo 와 같은 상태관리 툴들이 유용하긴 하지만 Remix와 함께 사용하기에는 적합하지 않다.
- 대부분이 Remix에서 클라이언트 데이터를 캐싱해주고 있는 부분과 기능이 중복된다고 한다.
- 대부분의 상태들이 이미 Remix에서 제공하는 기능에 상태에 저장된다.
- Remix에서 제공하는 상태관리 이외에 상태를 관리해야 할때는 로컬저장소나 쿠키를 활용하는걸 추천한다.
- useOutletContext 정도로 충분한것 같다.
*.client.ts
- 해당 이름으로 정의된 파일 내에서 export 하면 클라이언트에서만 쓸수 있습니다.
- useEffect나 이벤트 핸들러 내에서만 접근 가능합니다.
*.server.ts
- 서버 종속성이 있는 파일은 이와같이 정의하면 클라이언트 빌드에서는 제거됩니다.
entry.client.tsx
- Remix는 자체적으로 알아서 클라이언트를 위한 수화처리를 하는데
remix reveal을 사용하면 이 동작을 커스텀 변경 할수 있다. - revial을 하면 entry.client.tsx이 생성되느데 이 파일을 원하는대로 수정하면 된다.
entry.server.tsx
- http 응답을 후킹하여 수정할 수 있습니다.
- revial을 하면 entry.server.tsx이 생성되느데 이 파일을 원하는대로 수정하면 된다.
remix.config.js
- 각종 Remix의 환경설정
root
- 루트에서만 사용해야되는 기능이 있다.
import {
Links,
LiveReload,
Meta,
Outlet,
Scripts,
ScrollRestoration,
} from "@remix-run/react";
action
- DELETE, PATCH, POST 또는 PUT 요청이 일어날 경우
loader보다action이 우선 실행된다. - loader와 action은 똑같은 구성이지만, action은 쓰는데이터에 loader는 데이터를 읽는데 쓰인다.
- Form의 액션값을 생략하면 자기자신의 action을 실행하고, 액션에 path가 정의되어 있으면 해당 경로의 action을 실행한다.
clientAction
- 클라이언트에서만 실행되는 액션을 정의할 수 있다.
- 액션 실행시 클라이언트에서는 다른 추가 기능이 필요할때 사용
clientLoader, HydrateFallback
- clientAction과 마찬가지로 클라이언트에서만 실행되는 로더다.
- 페이지 첫 로드시 하이드레이션 될 때는 clientLoader가 실행되지 않는다.
- (꼭 필요한 경우에는 clientLoader.hydrate 를 통해 설정가능하다)
- clientLoader에서 가져와야 하는 데이터가 있기 때문에 SSR 중에 기본 경로 구성 요소를 렌더링하지 않아야 하는 경우
HydrateFallback구성 요소를 사용할 수 있다.
ErrorBoundary
- 오류 처리를 위한 컴포넌트
useMatchs, handle
- 서브 경로마다 handle을 export 하면 root.ts 에서 useMatchs를 통해 노출되고 있는 라우터를 전부 표시할수 있다.
- breadcrumb 와 같은 ui 구현시 용이하다.
headers
- 각 라우터마다 헤더를 http 헤더에 추가 정보를 포함시킬 수 있다.
import type { HeadersFunction } from "@remix-run/node"; // or cloudflare/deno
export const headers: HeadersFunction = ({
actionHeaders,
errorHeaders,
loaderHeaders,
parentHeaders,
}) => ({
"X-Stretchy-Pants": "its for fun",
"Cache-Control": "max-age=300, s-maxage=3600",
});
links
- root에서 링크 태그로 리소스 삽입
loader
- loader 응답은 new Response 를 만들어서 리턴할수도 있고
import { json, redirect } from "@remix-run/node";를 가져와서 실행할 수 있습니다.
meta
- links 처럼 meta 태그를 내보낼수 있다.
ShouldRevalidate
- React의 shouldUpdate 처럼 특정 페이지에 업데이트에 loader가 반응할 필요가 있는 항목만 필터링 할 수 있다.
- 각 항목의 설명은 필요시 찾아보면 되므로 패스
import type { ShouldRevalidateFunction } from "@remix-run/react";
export const shouldRevalidate: ShouldRevalidateFunction = ({
actionResult,
currentParams,
currentUrl,
defaultShouldRevalidate,
formAction,
formData,
formEncType,
formMethod,
nextParams,
nextUrl,
}) => {
return true;
};
컴포넌트
- 지연되는 Promise를 기다려 줬다 보여주는 기능을 합니다.
import { Await } from "@remix-run/react";
<Suspense fallback={<div>Loading...</div>}>
<Await resolve={somePromise}>
{(resolvedValue) => <p>{resolvedValue}</p>}
</Await>
</Suspense>;
- URL을 변경하거나 브라우저 히스토리 스택을 변경해야할때 사용하는 HTML 래퍼다.
- 브라우저 스택을 조작하지 않아야 할때는
<fetcher.Form>을 사용하면 된다. - 속성
- action - 듀토리얼에서 실습해봤음
- method - 듀토리얼에서 실습해봤음
- encType -
application/x-www-form-urlencodedormultipart/form-data. - navigate -
<fetcher.Form>와 비슷하지만 결과에 신경쓰지 않고 데이터 변경이 필요할수 있으므로 사용된다.(UI와의 결합도를 낮춰줌) - fetcherKey - useFetchers와 함께 사용할때 유용하게 사용할수 있다.
- preventScrollReset - from을 재출할때 스크롤이 상단으로 재설정 되는걸 방지 한다.
- replace - 새 항목을 푸시하는대신 기존 마지막 항목의 상태를 바꾼다.
- reloadDocument - 리얼 브라우저의 From 기능을 사용한다.
a tag처럼 페이지를 이동시키지만 라우팅 기능으로 동작한다.- 속성
- prefetch - none, intent(마우스를 올리거나 포커스될때), render(랜더링될때), viewport(뷰포트에 올라올때)
- relative - 링크의 상대경로 동작을 정의
- reloadDocument - 리얼 브라우저의 a 기능을 사용합니다.
- replace - replace은 새 항목을 푸시하는 대신 기록 스택의 현재 항목을 대체합니다.
- state - 클라우트측 라우팅 상태를 추가
- 라우터에 등록된 모든 링크를 출력
- 개발중이던 파일이 변경되면 자동으로 리로드 시켜준다.
- meta 태그 내보낼때 문서에서 태그 위치 지정
- 태그와 같지만 스타일을 지정하기 위한 기능이 추가되어 있다.
- 하위경로 렌더링
context속성 사용가능useOutletContext와 함께 사용가능
- 이 구성 요소를 사용하면 페이지의 모든 자산을 프리페치하여 해당 페이지를 즉시 탐색할 수 있습니다.
- 스크립트 랜더링
- location 변경시 스크롤을 복원해준다.
훅
useActionData
- 가장 최근 작업한 action 데이터를 사용할수 있게 해준다.
useAsyncError
- Await 컴포넌트와 같이 쓰이며 에러처리를 위해 쓰인다.
useAsyncValue
- Await 컴포넌트와 같이 쓰이며 에러처리를 위해 쓰인다.
useBeforeUnload
- 페이지에서 떠날때 어플리케이션의 상태를 정리해주는건 좋은 습관이다.
useBlocker
- 사용자가 현재 위치에서 벗어나는 것을 방지하고 사용자 지정 UI를 제공
useFetcher
- 서버와 상호작용을 하기 위한 훅
- key 속성은 useFetchers에서 조회될경우 식별할 수 있게 해준다.
- <fetcher.Form> 과 함께 사용 가능
fetcher.submit을 사용할수 있다. useSubmit의 fetcher 버전인듯 하다.fetcher.load특정 경로의 loader에서 데이터를 로드하고 해당 UI에 업데이트 된다.- 읽기 속성들
- state - fetcher의 상태 (요청중인지 로딩중인지 대기중인지)
- data - 작업에서 반환된 데이터가 여기에 저장됨
- formData - 서버에 제출된 폼데이터의 인스턴스가 저장됨
- formAction - 제출된 액션 url
- formMethod - 제출된 폼의 제출방식
useFetchers
- 모든 진행중인 fetcher의 배열을 반환합니다.
useFormAction
- 가장 가까운 라우터 경로를 반환함으로서 action 경로를 쉽게 만들 수 있게 해 준다.
useHref
- 링크 경로로 사용할 현재 위치에 대한 전체 url 을 리턴한다.
useLoaderData
- loader 부터 데이터를 컴포넌트로 가져온다.
useLocation
- 현재 location 객체를 반환한다.
useMatches
- 페이지에서 현재 경로와 일치하는 경로들을 리턴한다. handle과 사용하면 편리하게 사용할수 있다.
useNavigate
- 듀토리얼 포함내용이라서 설명생략.
useNavigation
- 듀토리얼 포함내용.
- pending 상태의 페이지 요청에 진행 상태를 리턴해준다.
useNavagationType
- 현재 위치의 페이지에 도달했을때 탐색 유형을 반환한다.
- (push, replace, pop)
useOutlet
- 하위계층에 대한 요소를 반환한다.
컴포넌트에서 내부적으로 사용되는 훅.
useOutletContext
- 가장 가까운 상위 컨텍스트(
<Outlet context="값" />으로 정의한) 값을 반환한다.
useParams
- 현재 url에서 쿼리스트링 값을 키-값 객체 형태로 리턴
useResolvePath
- 알고자 하는 라우터 경로에 해당하는 path정보를 리턴한다.
useRevalidator
- 페이지의 유효성을 검증한다 (특수한 경우에만 유용하며 일반적인 상황에서는 쓸 필요 없다).
- 일반적인 흐름으로 제공되는 action , loader, form 에 연결된 api에서는 자체적으로 이미 검증을 하고 있으므로 사용할 필요 없다.
useRouterError
- action, loader 사용중에 발생하는 오류를 처리할 수 있다.
useRouteLoaderData
- 특정 라우터에 대한 loader 데이터에 접근할 수 있다.
- fetcher.load와 다른점은 UI와 상호 작용 하는 방식이 틀린것 같다.
- (현재 문서에 자세히 설명이 안되어 있어서, 자세히 동작을 알아보기 위해서는 직접 사용하며 테스트를 할 필요가 있어보인다.)
useSearchParams
- 현재 url에 searchParams와 이를 변경할 수 있는 세터를 반환한다.
useSubmit
- 을 스크립트로 submit 해줄수 있다.
유틸리티
쿠키 createCookie
- loader 및 action에서 사용할수 있다.
- (서버측에서 쿠키를 만들때 사용하고 클라이언트에서는 별도의 api는 없는것 같다.)
// *.server.ts
import { createCookie } from "@remix-run/node"; // or cloudflare/deno
export const userPrefs = createCookie("user-prefs", {
maxAge: 604_800, // one week
});
createRemixStub
- 컴포넌트 단위테스트 용도로 사용한다.
defer
- 데이터 스트리밍과 관련된 기능, json()과 사용법은 비슷하지만 Promise를 컴포넌트로 직접 전송하는 기능이다.
isRouteErrorResponse
- react router 에서 라우터 에러 처리와 동일하다고 한다.
json()
- Response 를 json 형태로 보낼때의 쇼트컷 헬퍼
import { json } from "@remix-run/node"; // or cloudflare/deno
export const loader = async () => {
// So you can write this:
return json({ any: "thing" });
// Instead of this:
return new Response(JSON.stringify({ any: "thing" }), {
headers: {
"Content-Type": "application/json; charset=utf-8",
},
});
};
redirect
- 응답 30x 로 Response를 보내는 쇼트컷
redirectDocument
- redirect의 래퍼.
- remix인 앱에서 동일한 도메인의 remix가 아닌 앱으로 리다이렉트 할때 필요하다.
sessions
-
일련의 방법을 통해 세션처리를 할수 있다- 자세한 사용법은 공식문서 참고.
-
createSessionStorage
- 데이터를 서버에 저장한다.
- api를 사용하면 필요한 경우 데이터 베이스에 세션정보를 저장하고 여러 서버에서 공유할수 있다.
-
createCookieSessionStorage
- 순수 쿠키 기반으로 세션기능을 사용한다. 모든 정보를 쿠키에 저장한다.
-
createMemorySessionStorage
- 서버 메모리에 세션정보를 저장하지만, 개발용도로만 사용하고 프러덕션에는 사용하면 안된다고 한다.
-
createFileSessionStorage
- 세션정보를 파일에 저장한다.
-
createWorkersKVSessionStorage
- Cloudflare Workers KV 세션 지원 서버일 경우 사용한다.
-
createArcTableSessionStorage
- Amazon DynamoDB 지원 세션 지원 서버일 경우 사용한다.
스타일링
css 번들링
npm install @remix-run/css-bundle설치 후 아래와 같이 사용가능 (엄청 신기함)
import { cssBundleHref } from "@remix-run/css-bundle";
import type { LinksFunction } from "@remix-run/node"; // or cloudflare/deno
export const links: LinksFunction = () => [
...(cssBundleHref
? [{ rel: "stylesheet", href: cssBundleHref }]
: []),
// ...
];
일반적인 css 사용법
-
해당 라우터에서만 특정 스타일이 적용되어야 할경우 해당 라우터에 links를 export 하면된다.
- (캐스캐이딩으로 동작한다).
-
스타일 공유하기 첫번째 방법
- (공유 되어야 하는 스타일을 하나의 css파일에 몰아 넣고, root.tsx에 export 시키는 방식)
-
스타일 공유하기 두번째 방법
- (컴포넌트 별로 css를 import 하고 필요한 기본 스타일은 상위에서 Links를 import 에서 사용한다.)
- 중첩관리는 Remix에서 알아서 해주는걸로 판단됨.
-
미디어 쿼리 연결 가능하며 스타일 뿐만 아니라 svg같은 파일도 load가능
css Import
- config에서 설정 후
/** @type {import('@remix-run/dev').AppConfig} */
module.exports = {
serverDependenciesToBundle: [
/^@adobe\/react-spectrum/,
/^@react-spectrum/,
/^@spectrum-icons/,
],
// ...
};
- 아래와 같이 사용 가능
import "./menu-button.css";
export function MenuButton() {
return <button data-menu-button>{/* ... */}</button>;
}
Styled Components 및 Emotion과 같은 CSS-in-JS 라이브러리를 사용
- 사용예도 이미 있고 가능은하지만, 중복 랜더링등 성능 문제가 있어서 Remix에서는 사용하지 않는게 좋다고 함.
css 모듈
- 내장된 css모듈이 있어서 아래와 같이 사용가능함. 다만 컨피그에서 설정이 필요함
import styles from "./styles.module.css";
export const Button = React.forwardRef(
({ children, ...props }, ref) => {
return (
<button
{...props}
ref={ref}
className={styles.root}
/>
);
}
);
Button.displayName = "Button";
Post css
- config에서 설정하면 사용 가능함.
- 먼저 관련된 패키지를 설치해야함
- css 번들링이나 import 를 사용하여 css를 사용할때 작동한다.
Tailwind
- Remix에서 사용하는 가장 인기있는 스타일링 방법이다.
tailwind.config.js가 root에 있는 경우 자동 작동한다.- @import 를 직접 사용하지 말고 @tailwind 지시어를 사용하는걸 추천한다고 함.
Vanilla Extract
- 타입스크립트를 사용해 css를 만들어주는 라이브러리 사용가능