-
Notifications
You must be signed in to change notification settings - Fork 39
[배수민] sprint8 #232
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[배수민] sprint8 #232
The head ref may contain hidden characters: "React-\uBC30\uC218\uBBFC-sprint8"
Conversation
src/stores/useAuthStore.ts
Outdated
| const useAuthStore = create<AuthState>((set) => ({ | ||
| email: { | ||
| value: "", | ||
| validInfo: { isValid: null, message: "" }, | ||
| }, | ||
| password: { | ||
| value: "", | ||
| validInfo: { isValid: null, message: "" }, | ||
| }, | ||
| passwordCheck: { | ||
| value: "", | ||
| validInfo: { isValid: null, message: "" }, | ||
| }, | ||
| nickname: { | ||
| value: "", | ||
| validInfo: { isValid: null, message: "" }, | ||
| }, | ||
| setField: (name, value) => | ||
| set((state) => ({ | ||
| [name]: { ...state[name], value }, | ||
| })), | ||
| validateField: (name, ...values) => { | ||
| const { isValid, message } = validateInput(name, ...values); | ||
| set((state) => ({ | ||
| [name]: { | ||
| ...state[name], | ||
| validInfo: { | ||
| isValid, | ||
| message, | ||
| }, | ||
| }, | ||
| })); | ||
| }, | ||
| resetFields: () => | ||
| set({ | ||
| email: { value: "", validInfo: { isValid: null, message: "" } }, | ||
| password: { value: "", validInfo: { isValid: null, message: "" } }, | ||
| }), | ||
| })); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
zustand로 로그인/회원가입 상태를 useAuthStore에서 공통으로 관리하고 있는데, 페이지 이동 시 상태값이 남아있어서 resetFields를 사용하여 따로 초기화를 해주고 있습니다. 로그인/회원가입 상태를 따로 관리하는 것이 좋을까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
네! 따로 분리해 관리하는것을 권장합니다.
이유는, 로그인과 회원가입이 같은 store를 사용하여 상태가 공유되어, 페이지 이동 시 이전 상태가 남아있는 문제가 발생할 수 있기 때문입니다 :)
또한, 타입스크립트를 사용하는 관점에서도 각 store가 필요한 필드만 정의하게되면 타입 안정성이 향상될수있으므로, 기능별로 독립적인 수정이 가능한 형태로 분리하시는게 훨씬 좋겠네요!
일반적으로 소프트웨어 방법론에서 불필요한 관심사 결합이 일어나면 예상치못한 사이드이펙트가 발생할 수 있어 좋지 않습니다.
이점을 알아가시면 좋을것같네요 :)
아래와 같이 별도의 store를 통해 관리해볼까요?
import { create } from "zustand";
import { validateInput } from "../utils/formValidation";
interface LoginState {
email: {
value: string;
validInfo: { isValid: boolean | null; message: string };
};
password: {
value: string;
validInfo: { isValid: boolean | null; message: string };
};
setField: (name: "email" | "password", value: string) => void;
validateField: (name: "email" | "password", value: string) => void;
resetFields: () => void;
}
const useLoginStore = create<LoginState>((set) => ({
email: {
value: "",
validInfo: { isValid: null, message: "" },
},
password: {
value: "",
validInfo: { isValid: null, message: "" },
},
setField: (name, value) =>
set((state) => ({
[name]: { ...state[name], value },
})),
validateField: (name, value) => {
const { isValid, message } = validateInput(name, value);
set((state) => ({
[name]: {
...state[name],
validInfo: {
isValid,
message,
},
},
}));
},
resetFields: () =>
set({
email: { value: "", validInfo: { isValid: null, message: "" } },
password: { value: "", validInfo: { isValid: null, message: "" } },
}),
}));
export default useLoginStore;import { create } from "zustand";
import { validateInput } from "../utils/formValidation";
interface SignupState {
email: {
value: string;
validInfo: { isValid: boolean | null; message: string };
};
password: {
value: string;
validInfo: { isValid: boolean | null; message: string };
};
passwordCheck: {
value: string;
validInfo: { isValid: boolean | null; message: string };
};
nickname: {
value: string;
validInfo: { isValid: boolean | null; message: string };
};
setField: (
name: "email" | "password" | "passwordCheck" | "nickname",
value: string
) => void;
validateField: (
name: "email" | "password" | "passwordCheck" | "nickname",
value: string,
compareValue?: string
) => void;
resetFields: () => void;
}
const useSignupStore = create<SignupState>((set) => ({
email: {
value: "",
validInfo: { isValid: null, message: "" },
},
password: {
value: "",
validInfo: { isValid: null, message: "" },
},
passwordCheck: {
value: "",
validInfo: { isValid: null, message: "" },
},
nickname: {
value: "",
validInfo: { isValid: null, message: "" },
},
setField: (name, value) =>
set((state) => ({
[name]: { ...state[name], value },
})),
validateField: (name, value, compareValue) => {
const { isValid, message } = validateInput(
name,
compareValue ? [value, compareValue] : [value]
);
set((state) => ({
[name]: {
...state[name],
validInfo: {
isValid,
message,
},
},
}));
},
resetFields: () =>
set({
email: { value: "", validInfo: { isValid: null, message: "" } },
password: { value: "", validInfo: { isValid: null, message: "" } },
passwordCheck: { value: "", validInfo: { isValid: null, message: "" } },
nickname: { value: "", validInfo: { isValid: null, message: "" } },
}),
}));
export default useSignupStore;There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
넵! 로그인/회원가입 store 분리하여 사용하겠습니다
src/pages/LoginPage.tsx
Outdated
| useEffect(() => { | ||
| resetFields(); | ||
| }, [resetFields]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
상태를 useAuthStore에서 공통으로 관리하고 있어 페이지에서 useEffect로 초기화 해주는 부분입니다.
src/utils/formValidation.ts
Outdated
| export const validateInput = <T extends FormFieldName>( | ||
| validatorType: T, | ||
| ...values: Parameters<ValidatorRules[T]> | ||
| ): ValidatorResult => { | ||
| const errorValidator = () => ({ | ||
| isValid: false, | ||
| message: "유효성 체크가 정의되지 않았습니다.", | ||
| }); | ||
|
|
||
| const validator = | ||
| (validators[validatorType] as ( | ||
| ...args: Parameters<ValidatorRules[T]> | ||
| ) => ValidatorResult) || errorValidator; | ||
| const { isValid, message } = validator(...values); | ||
| return { isValid, message }; | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
폼 유효성 체크 (formValidation)파일에서 validateInput에서 여러 개의 값을 받기 위해 args를 사용하였는데, 타입을 지금처럼 작성해도 될까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
잘 하셨는데, 타입을 조금 복잡하게 쓰시는 편인것 같아서, ValidatorParams 타입을 추가해 validateInput에서 제네릭으로 각 필드별 타입을 안전하게 추론하게끔 개선하면 좋겠네요! 👍
// 각 필드별 유효성 검사 함수의 매개변수 타입 정의
export type ValidatorParams = {
image: [value: string];
title: [value: string];
content: [value: string];
price: [value: string];
tagList: [value: string[]];
inquiry: [value: string];
email: [value: string];
password: [value: string];
passwordCheck: [value: string, password: string];
nickname: [value: string];
};| export const validateInput = <T extends FormFieldName>( | |
| validatorType: T, | |
| ...values: Parameters<ValidatorRules[T]> | |
| ): ValidatorResult => { | |
| const errorValidator = () => ({ | |
| isValid: false, | |
| message: "유효성 체크가 정의되지 않았습니다.", | |
| }); | |
| const validator = | |
| (validators[validatorType] as ( | |
| ...args: Parameters<ValidatorRules[T]> | |
| ) => ValidatorResult) || errorValidator; | |
| const { isValid, message } = validator(...values); | |
| return { isValid, message }; | |
| }; | |
| export const validateInput = <T extends FormFieldName>( | |
| validatorType: T, | |
| ...values: ValidatorParams[T] | |
| ): ValidatorResult => { | |
| const validator = validators[validatorType] as ( | |
| ...args: ValidatorParams[T] | |
| ) => ValidatorResult; | |
| return validator(...values); | |
| }; | |
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
넵! 이 부분 타입 추가하여 수정하겠습니다
addiescode-sj
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
수고하셨습니다!
타입스크립트 활용 위주로 피드백 드려봤어요 :)
주요 리뷰 포인트
- 컴포넌트 분리 제안
- 의도가 있지 않다면 분기문 추가대신 삼항 연산자 활용
- jsdoc으로 문서화 시도
- isValid 타입 안정성 문제 해결
- 초기화 용도 effect deps list 변경
- 불필요한 관심사 결합 방지하기 (store 기능별로 분리)
- validateInput 타입 안정성 개선
src/components/Footer.tsx
Outdated
| <a | ||
| href="https://www.facebook.com" | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| > | ||
| <img src={facebookLogo} alt="페이스북 로고" loading="lazy" /> | ||
| </a> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
가독성과 중복 제거를 위해 이런 부분은 컴포넌트로 분리해서 사용해볼까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
넵! 컴포넌트로 분리하여 추가하겠습니다
src/components/Nav.js
Outdated
| {isLogin && ( | ||
| <div className="header__content__user"> | ||
| <Link to="user"> | ||
| <img | ||
| src={userIcon} | ||
| alt="사용자 아이콘" | ||
| className="header__content__user__icon" | ||
| /> | ||
| </Link> | ||
| </div> | ||
| )} | ||
| {!isLogin && ( | ||
| <div className=""> | ||
| <Link to="login"> | ||
| <Button>로그인</Button> | ||
| </Link> | ||
| </div> | ||
| )} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
조건 추가를 고려해서 따로 분기문을 작성한게 아니라면 (의도),
삼항 연산자를 사용하시는게 좀더 깔끔해질것같네요!
{isLogin ? (
<div className="header__content__user">
<Link to="user">
<img
src={userIcon}
alt="사용자 아이콘"
className="header__content__user__icon"
/>
</Link>
</div>
) : (
<div className="">
<Link to="login">
<Button>로그인</Button>
</Link>
</div>
)}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
넵! 조건 추가가 될 예정은 아니라 삼항 연산자 사용하여 수정하겠습니다
src/components/Textfield.tsx
Outdated
| /* | ||
| [Textfield 필수 속성] | ||
| - onValueChange: 컴포넌트 밖에서 텍스트필드 값을 변경하는 메소드 전달 필요 | ||
| [Textfield 상태 속성] | ||
| - isValid: 에러 상태 표시 => null(초기상태). true 값을 넘기면 메시지 출력 | ||
| - message: 메세지가 있는 경우 출력 | ||
| */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
주석을 통해 어느정도의 문서화를 시도하신다면,
jsdoc 형식으로 작성해보시는건 어떨까요?
/**
* Textfield 컴포넌트
*
* @param {function} onValueChange - 컴포넌트 밖에서 텍스트필드 값을 변경하는 메소드
* @required
* @param {string} name - input 필드 이름
* @required
* @param {string} [value] - input 필드 값
* @param {boolean|null} [isValid=null] - 에러 상태 표시 (null: 초기상태, false: 에러상태)
* @param {string} [message] - 에러 메시지 (isValid가 false일 때 출력)
* @param {string} [className] - 추가 CSS 클래스명
* @param {string} [type] - input 타입 (password인 경우 비밀번호 표시/숨김 기능 제공)
* @param {...InputHTMLAttributes<HTMLInputElement>} rest - 기타 HTML input 속성들
* @returns {JSX.Element} Textfield 컴포넌트
*
* @example
* // 기본 사용 예시
* <Textfield
* name="username"
* value={username}
* onValueChange={handleChange}
* />
*
* @example
* // 비밀번호 필드 사용 예시
* <Textfield
* name="password"
* value={password}
* type="password"
* onValueChange={handleChange}
* />
*/There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
jsDoc 형식 적용했습니다!
src/components/Textfield.tsx
Outdated
| */ | ||
|
|
||
| interface TextfieldProps extends InputHTMLAttributes<HTMLInputElement> { | ||
| onValueChange?: (name: string, value: string) => void; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
주석에서는 필수 속성이라고 명시되어있는데, 타입 정의할때는 옵셔널 태그가 붙여져있네요. 타입을 의도에 맞게 맞춰서 사용해주세요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
넵! 필수 속성이어서 옵셔널 태그를 빼야할 것 같습니다
src/components/Textfield.tsx
Outdated
| )} | ||
| </div> | ||
| {showMessage && ( | ||
| <div className={`${statusMessageClass[String(isValid)]}`}> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isValid가 null일 때 String(null)은 "null"이 되어 statusMessageClass의 키로 사용되는데, 타입 안정성이 떨어지고 예측하기 어렵게 만드는 문제가 있네요!
부정확한 타입 정의를 제거하고 코드 안정성을 높이기위해 statusMessageClass mapper를 만드는대신, 아래와 같이 타입이 정의된 렌더러 함수를 사용해보는 방법은 어떨까요?
const getErrorMessageClass = (isValid: boolean | null): string => {
if (isValid === false) {
return "input__message--error";
}
return "";
};| <div className={`${statusMessageClass[String(isValid)]}`}> | |
| <div className={getErrorMessageClass(isValid)}>{message}</div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
타입 안정성이 떨어지는 문제가 있었네요! 함수로 변경하여 적용하겠습니다
src/pages/LoginPage.tsx
Outdated
| const disableLoginButton = () => { | ||
| return !(email.validInfo.isValid && password.validInfo.isValid); | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
현재 이 함수는 단순히 조건식을 반환하는 역할만 하고 있죠? 함수로 만드는 것보다는 적절한 네이밍을 가진 상수로 만드는 것이 더 좋겠네요.
전달하는 의미도 명확해지겠죠?
const isLoginFormValid =
email.validInfo.isValid && password.validInfo.isValid;
...
return (
<Button disabled={!isSignupFormValid}>회원가입</Button>
);There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
넵! 상수로 수정하는 것이 좋을 것 같네요 수정하겠습니다
src/pages/SignupPage.tsx
Outdated
| useEffect(() => { | ||
| resetFields(); | ||
| }, [resetFields]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
해당 effect의 deps list도 초기화 용도에 걸맞게 빈배열로 바꿔주세요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
빈 배열로 바꾸고, eslint 오류로 인해 주석 추가했습니다.
eslint-disable-next-line react-hooks/exhaustive-deps
src/stores/useAuthStore.ts
Outdated
| const useAuthStore = create<AuthState>((set) => ({ | ||
| email: { | ||
| value: "", | ||
| validInfo: { isValid: null, message: "" }, | ||
| }, | ||
| password: { | ||
| value: "", | ||
| validInfo: { isValid: null, message: "" }, | ||
| }, | ||
| passwordCheck: { | ||
| value: "", | ||
| validInfo: { isValid: null, message: "" }, | ||
| }, | ||
| nickname: { | ||
| value: "", | ||
| validInfo: { isValid: null, message: "" }, | ||
| }, | ||
| setField: (name, value) => | ||
| set((state) => ({ | ||
| [name]: { ...state[name], value }, | ||
| })), | ||
| validateField: (name, ...values) => { | ||
| const { isValid, message } = validateInput(name, ...values); | ||
| set((state) => ({ | ||
| [name]: { | ||
| ...state[name], | ||
| validInfo: { | ||
| isValid, | ||
| message, | ||
| }, | ||
| }, | ||
| })); | ||
| }, | ||
| resetFields: () => | ||
| set({ | ||
| email: { value: "", validInfo: { isValid: null, message: "" } }, | ||
| password: { value: "", validInfo: { isValid: null, message: "" } }, | ||
| }), | ||
| })); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
네! 따로 분리해 관리하는것을 권장합니다.
이유는, 로그인과 회원가입이 같은 store를 사용하여 상태가 공유되어, 페이지 이동 시 이전 상태가 남아있는 문제가 발생할 수 있기 때문입니다 :)
또한, 타입스크립트를 사용하는 관점에서도 각 store가 필요한 필드만 정의하게되면 타입 안정성이 향상될수있으므로, 기능별로 독립적인 수정이 가능한 형태로 분리하시는게 훨씬 좋겠네요!
일반적으로 소프트웨어 방법론에서 불필요한 관심사 결합이 일어나면 예상치못한 사이드이펙트가 발생할 수 있어 좋지 않습니다.
이점을 알아가시면 좋을것같네요 :)
아래와 같이 별도의 store를 통해 관리해볼까요?
import { create } from "zustand";
import { validateInput } from "../utils/formValidation";
interface LoginState {
email: {
value: string;
validInfo: { isValid: boolean | null; message: string };
};
password: {
value: string;
validInfo: { isValid: boolean | null; message: string };
};
setField: (name: "email" | "password", value: string) => void;
validateField: (name: "email" | "password", value: string) => void;
resetFields: () => void;
}
const useLoginStore = create<LoginState>((set) => ({
email: {
value: "",
validInfo: { isValid: null, message: "" },
},
password: {
value: "",
validInfo: { isValid: null, message: "" },
},
setField: (name, value) =>
set((state) => ({
[name]: { ...state[name], value },
})),
validateField: (name, value) => {
const { isValid, message } = validateInput(name, value);
set((state) => ({
[name]: {
...state[name],
validInfo: {
isValid,
message,
},
},
}));
},
resetFields: () =>
set({
email: { value: "", validInfo: { isValid: null, message: "" } },
password: { value: "", validInfo: { isValid: null, message: "" } },
}),
}));
export default useLoginStore;import { create } from "zustand";
import { validateInput } from "../utils/formValidation";
interface SignupState {
email: {
value: string;
validInfo: { isValid: boolean | null; message: string };
};
password: {
value: string;
validInfo: { isValid: boolean | null; message: string };
};
passwordCheck: {
value: string;
validInfo: { isValid: boolean | null; message: string };
};
nickname: {
value: string;
validInfo: { isValid: boolean | null; message: string };
};
setField: (
name: "email" | "password" | "passwordCheck" | "nickname",
value: string
) => void;
validateField: (
name: "email" | "password" | "passwordCheck" | "nickname",
value: string,
compareValue?: string
) => void;
resetFields: () => void;
}
const useSignupStore = create<SignupState>((set) => ({
email: {
value: "",
validInfo: { isValid: null, message: "" },
},
password: {
value: "",
validInfo: { isValid: null, message: "" },
},
passwordCheck: {
value: "",
validInfo: { isValid: null, message: "" },
},
nickname: {
value: "",
validInfo: { isValid: null, message: "" },
},
setField: (name, value) =>
set((state) => ({
[name]: { ...state[name], value },
})),
validateField: (name, value, compareValue) => {
const { isValid, message } = validateInput(
name,
compareValue ? [value, compareValue] : [value]
);
set((state) => ({
[name]: {
...state[name],
validInfo: {
isValid,
message,
},
},
}));
},
resetFields: () =>
set({
email: { value: "", validInfo: { isValid: null, message: "" } },
password: { value: "", validInfo: { isValid: null, message: "" } },
passwordCheck: { value: "", validInfo: { isValid: null, message: "" } },
nickname: { value: "", validInfo: { isValid: null, message: "" } },
}),
}));
export default useSignupStore;
src/utils/formValidation.ts
Outdated
| /* 공통 유효성 체크 */ | ||
| export const validators: ValidatorRules = { | ||
| image: (value) => { | ||
| if (value) | ||
| return { | ||
| isValid: false, | ||
| message: "*이미지 등록은 최대 1개까지 가능합니다.", | ||
| }; | ||
| return { isValid: true, message: "" }; | ||
| }, | ||
| title: (value) => { | ||
| if (!value) return { isValid: false, message: "상품명을 입력해주세요" }; | ||
| return { isValid: true, message: "" }; | ||
| }, | ||
| content: (value) => { | ||
| if (!value) return { isValid: false, message: "상품 소개를 입력해주세요" }; | ||
| return { isValid: true, message: "" }; | ||
| }, | ||
| price: (value) => { | ||
| const unformattedVal = value.replace(/,/g, ""); | ||
| const regex = /^[1-9]\d*$/; | ||
| if (!value) return { isValid: false, message: "판매 가격을 입력해주세요" }; | ||
| if (!regex.test(unformattedVal)) | ||
| return { | ||
| isValid: false, | ||
| message: "판매 가격 형식을 올바르게 입력해주세요", | ||
| }; | ||
|
|
||
| return { isValid: true, message: "" }; | ||
| }, | ||
| tagList: (value) => { | ||
| if (value.length === 0) | ||
| return { isValid: false, message: "태그를 입력해주세요" }; | ||
| return { isValid: true, message: "" }; | ||
| }, | ||
| inquiry: (value) => { | ||
| if (!value) return { isValid: false, message: "문의 내용을 입력해주세요" }; | ||
| return { isValid: true, message: "" }; | ||
| }, | ||
| email: (value) => { | ||
| const regex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/i; | ||
| if (!value) return { isValid: false, message: "이메일을 입력해주세요" }; | ||
| if (!regex.test(value)) | ||
| return { | ||
| isValid: false, | ||
| message: "잘못된 이메일 형식입니다", | ||
| }; | ||
|
|
||
| return { isValid: true, message: "" }; | ||
| }, | ||
| password: (value) => { | ||
| if (!value) return { isValid: false, message: "비밀번호를 입력해주세요" }; | ||
| if (value.length < 8) | ||
| return { | ||
| isValid: false, | ||
| message: "비밀번호를 8자 이상 입력해주세요", | ||
| }; | ||
|
|
||
| return { isValid: true, message: "" }; | ||
| }, | ||
| passwordCheck: (value, password) => { | ||
| if (!password) | ||
| return { isValid: false, message: "비밀번호를 먼저 입력해주세요" }; | ||
| if (value.length < 8) | ||
| return { isValid: false, message: "비밀번호를 8자 이상 입력해주세요" }; | ||
| if (value !== password) | ||
| return { isValid: false, message: "비밀번호가 일치하지 않습니다" }; | ||
| return { isValid: true, message: "" }; | ||
| }, | ||
| nickname: (value) => { | ||
| if (!value) return { isValid: false, message: "닉네임을 입력해주세요" }; | ||
| return { isValid: true, message: "" }; | ||
| }, | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
해당 코드 블락은 공용 constants 폴더 아래로 따로 빼시거나, types/form 아래에서 as const를 활용해 export 하는 방향이 더 좋겠네요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
넵! types/form 파일로 옮기겠습니다
src/utils/formValidation.ts
Outdated
| export const validateInput = <T extends FormFieldName>( | ||
| validatorType: T, | ||
| ...values: Parameters<ValidatorRules[T]> | ||
| ): ValidatorResult => { | ||
| const errorValidator = () => ({ | ||
| isValid: false, | ||
| message: "유효성 체크가 정의되지 않았습니다.", | ||
| }); | ||
|
|
||
| const validator = | ||
| (validators[validatorType] as ( | ||
| ...args: Parameters<ValidatorRules[T]> | ||
| ) => ValidatorResult) || errorValidator; | ||
| const { isValid, message } = validator(...values); | ||
| return { isValid, message }; | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
잘 하셨는데, 타입을 조금 복잡하게 쓰시는 편인것 같아서, ValidatorParams 타입을 추가해 validateInput에서 제네릭으로 각 필드별 타입을 안전하게 추론하게끔 개선하면 좋겠네요! 👍
// 각 필드별 유효성 검사 함수의 매개변수 타입 정의
export type ValidatorParams = {
image: [value: string];
title: [value: string];
content: [value: string];
price: [value: string];
tagList: [value: string[]];
inquiry: [value: string];
email: [value: string];
password: [value: string];
passwordCheck: [value: string, password: string];
nickname: [value: string];
};| export const validateInput = <T extends FormFieldName>( | |
| validatorType: T, | |
| ...values: Parameters<ValidatorRules[T]> | |
| ): ValidatorResult => { | |
| const errorValidator = () => ({ | |
| isValid: false, | |
| message: "유효성 체크가 정의되지 않았습니다.", | |
| }); | |
| const validator = | |
| (validators[validatorType] as ( | |
| ...args: Parameters<ValidatorRules[T]> | |
| ) => ValidatorResult) || errorValidator; | |
| const { isValid, message } = validator(...values); | |
| return { isValid, message }; | |
| }; | |
| export const validateInput = <T extends FormFieldName>( | |
| validatorType: T, | |
| ...values: ValidatorParams[T] | |
| ): ValidatorResult => { | |
| const validator = validators[validatorType] as ( | |
| ...args: ValidatorParams[T] | |
| ) => ValidatorResult; | |
| return validator(...values); | |
| }; | |
| }; |
질문에 대한 답변
본문 내에 질문 주신 부분 코멘트로 답변드렸습니다! |
요구사항
기본
심화
주요 변경사항
배포링크
https://sprint-misson8-suminbae.netlify.app/
스크린샷
멘토에게