REFACTORING
이제 enter페이지의 form들을 이전 챕터에서 배운 React Hook Form으로 다 바꿔보도록 하겠습니다.
/pages/enter.tsx
import { useState } from "react";
import Button from "../components/button";
import Input from "../components/Input";
import { cls } from "../libs/utils";
import { useForm } from "react-hook-form";
type MethodType = "email" | "phone";
interface EnterForm {
email?: string;
phone?: string;
}
export default function Enter() {
const { register, watch, handleSubmit, reset } =
useForm<EnterForm>();
const [method, setMethod] =
useState<MethodType>("email");
const onEmailClick = () => {
reset();
setMethod("email");
};
const onPhoneClick = () => {
reset();
setMethod("phone");
};
const onValid = (data: EnterForm) => {
console.log(data);
};
return (
<div className="mt-16 px-4">
<h3 className="text-center text-3xl font-bold">
Enter to Carrot
</h3>
<div className="mt-8">
<div className="flex flex-col items-center">
<h5 className="text-sm font-medium text-gray-500">
Enter using:
</h5>
<div className="mt-8 grid w-full grid-cols-2 gap-16 border-b">
<button
className={cls(
"border-b-2 pb-4 font-medium",
method === "email"
? " border-orange-500 text-orange-500"
: "border-transparent text-gray-500"
)}
onClick={onEmailClick}
>
Email
</button>
<button
className={cls(
"border-b-2 pb-4 font-medium",
method === "phone"
? " border-orange-500 text-orange-500"
: "border-transparent text-gray-500"
)}
onClick={onPhoneClick}
>
Phone
</button>
</div>
</div>
<form
onSubmit={handleSubmit(onValid)}
className="mt-8 flex flex-col"
>
{method === "email" ? (
<Input
register={register("email", {
required: true,
})}
name="email"
label="Email address"
type="email"
/>
) : null}
{method === "phone" ? (
<Input
register={register("phone", {
required: true,
})}
name="phone"
label="Phone number"
type="number"
kind="phone"
/>
) : null}
{method === "email" ? (
<Button text={"Get login link"} />
) : null}
{method === "phone" ? (
<Button text={"Get one-time password"} />
) : null}
</form>
<div className="mt-8">
<div className="relative">
<div className="absolute w-full border-t border-gray-300" />
<div className="relative -top-3 text-center ">
<span className="bg-white px-2 text-sm text-gray-500">
Or enter with
</span>
</div>
</div>
<div className="mt-3 grid grid-cols-2 gap-3">
<button className="flex justify-center rounded-md border border-gray-500 bg-white py-2 px-4 text-sm font-medium text-gray-500 shadow-sm hover:bg-gray-50">
<svg
className="h-5 w-5"
aria-hidden="true"
fill="currentColor"
viewBox="0 0 20 20"
>
<path d="M6.29 18.251c7.547 0 11.675-6.253 11.675-11.675 0-.178 0-.355-.012-.53A8.348 8.348 0 0020 3.92a8.19 8.19 0 01-2.357.646 4.118 4.118 0 001.804-2.27 8.224 8.224 0 01-2.605.996 4.107 4.107 0 00-6.993 3.743 11.65 11.65 0 01-8.457-4.287 4.106 4.106 0 001.27 5.477A4.073 4.073 0 01.8 7.713v.052a4.105 4.105 0 003.292 4.022 4.095 4.095 0 01-1.853.07 4.108 4.108 0 003.834 2.85A8.233 8.233 0 010 16.407a11.616 11.616 0 006.29 1.84" />
</svg>
</button>
<button className="flex justify-center rounded-md border border-gray-500 bg-white py-2 px-4 text-sm font-medium text-gray-500 shadow-sm hover:bg-gray-50">
<svg
className="h-5 w-5"
aria-hidden="true"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
fillRule="evenodd"
d="M10 0C4.477 0 0 4.484 0 10.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0110 4.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.203 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.942.359.31.678.921.678 1.856 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0020 10.017C20 4.484 15.522 0 10 0z"
clipRule="evenodd"
/>
</svg>
</button>
</div>
</div>
</div>
</div>
);
}
기존의 방법대로 useForm을 import해오고 register를 각각의 Input 컴포넌트에 넣어 주었습니다. 이에 따라 input 컴포넌트도 바꾸어 주어야 하겠죠? 그리고 한개의 email Form에서 phone Form으로 넘어갈 때 Form을 초기화 시켜주기 위해, reset()을 호출해서 초기화 시켜 주었습니다.
/components/Input.tsx
import type { UseFormRegisterReturn } from "react-hook-form";
interface InputProps {
label: string;
name: string;
kind?: "text" | "phone" | "price";
register: UseFormRegisterReturn;
type: string;
required?: boolean;
// [key: string]: any;
}
/* defualt is kind="text" */
const Input = ({
name,
label,
kind = "text",
register,
type,
required,
}: InputProps) => {
return (
<div>
<label
htmlFor={name}
className="text-sm font-medium text-gray-700"
>
{label}
</label>
{kind === "text" ? (
<div className="mt-2 flex items-center shadow-sm">
<input
id={name}
required={required}
{...register}
type={type}
className="w-full appearance-none rounded-md border border-gray-300
px-3 py-2 placeholder-gray-400 shadow-sm focus:border-orange-500
focus:outline-none focus:ring-1 focus:ring-orange-500"
/>
</div>
) : null}
{kind === "phone" ? (
<div className="mt-2 flex rounded-md shadow-sm">
<span className="flex select-none items-center justify-center rounded-l-md border border-r-0 border-gray-300 bg-gray-50 px-3 text-sm text-gray-500">
+82
</span>
<input
id={name}
required={required}
{...register}
type={type}
className="w-full appearance-none rounded-r-md border border-gray-300
px-3 py-2 placeholder-gray-400 shadow-sm focus:border-orange-500 focus:outline-none focus:ring-1 focus:ring-orange-500"
/>
</div>
) : null}
{kind === "price" ? (
<div className="relative flex items-center rounded-md shadow-sm">
<div className="pointer-events-none absolute left-0 flex items-center justify-center pl-3">
<span className="text-sm text-gray-500">
$
</span>
</div>
<input
id={name}
required={required}
{...register}
type={type}
className="w-full appearance-none rounded-md border border-gray-300 px-3 py-2 pl-7 placeholder-gray-400 shadow-sm focus:border-orange-500 focus:outline-none focus:ring-1 focus:ring-orange-500"
/>
<div className="pointer-events-none absolute right-0 flex items-center pr-3">
<span className="text-gray-500">USD</span>
</div>
</div>
) : null}
</div>
);
};
export default Input;
다음과 같이 불필요한 ...res를 없애고, 필요한 요소만 적어 주었습니다. 일단 InputProps를 보면 register는 UseFormRegisterReturn이기 때문에 필수로 적우주고, type, required도 적어주겠습니다. 이 register자체가 객체이기 때문에 input에 뿌려주어야 합니다. 뿌려주기 위해서는 {...register}를 해주면 되겠죠?
Form Submition
이제 백엔드로 데이터 요청을 보내 봅시다.
/pages/api/users/enter.tsx
import { NextApiRequest, NextApiResponse } from "next";
import client from "../../../libs/client";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== "POST") {
res.status(401).end();
}
console.log(req.body.email);
res.status(200).end();
}
해 주어야 합니다. 하지만 프론트에서 중요한 설정 하나를 해주어야 하는데, req는 body를 req type encoding에 따라 해석합니다. 따라서 header에 'Contenet-Type'을 반드시 설정해 주어야 합니다.
/pages/enter.tsx
import type { NextPage } from "next";
import { useState } from "react";
import Button from "../components/button";
import Input from "../components/Input";
import { cls } from "../libs/utils";
import { useForm } from "react-hook-form";
type MethodType = "email" | "phone";
interface EnterForm {
email?: string;
phone?: string;
}
const Enter: NextPage = () => {
const [submitting, setSubmitting] = useState(false);
const { register, watch, handleSubmit, reset } =
useForm<EnterForm>();
const [method, setMethod] =
useState<MethodType>("email");
const onEmailClick = () => {
reset();
setMethod("email");
};
const onPhoneClick = () => {
reset();
setMethod("phone");
};
const onValid = (data: EnterForm) => {
setSubmitting(true);
fetch("/api/users/enter", {
method: "POST",
body: JSON.stringify(data),
headers: {
"Content-Type": "application/json",
},
}).then(() => {
setSubmitting(false);
});
};
return (
<div className="mt-16 px-4">
<h3 className="text-center text-3xl font-bold">
Enter to Carrot
</h3>
<div className="mt-8">
<div className="flex flex-col items-center">
<h5 className="text-sm font-medium text-gray-500">
Enter using:
</h5>
<div className="mt-8 grid w-full grid-cols-2 gap-16 border-b">
<button
className={cls(
"border-b-2 pb-4 font-medium",
method === "email"
? " border-orange-500 text-orange-500"
: "border-transparent text-gray-500"
)}
onClick={onEmailClick}
>
Email
</button>
<button
className={cls(
"border-b-2 pb-4 font-medium",
method === "phone"
? " border-orange-500 text-orange-500"
: "border-transparent text-gray-500"
)}
onClick={onPhoneClick}
>
Phone
</button>
</div>
</div>
<form
onSubmit={handleSubmit(onValid)}
className="mt-8 flex flex-col"
>
{method === "email" ? (
<Input
register={register("email", {
required: true,
})}
name="email"
label="Email address"
type="email"
/>
) : null}
{method === "phone" ? (
<Input
register={register("phone", {
required: true,
})}
name="phone"
label="Phone number"
type="number"
kind="phone"
/>
) : null}
{method === "email" ? (
<Button text={"Get login link"} />
) : null}
{method === "phone" ? (
<Button
text={
submitting
? "Loading"
: "Get one-time password"
}
/>
) : null}
</form>
<div className="mt-8">
<div className="relative">
<div className="absolute w-full border-t border-gray-300" />
<div className="relative -top-3 text-center ">
<span className="bg-white px-2 text-sm text-gray-500">
Or enter with
</span>
</div>
</div>
<div className="mt-3 grid grid-cols-2 gap-3">
<button className="flex justify-center rounded-md border border-gray-500 bg-white py-2 px-4 text-sm font-medium text-gray-500 shadow-sm hover:bg-gray-50">
<svg
className="h-5 w-5"
aria-hidden="true"
fill="currentColor"
viewBox="0 0 20 20"
>
<path d="M6.29 18.251c7.547 0 11.675-6.253 11.675-11.675 0-.178 0-.355-.012-.53A8.348 8.348 0 0020 3.92a8.19 8.19 0 01-2.357.646 4.118 4.118 0 001.804-2.27 8.224 8.224 0 01-2.605.996 4.107 4.107 0 00-6.993 3.743 11.65 11.65 0 01-8.457-4.287 4.106 4.106 0 001.27 5.477A4.073 4.073 0 01.8 7.713v.052a4.105 4.105 0 003.292 4.022 4.095 4.095 0 01-1.853.07 4.108 4.108 0 003.834 2.85A8.233 8.233 0 010 16.407a11.616 11.616 0 006.29 1.84" />
</svg>
</button>
<button className="flex justify-center rounded-md border border-gray-500 bg-white py-2 px-4 text-sm font-medium text-gray-500 shadow-sm hover:bg-gray-50">
<svg
className="h-5 w-5"
aria-hidden="true"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
fillRule="evenodd"
d="M10 0C4.477 0 0 4.484 0 10.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0110 4.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.203 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.942.359.31.678.921.678 1.856 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0020 10.017C20 4.484 15.522 0 10 0z"
clipRule="evenodd"
/>
</svg>
</button>
</div>
</div>
</div>
</div>
);
};
export default Enter;
다음과 같은데 onValid 함수에서 form데이터가 submit된 데이터를 body로 보내고 header로 'Content-Type'을 'application/json'으로 설정해 주었습니다. 또한 submitting이라는 상태를 만들어 로딩중이면 버튼의 text를 'Loading'이라고 뜨게 했습니다.
이제 다음으로는 이를 더 깔끔하게 정리해 보도록 하겠습니다. 그냥 User Expereience가 좋게 React hook을 만들어서 빼보도록 하겠습니다.
useMutation이라는 hook을 만들 것인데, 인자로는 api경로, 반환값은 배열인데 첫번쨰 인자로 post method를 날리는 함수와, {loading, data, error}객체를 두번째로 반환합니다. 먼저 어떻게 프론트에서 사용할 수 있는지 enter.tsx에서 살펴보겠습니다.
/pages/enter.tsx
import type { NextPage } from "next";
import { useState } from "react";
import Button from "../components/button";
import Input from "../components/Input";
import { cls } from "../libs/client/utils";
import { useForm } from "react-hook-form";
import useMutation from "../libs/client/useMutation";
type MethodType = "email" | "phone";
interface EnterForm {
email?: string;
phone?: string;
}
const Enter: NextPage = () => {
const [enter, { loading, data, error }] =
useMutation("/api/users/enter");
const [submitting, setSubmitting] = useState(false);
const { register, handleSubmit, reset } =
useForm<EnterForm>();
const [method, setMethod] =
useState<MethodType>("email");
const onEmailClick = () => {
reset();
setMethod("email");
};
const onPhoneClick = () => {
reset();
setMethod("phone");
};
const onValid = (validForm: EnterForm) => {
enter(validForm);
};
console.log(loading, data, error);
return (
<div className="mt-16 px-4">
<h3 className="text-center text-3xl font-bold">
Enter to Carrot
</h3>
<div className="mt-8">
<div className="flex flex-col items-center">
<h5 className="text-sm font-medium text-gray-500">
Enter using:
</h5>
<div className="mt-8 grid w-full grid-cols-2 gap-16 border-b">
<button
className={cls(
"border-b-2 pb-4 font-medium",
method === "email"
? " border-orange-500 text-orange-500"
: "border-transparent text-gray-500"
)}
onClick={onEmailClick}
>
Email
</button>
<button
className={cls(
"border-b-2 pb-4 font-medium",
method === "phone"
? " border-orange-500 text-orange-500"
: "border-transparent text-gray-500"
)}
onClick={onPhoneClick}
>
Phone
</button>
</div>
</div>
<form
onSubmit={handleSubmit(onValid)}
className="mt-8 flex flex-col"
>
{method === "email" ? (
<Input
register={register("email", {
required: true,
})}
name="email"
label="Email address"
type="email"
/>
) : null}
{method === "phone" ? (
<Input
register={register("phone", {
required: true,
})}
name="phone"
label="Phone number"
type="number"
kind="phone"
/>
) : null}
{method === "email" ? (
<Button text={"Get login link"} />
) : null}
{method === "phone" ? (
<Button
text={
submitting
? "Loading"
: "Get one-time password"
}
/>
) : null}
</form>
<div className="mt-8">
<div className="relative">
<div className="absolute w-full border-t border-gray-300" />
<div className="relative -top-3 text-center ">
<span className="bg-white px-2 text-sm text-gray-500">
Or enter with
</span>
</div>
</div>
<div className="mt-3 grid grid-cols-2 gap-3">
<button className="flex justify-center rounded-md border border-gray-500 bg-white py-2 px-4 text-sm font-medium text-gray-500 shadow-sm hover:bg-gray-50">
<svg
className="h-5 w-5"
aria-hidden="true"
fill="currentColor"
viewBox="0 0 20 20"
>
<path d="M6.29 18.251c7.547 0 11.675-6.253 11.675-11.675 0-.178 0-.355-.012-.53A8.348 8.348 0 0020 3.92a8.19 8.19 0 01-2.357.646 4.118 4.118 0 001.804-2.27 8.224 8.224 0 01-2.605.996 4.107 4.107 0 00-6.993 3.743 11.65 11.65 0 01-8.457-4.287 4.106 4.106 0 001.27 5.477A4.073 4.073 0 01.8 7.713v.052a4.105 4.105 0 003.292 4.022 4.095 4.095 0 01-1.853.07 4.108 4.108 0 003.834 2.85A8.233 8.233 0 010 16.407a11.616 11.616 0 006.29 1.84" />
</svg>
</button>
<button className="flex justify-center rounded-md border border-gray-500 bg-white py-2 px-4 text-sm font-medium text-gray-500 shadow-sm hover:bg-gray-50">
<svg
className="h-5 w-5"
aria-hidden="true"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
fillRule="evenodd"
d="M10 0C4.477 0 0 4.484 0 10.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0110 4.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.203 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.942.359.31.678.921.678 1.856 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0020 10.017C20 4.484 15.522 0 10 0z"
clipRule="evenodd"
/>
</svg>
</button>
</div>
</div>
</div>
</div>
);
};
export default Enter;
onValid함수가 획기적으로 줄었죠? useMutation가 어떻게 생겼을지 한번 봅시다. 앗 그리고 lib폴더를 두가지로 나누었는데, client, server로 나누었는데, client, server에서의 util함수를 나누고 싶었기 때문입니다.
/libs/client/useMutation.ts
import { useState } from "react";
interface UseMutationState {
loading: boolean;
data: undefined | any;
error: undefined | any;
}
export default function useMutation(
url: string
): [(data: any) => void, UseMutationState] {
const [state, setState] = useState({
loading: false,
data: undefined,
error: undefined,
});
function mutation(data: any) {
setState((prev: UseMutationState) => ({
...prev,
loading: true,
}));
fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
})
.then((response) =>
response.json().catch(() => {})
)
.then((data) =>
setState((prev) => ({ ...prev, data }))
)
.catch((error) =>
setState((prev) => ({ ...prev, error }))
)
.finally(() =>
setState((prev) => ({
...prev,
loading: false,
}))
);
}
return [mutation, state];
}
다음과 같이 공통된 부분과 error를 잡아줄 수 있습니다. 다음으로는 Next API Route가 어떻게 작성되었는지 볼까요?
/pages/api/users/enter.tsx
import { NextApiRequest, NextApiResponse } from "next";
import client from "../../../libs/client/client";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== "POST") {
res.status(401).end();
}
console.log(req.body);
res.status(200).end();
}
일단 POST만 처리하였는데, 나중에 GET은 SWR로 처리할 것이기 때문입니다. res.end()로 따로 응답값을 주진 않았습니다.
브라우저와 서버에서 다음과 같이 정상 작동함을 보실 수 있습니다.
이제 또 다시 한번 Next API Route를 withHandler라는 함수를 통해 정리해 보도록 하겠습니다.
일단 먼저 withHandler를 어떻게 호출하면 좋을지 API Route에서 한번 보고 작성해 봅시다.
/pages/api/users/enter.tsx
import type {
NextApiRequest,
NextApiResponse,
} from "next";
import client from "../../../libs/client/client";
import withHandler from "../../../libs/server/withHandler";
async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
console.log(req.body);
res.status(200).end();
}
export default withHandler("POST", handler);
와 같이 작성해 줄 수 있습니다. Next API Route에서의 몇가지 규칙이 있습니다. 우리가 실행할 함수를 return해야 한다는 점과 export defaults로 반환해야 한다는 점입니다. 이 점에 착안하여 함수를 반환하는 함수인 고차함수(High Order Function)(HOC) withHandler를 작성해 봅시다.
withHandler는 인자로 작동시킬 HTTP PROTOCOL을 첫번쨰 인자로 넘겨주고, 작동 시켜 줄 함수를 두 번째 인자로 넘겨줍니다. 이제 본격적으로 어떻게 생겨먹은 함수인지 봅시다.
/libs/server/withHandler.ts
import type {
NextApiRequest,
NextApiResponse,
} from "next/types";
export default function withHandler(
method: "GET" | "POST" | "DELETE",
fn: (
req: NextApiRequest,
res: NextApiResponse
) => void
) {
// 우리가 NexJS에서 실행할 함수를 return해야 합니다.
return async function (
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== method) {
return res.status(405).end();
}
try {
await fn(req, res);
} catch (error) {
console.error(error);
return res.status(500).json({ error });
}
};
}
withHandler는 function을 반환해야 합니다. 이 함수 안에서 두 번쨰 인자로 전달한 함수를 작동시켜 주어야 합니다. 그리고 req.method !== method 로 POST만 작동시켜주는 api로 만들어 줍니다. 그리고 api를 받아오는 과정에서 error가 발생하는 예외를 처리해주는 코드를 작성해 줍니다.
https://en.wikipedia.org/wiki/Higher-order_function
고차함수를 여러번 쓰다보면 익숙해 지실겁니다.
그리고 자주쓰는 status코드를 정리해 보도록 하겠습니다. ( 매번 써도 기억에 잘 남지가 않습니다 ㅠ... )
200 OK: 요청이 성공적으로 되었습니다. (성공)
400 Bad Request: 이 응답은 잘못된 문법으로 인하여 서버가 요청을 이해할 수 없음을 의미합니다. (클라이언트에서 Request할 때 발생한 문제)
403 Forbidden: 클라이언트는 콘텐츠에 접근할 권리를 가지고 있지 않습니다. 401과 다른 점은 서버가 클라이언트가 누구인지 알고 있습니다.
405 Method Not Allowed: 요청한 메서드는 서버에서 알고 있지만, 제거되었고 사용할 수 없습니다. (허용되지 않은 메서드 사용)
500 Internal Server Error: 서버가 처리 방법을 모르는 상황이 발생했습니다. 서버는 아직 처리 방법을 알 수 없습니다.
https://developer.mozilla.org/ko/docs/Web/HTTP/Status
Paths
마지막으로 엄청엄청 사소하지만 중요한 작업을 진행하도록 하겠습니다. 바로 경로를 바꿔주는 것입니다. 막 "../../components/**/*"이런경우와 "../libs/**/*"이런 경우를 생각해 봅시다. .. 이매우 거슬리죠!! 그래서 이를 모두 전자는 "@components/**/*"로 후자는 "@libs/**/*"로 바꿔보겠습니다. 이를 하기 위해서는 root directory의 tsconfig.json을 만져주어야 합니다.
/tsconfig.json
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"baseUrl": ".",
"paths": {
"@libs/*": ["libs/*"],
"@components/*": ["components/*"],
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}
다음과 같이 baseUrl을 tsconfig.json이 있는 rootDirectory로 설정해 주고, paths설정을 libs의 모든 파일을 @libs로 접근 가능하게 하고, components도 마찬가지로 설정해 줍니다.
그리고 vscode의 "Edit -> Edit in File"을 클릭 한다음에 "../../components을 위에 치고 "@components를 쳐주게 되면 모든 파일에서 전자가 후자로 대치됩니다. 동일하게 "../components도 진행해 주고 "../libs를 "@libs로 대치해 주는 작업을 거쳐줍니다. 그렇게 되면 하나의 파일을 들여다 보면
/pages/enter.tsx
import client from "@libs/client/client";
import withHandler from "@libs/server/withHandler";
import type {
NextApiRequest,
NextApiResponse,
} from "next";
async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
console.log(req.body);
res.status(200).end();
}
export default withHandler("POST", handler);
자동완성에도 @libs @components가 뜨며 페이지 오류가 하나도 안 뜸을 확인할 수 있습니다. 굿굿
'Web > CloneCoding' 카테고리의 다른 글
[Carrot Market] #9 - Authentication - 2 (0) | 2022.05.12 |
---|---|
[Carrot Market] #9 - Authentication - 1 (0) | 2022.05.12 |
[Carrot Market] #7 React Hook Form (0) | 2022.05.03 |
[Carrot Market] #6 Prisma - PlanetScale (0) | 2022.05.03 |
[Carrot Market] #5 TAILWIND ReFactoring UI - 2 (0) | 2022.05.02 |