우리 팀 프로젝트에서는 node하시는 백엔드 분이 계셔서,
서버와 통신을 http통신으로 한다.
서버작업 하시는 분이 "세션,or 토큰" 중에 어떤 것으로 할건지 물어봤을 때,
내가 토큰으로 하시죠 해서 토큰방식으로 진행하기로 했다.
다시 복기 하자면,
토큰방식은 클라이언트에서 서버와 통신하기 위한 값을 관리한다.
따라서 토큰을 관리하기 위해서 생각했던 것은,
1. 전역적으로 사용할 것이므로, 전역관리 상태를 사용하여햐 했고,
2. 혹시나 하는 새로고침에도, 값이 사라지지 않아야 하므로,
localStorage, SessionStorage중 하나에 저장해서 토큰 값을 받아오면 우선적으로 브라우저의 스토리지에 저장하고 그 값을 전역에 저장하여서 관리하기로 생각하였다.
물론 저번 포스팅에 작성 했지만, 계속 사용안하면 이상하리만큼 까먹으니깐 다시 작성해봤다.
const schema = z
.object({
email: z.string().email("이메일 쓰세요"),
password: z.string().min(6, "최소6글자"),
passwordConfirm: z.string().min(6, "비번이 틀렸어요"),
})
.superRefine(({ password, passwordConfirm }, ctx) => {// superRefine으로 비밀번호 동일검사
// ctx는 refinementCtx이다.
if (password !== passwordConfirm) {
ctx.addIssue({
code: "custom",
message: "비번이 맞지않습니다.",
path: ["password"],
});
ctx.addIssue({
code: "custom",
message: "비번이 맞지않습니다.",
path: ["passwordConfirm"],
});
}
});
type Schema = z.infer<typeof schema>;
const method = useForm<Schema>({
resolver: zodResolver(schema), // pnpm i @hookform/resolvers
});
zod의 method중 refine
은 .superRefine의 슈가 문법일 뿐, superRefine은 코드의 검증을 수행하다가 유효하지 않으면 zodIssueCode를 설정 할 수 있다. 쉽게 말하면, refine
대비 확장할 수 있다는 이야기이다.
export interface RefinementCtx {
addIssue: (arg: IssueData) => void;
path: (string | number)[];
}
.superRefine((fields,ctx:RefinementCtx)=>{
//fields는 위의 z.object의 값들이며, ctx는 issue를 생성할 수 있는 객체가 제공된다.
ctx.addIssue({
code: "custom" || "z.ZodIssueCode.custom", // 코드에들어간 "custom"은 내가 custom을 할게라고 명시하는것
message: "비번이 맞지않습니다.", // 보여줄 에러 메세지
path: ["passwordConfirm"], // 어느 field인지 지정해준다.
});
})
ctx.addIssue()
에 들어가는 code에 따라서 직접 issue를 등록 할 수 있는데, ( 검증이 틀리거나, 맞거나 )
다양한 code들이 들어가게 된다.
code 프로퍼티에 문자열로 원하는 검증 코드 타입을 넣으면, 그 타입에 따라서 프로퍼티 속성이 달라진다.!
아래의 사진은 일부 발췌했다.
ctx.addIssue({
code: "you wanna issue code type" // 원하는 타입의 이슈코드 타입을 명시하면
message: "비번이 맞지않습니다.", // 보여줄 에러 메세지
path: ["passwordConfirm"], // 어느 field인지 지정해준다.
});
기본적으로 ZodIssueBase
가 각 zodIssueType에 들어간다.
그리고 각 code의 value 값인 type의 issue에 따라서 프로퍼티의 타입들이 바뀌는 것을 보여주는 사진이다.
//input
interface Input_Props<
TField extends FieldValues,
TName extends FieldPath<TField>
> {
control: Control<TField>;
name: TName;
}
const Input = <T extends FieldValues, N extends FieldPath<T>>({
control,
name,
}: Input_Props<T, N>) => {
const method = useController({ control, name });
return (
<div>
<input {...method.field} />
{method.fieldState.error && <div>{method.fieldState.error.message}</div>}
</div>
);
};
export default Input;
// Form
//...생략
const submitHandler = async(value:Schema) => {
const { data } = await axios.post("/api/token/handler", values);
}
const method = useForm<Schema>({
resolver: zodResolver(schema), // react-hook-form에 schema를 연결
});
<form onSubmit={method.handleSubmit(submitHandler)}>
<Input // 위의 InputComponent
control={method.control}
name="email"
placeholder="email입력바람"
/>
<Input control={method.control} name="password" placeholder="비번입력" />
<Input
control={method.control}
name="passwordConfirm"
placeholder="비번확인"
/>
<button type="submit">
버튼
</button>
</form>
토큰생성과 db저장을 위해, db.json
과 api router에서 jwt
를 처리하였다.
//...생략
const token = jwt.sign(body.email, "secret");
// db.json으로 보내기
const { data } = await axios.post("http://localhost:4000/users", {
...body,
token,
});
res.status(200).send({ data });
api router에서 토큰을 생성한 것을 혹시나 의아해하는 프엔이 있을까봐 작성하지만, jwt는 node에서(서버)에서 생성과 파괴등을 거치기에 서버리스 함수인 api router에서 작성하였다.
내 db.json에 잘 받아와진다.
이제 zustand의 persist
를 사용해서 localStorage에 담기는과정과 로직을 한번 작성해보자.