죽은 코드 살리기1
Motivation
- 기존 코드가 하드 코딩되어 있어서 로직을 바꿔보자는 생각을 했습니다.
- 약관 페이지는 변경될 가능성이 있으니, 로직을 따로 분리해보고자 했습니다.
기존 코드의 문제
const [agreements, setAgreements] = useState([false, false, false, false, false])
- 업데이트하는 로직도 하드코딩 되어있습니다.
- 에러 로직 처리하는 부분이 아주 이상하게 조건이 많았습니다.
useState 대신 useReducer를 사용한 이유
- 업데이트 로직을 분리하는 것이 로직을 이해하는 데 더 좋을 것으로 생각했습니다.
- 약관이 추가되거나 삭제가되는 경우,
initialState,State를 활용해 관리할 수 있습니다. - 업데이트 로직을 분리해서 테스트하기 쉽습니다..
코드 설명
TermValue
type TermValue =
| "isMoreThan14"
| "termOfService"
| "privacy"
| "privacyThirdParty"
| "marketing";
TermValue타입은 약관을 나타내는 타입입니다.input의name으로 들어갑니다.
Term
type Term = {
value: TermValue;
checked: boolean;
required: boolean;
};
Term은 하나의 약관이 갖고 있는 필드입니다.value: 약관을 구별해주는 역할을 합니다.checked: 체크가 되어있는지 여부를 나타냅니다.required: 필수 여부를 나타냅니다.
Action
type Action =
| {
type: TermValue;
payload: boolean;
} | {
type: "allAgreements";
payload: boolean;
}
| {
type: "reset";
};
Action은TermValue에 있는 것 외에allAgreements와reset이 있는데, 이것은 전체 선택과 값을 초기화하는 요구사항이 있기 때문입니다.
State
type State = Term[];
const initialState: State = [
{
value: "isMoreThan14",
checked: false,
required: true,
},
{
value: "termOfService",
checked: false,
required: true,
},
{
value: "privacy",
checked: false,
required: true,
},
{
value: "privacyThirdParty",
checked: false,
required: false,
},
{
value: "marketing",
checked: false,
required: false,
},
];
State타입은initialState를 정의하는 역할을 합니다.initialState는reducer에 넣을 초기값 입니다.
reducer
const reducer = (state: State, action: Action) => {
switch (action.type) {
case "allAgreements":
return state.map((agreement) => ({
...agreement,
checked: action.payload,
}));
case "reset":
return state.map((agreement) => ({ ...agreement, checked: false }));
default:
return state.map((agreement) =>
agreement.value === action.type
? { ...agreement, checked: action.payload }
: agreement
);
}
};
action.typeisallAgreements: 모든 필드를action.payload로 만들어 줍니다.reset: 모든 필드를false로 만들어 줍니다..- 기본적으로
action.type과 같은 필드를 찾아서action.payload로 업데이트 해줍니다.
useTerms
const useTerms = () => {
const [state, dispatch] = useReducer(reducer, initialState);
const onTermChange = (name: Action["type"], checked: boolean) =>
dispatch({ type: name, payload: checked });
const allAgreements =
state.filter((agreemenet) => !agreemenet.checked).length < 1;
const validateRequired = (terms: State) =>
terms
.filter((agreement) => agreement.required)
.every((agreement) => agreement.checked);
const reset = () => dispatch({ type: "reset" });
return {
agreements: state,
allAgreements,
onTermChange,
validateRequired,
reset,
};
};
onTermChange:dispatch를 이름만 바꿨다고 보면 됩니다.allAgreements: 전체 선택에 관한 로직 입니다.- 하나씩 눌러서 필드 전체의
checked가true이면 체크가 됩니다. - 필드 전체의
checked가true인 상태에서 하나라도false가 되면 체크가 해제됩니다.
- 하나씩 눌러서 필드 전체의
validateRequired:required가true인 필드는 전부 체크가 되어야 합니다.(checked: true)reset: 모든 필드의checked를 초기화 시킵니다.
조금 더 깔끔해진 모습을 볼 수 있습니다. 하지만 문제점이 있습니다. 그것은 required 정보를 State 쪽에서 건들여야한다는 점입니다. UI 단에서 required Props만 넘기도록 만든다면 더 관리하기 좋을 것 같습니다. 다음에는 이것을 Compound Component Pattern으로 리팩토링해보는 시간을 가져보겠습니다.