오늘은 코드를 개선해보며 놀아보도록 한다.
일단 Camp 진행하면서 만들어진 코드를 재 구성해본다.
여기서 생각해볼 점은 Error 및 Value 값을 어떻게 간편하게 만드는지이다.
일단 상태값을 다음처럼 만들었다.
function Board() {
const router = useRouter();
const [ createBoard ] = useMutation(CREATE_BOARD)
const [ values, setValues ] = useState({
writer: "",
password: "",
title: "",
contents: "",
});
const [ errors, setErrors ] = useState({
writer: "",
password: "",
title: "",
contents: "",
});
api 상에 필수입력 만 받게끔 state 를 설정했으며, 일일히 하기 귀찮아서 Object 로 property 를 주었다.
다음은 onChangeValue 라는 함수 부분이다.
이부분은 setValue 를 통해 변경되어야 하는 부분이다.
처음 작성했던 이 코드는 정말 내가 봐도 지독할 정도로 지저분했다.
const onChangeValues = e => {
const name = e.target.name;
const value = e.target.value;
if (name === "writer") {
setValues(values => ({
...values,
writer: value,
}));
setErrors( errors => ({
...errors,
writer: "",
}))
if (!value) setErrors( errors => ({
...errors,
writer: "이름을 적어주세요."
}));
}
if (name === "password") {
setValues(values => ({
...values,
password: value,
}));
setErrors( errors => ({
...errors,
password: "",
}))
if (!value) setErrors( errors => ({
...errors,
password: "비밀번호를 적어주세요."
}));
}
if (name === "title") {
setValues(values => ({
...values,
title: value
}));
setErrors( errors => ({
...errors,
title: "",
}))
if (!value) setErrors( errors => ({
...errors,
title: "제목을 적어주세요."
}));
}
if (name === "contents") {
setValues(values => ({
...values,
contents: value,
}));
setErrors( errors => ({
...errors,
contents: "",
}))
if (!value) setErrors( errors => ({
...errors,
contents: "내용을 적어주세요."
}));
}
if (name === "youtubeUrl") {
setValues(values => ({
...values,
youtubeUrl: value,
}))
}
}
이 onChange 는 다음의 InputWrapper 에서 사용된다.
<InputWrapper
type="text"
labelName="작성자"
placeholder="이름을 적어주세요."
value={values.writer}
onChange={onChangeValues} // <-- 이부분
name="writer"
id="writer"
error={errors.writer}
required
/>
이녀석을 좀더 단순하게 만들어보자.
input 상에서 공통된 부분이 무엇인지 살펴본다.
확인해 보니 state 의 객체의 프로퍼티가 Input 의 name 과 동일하다는 것이다.
다음을 보면 명확해 진다.
const [ values, setValues ] = useState({
writer: "", // <-- input name
password: "",
title: "",
contents: "",
});
const [ errors, setErrors ] = useState({
writer: "", // <-- input name
password: "",
title: "",
contents: "",
});
/*** 중략 ***/
<InputWrapper
type="text"
labelName="작성자"
placeholder="이름을 적어주세요."
value={values.writer}
onChange={onChangeValues}
name="writer" // <-- 이부분
id="writer"
error={errors.writer}
required
/>
위의 state 를 관리하기 위해 spread 문법을 사용하여 state를 set 했다
spread 문법은 다음처럼 ...obj 를 사용하여 요소하나하나를 전개한다.
전개 연산자 라고도 불린다.
보통 spread operator 를 사용하는 이유는 기존의 상태를 변경시키지 않고 새로운 객체를 생성하기 위해 많이 사용된다. 이러한 성질을 불변성(immutable) 이라 한다.
/*** 출처 MDN ***/
const obj1 = { foo: 'bar', x: 42 };
const obj2 = { foo: 'baz', y: 13, x: 12 };
const clonedObj = { ...obj1 };
// { foo: "bar", x: 42 } <-- 전개되어 새로운 객체를 생성한다.
const mergedObj = { ...obj1, ...obj2 };
// { foo: "baz", x: 12, y: 13 } <-- 전개되어 합친후 새로운 객체를 생성한다.
// 이때 중복되는 property 는 마지막에 오는
// property 의 값을 가진다.
react 의 state 는 오직 setState 를 사용해서 변경되어야 하므로,
기존 state 의 변경 위험성을 없애기 위해 spread 문법을 통해 새로운 객체로 만든이후
setState 한다.
이 코드에서는 state 를 Object 를 사용했으므로, spread 연산자를 사용하여, setState 한다.
자 그럼 input name 을 이용할 수 있도록 onChange 함수를 새로 작성해본다.
const onChangeValues = (e) => {
}
이렇게 만들어진 onChange 함수는 event 객체를 받아 target 프로퍼티를 사용하면 해당 input 요소에 접근가능하다.
여기서 e 는 event 객체인것을 참고하자.
이렇게 접근한 input 요소의 value 와 name 을 받아오자.
const onChangeValues = (e) => {
const value = e.target.value
const name = e.target.name
}
이렇게 value변수에는 input 요소의 value 를 담고,
name 변수에는 input 요소의 name 을 담는다.
이렇게 받은 name 을 이전에는 if 문을 써서 일일히 비교하였는데, 굉장히 비효율적이다.
생각해보면 if 를 쓰지 않고 name 은 확실하게 식별이 가능하니 다음처럼 직접 setValues 에 접근 가능하다.
/** 개선된 코드 ***/
const onChangeValues = (e) => {
const value = e.target.value
const name = e.target.name
setValues( values => ({
...values,
[name]: value,
}));
}
이것으로, 일일히 if 문 구현없이 모든 name 값을 쉽게 변경 가능하게 되었다.
앞전의 코드를 보면 setValues 를 설정하기 위해 일일히 if문을 쓴것과 많이 비교된다.
다음은 setValues 만을 사용하여 if 문으로 구현했던 코드이다.
다음을 보면 확실하게 비교될것이다.
/** 이전 아무생각없이 작성했던 코드 ***/
const onChangeValues = e => {
const name = e.target.name;
const value = e.target.value;
if (name === "writer") {
setValues(values => ({
...values,
writer: value,
}));
setErrors( errors => ({
...errors,
writer: "",
}))
if (!value) setErrors( errors => ({
...errors,
writer: "이름을 적어주세요."
}));
}
if (name === "password") {
setValues(values => ({
...values,
password: value,
}));
setErrors( errors => ({
...errors,
password: "",
}))
if (!value) setErrors( errors => ({
...errors,
password: "비밀번호를 적어주세요."
}));
}
if (name === "title") {
setValues(values => ({
...values,
title: value
}));
setErrors( errors => ({
...errors,
title: "",
}))
if (!value) setErrors( errors => ({
...errors,
title: "제목을 적어주세요."
}));
}
if (name === "contents") {
setValues(values => ({
...values,
contents: value,
}));
setErrors( errors => ({
...errors,
contents: "",
}))
if (!value) setErrors( errors => ({
...errors,
contents: "내용을 적어주세요."
}));
}
if (name === "youtubeUrl") {
setValues(values => ({
...values,
youtubeUrl: value,
}))
}
}
이렇게 setValues 를 개선해보았다면, setError 역시 같은 방식으로 개선 가능할 것이다.
error 설정을 위해 코드를 개선해본다.
이전에는 input 에 error 설정을 위해 다음처럼 코드작성 했다.
const errorHandler = () => {
if (!values.writer) {
setErrors( errors => ({
...errors,
writer: "이름을 적어주세요."
}));
}
if (!values.password) {
setErrors( errors => ({
...errors,
password: "비밀번호를 적어주세요."
}));
}
if (!values.title) {
setErrors( errors => ({
...errors,
title: "제목을 적어주세요."
}));
}
if (!values.contents) {
setErrors( errors => ({
...errors,
contents: "내용를 적어주세요."
}));
}
}
이 코드는 input 에 내용이 없다면 내용을 적어달라는 문구를 생성한다.
이렇게 생성되면 error 의 내용을 전달하여 input 아래에 error 문구가 나오게 된다.
이것도 if 문으로 인해 지저분해 졌다
다시 개선해 본다.
여기서 errorHandler 는 onChangeValues 내부에 사용되는 함수이므로,
onChangeValues 의 name 과 value 값을 인자로 받는다.
const errorHandler = (name, value) => {
if (value === "") { // 값이 비어있다면 아래의 setErrors 를 실행
setErrors( errors => ({
...erros,
[name]: ??, // <-- 공통되는 값이 아니다.....
}))
}
}
문제가 발생했다..
input 의 name 및 value 는 해당요소에서 가져오기만 하면 처리 가능하지만,
error 발생시 작성되는 값 은 공통적이지 않다.
이를 해결하기 위해, 어쩔수 없이 분기문을 가진 함수를 작성한다.
이때 name 을 parameter 로 받아, 만약 name 의 값과 같다면 원하는 값이 return 되도록 한다.
이 함수의 명을 returnErrorValue 라고 할것이다.
const returnErrorValue = (name) => {
if (name === "writer") return "이름을 입력해 주세요"
if (name === "title") return "제목을 입력해 주세요"
if (name === "contents") return "내용을 입력해 주세요"
if (name === "password") return "비밀번호를 입력해 주세요"
}
이제 이렇게 만들어진 함수를 다음처럼 값으로써 호출한다.
/*** 개선된 코드 ***/
const errorHandler = (name, value) => {
if (value === "") { // 값이 비어있다면 아래의 setErrors 를 실행
setErrors( errors => ({
...erros,
[name]: returnErrorValue(name),
}))
}
}
이전의 코드를 다시 살펴보면 얼마나 코드가 짧막해 졌는지 확인가능하다.
/*** 개선하기 전 생각없이 작성한 코드 ***/
const errorHandler = () => {
if (!values.writer) {
setErrors( errors => ({
...errors,
writer: "이름을 적어주세요."
}));
}
if (!values.password) { emptyError(name)
setErrors( errors => ({
...errors,
password: "비밀번호를 적어주세요."
}));
}
if (!values.title) {
setErrors( errors => ({
...errors,
title: "제목을 적어주세요."
}));
}
if (!values.contents) {
setErrors( errors => ({
...errors,
contents: "내용를 적어주세요."
}));
}
}
이제 만들어진 코드를 다음처럼 onChangeValues 에서 사용한다.
const onChangeValues = (e) => {
const value = e.target.value
const name = e.target.name
setValues( values => ({
...values,
[name]: value,
}));
errorHandler(name, value)
}
이렇게 onChange 시에 생성되는 value 및 error 를 줄여보았다.
하지만 문제가 발생했다.
input 에 값이 없다면, error 가 나타나고, input 에 값을 다시 입력하면 error 가 사라져야 한다.
하지만 input 에 값을 입력해도 사라지지 않는다..
다음처럼 말이다...

이를 해결하기 위해서는 해당하는 input 의 error 를 비어진 값으로 초기화 해준후,
error 를 재평가 해주면 될것 같다.
그래서 emptyError 를 생성한후, 이전 패턴과 마찬가지로 대괄호 표기법 을 사용하여 해당하는 error 의 값 을 비워준다.
const setEmptyError = (name) => {
setErrors(errors => ({
...errors,
[name]: "", // name 에 해당하는 errors 의 value 값을
// 빈문자열로 대체한다.
})
}
이제 onChangeValues 에 해당 함수를 호출해준다.
const onChangeValues = (e) => {
const value = e.target.value
const name = e.target.name
setEmptyError(name) // name 에 해당하는 error 를 빈문자열로 초기화 한다.
setValues( values => ({ // name 에 해당하는 value 의 값을 할당한다.
...values,
[name]: value,
}));
errorHandler(name, value) // name 에 해당하는 values를 평가하여
// value 가 빈문자열이면, error 를 발생시킨다.
}
이렇게 위의 코드가 만들어진다.
각 기능을 하나로 합치는것이 아니라 기능별로 나누어 구현한다면, 코드의 가독성이 높아지고 기능이해가 더 쉬워진다.
다시한번 생각없이 작성한 코드를 보면 더 확실히 느껴진다.
/** 이전 아무생각없이 작성했던 코드 ***/
const onChangeValues = e => {
const name = e.target.name;
const value = e.target.value;
if (name === "writer") {
setValues(values => ({
...values,
writer: value,
}));
setErrors( errors => ({
...errors,
writer: "",
}))
if (!value) setErrors( errors => ({
...errors,
writer: "이름을 적어주세요."
}));
}
if (name === "password") {
setValues(values => ({
...values,
password: value,
}));
setErrors( errors => ({
...errors,
password: "",
}))
if (!value) setErrors( errors => ({
...errors,
password: "비밀번호를 적어주세요."
}));
}
if (name === "title") {
setValues(values => ({
...values,
title: value
}));
setErrors( errors => ({
...errors,
title: "",
}))
if (!value) setErrors( errors => ({
...errors,
title: "제목을 적어주세요."
}));
}
if (name === "contents") {
setValues(values => ({
...values,
contents: value,
}));
setErrors( errors => ({
...errors,
contents: "",
}))
if (!value) setErrors( errors => ({
...errors,
contents: "내용을 적어주세요."
}));
}
if (name === "youtubeUrl") {
setValues(values => ({
...values,
youtubeUrl: value,
}))
}
}
/*** 개선하기 전 생각없이 작성한 코드 ***/
const errorHandler = () => {
if (!values.writer) {
setErrors( errors => ({
...errors,
writer: "이름을 적어주세요."
}));
}
if (!values.password) { emptyError(name)
setErrors( errors => ({
...errors,
password: "비밀번호를 적어주세요."
}));
}
if (!values.title) {
setErrors( errors => ({
...errors,
title: "제목을 적어주세요."
}));
}
if (!values.contents) {
setErrors( errors => ({
...errors,
contents: "내용를 적어주세요."
}));
}
}
이제 개선한 코드이다
// error 를 초기화 시켜주는 함수
const setEmptyError = (name) => {
setErrors(errors => ({
...errors,
[name]: "",
}))
}
// error 의 value 를 리턴해주는 함수
const returnErrorValue = (name) => {
if (name === "writer") return "이름을 입력해 주세요"
if (name === "title") return "제목을 입력해 주세요"
if (name === "contents") return "내용을 입력해 주세요"
if (name === "password") return "비밀번호를 입력해 주세요"
}
// value 가 빈문자열이면 error 의 값을 할당해주는 함수
const errorHandler = (name, value) => {
if (value === "") {
setErrors( errors => ({
...errors,
[name]: returnErrorValue(name),
}))
}
}
// input 의 값이 변경될때, 사용되는 event handler
const onChangeValues = (e) => {
const value = e.target.value
const name = e.target.name
setEmptyError(name)
setValues( values => ({
...values,
[name]: value,
}));
errorHandler(name, value)
}
이렇게 지속적으로 코드를 개선시켜 나가는 연습을 하는것이 좋을 것 같다.