코드 개선해보기

Jhoon·2023년 1월 15일
0
post-thumbnail

코드를 개선해 보자

오늘은 코드를 개선해보며 놀아보도록 한다.
일단 Camp 진행하면서 만들어진 코드를 재 구성해본다.

여기서 생각해볼 점은 ErrorValue 값을 어떻게 간편하게 만드는지이다.
일단 상태값을 다음처럼 만들었다.

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 를 설정했으며, 일일히 하기 귀찮아서 Objectproperty 를 주었다.

다음은 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 의 객체의 프로퍼티가 Inputname 과 동일하다는 것이다.

다음을 보면 명확해 진다.

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
/>

Spread 문법

위의 state 를 관리하기 위해 spread 문법을 사용하여 stateset 했다
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 의 값을 가진다.

reactstate 는 오직 setState 를 사용해서 변경되어야 하므로,
기존 state 의 변경 위험성을 없애기 위해 spread 문법을 통해 새로운 객체로 만든이후
setState 한다.

이 코드에서는 stateObject 를 사용했으므로, spread 연산자를 사용하여, setState 한다.

Input Name 을 이용하여 코드를 줄인다

자 그럼 input name 을 이용할 수 있도록 onChange 함수를 새로 작성해본다.


const onChangeValues = (e) => {
  
}

이렇게 만들어진 onChange 함수는 event 객체를 받아 target 프로퍼티를 사용하면 해당 input 요소에 접근가능하다.

여기서 eevent 객체인것을 참고하자.
이렇게 접근한 input 요소의 valuename 을 받아오자.


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 부분 개선하기

이전에는 inputerror 설정을 위해 다음처럼 코드작성 했다.

    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 문으로 인해 지저분해 졌다
다시 개선해 본다.

여기서 errorHandleronChangeValues 내부에 사용되는 함수이므로,
onChangeValuesnamevalue 값을 인자로 받는다.


const errorHandler = (name, value) => {
	if (value === "") { // 값이 비어있다면 아래의 setErrors 를 실행
      setErrors( errors => ({
          ...erros,
          [name]: ??, // <-- 공통되는 값이 아니다.....
      }))
    }
}

문제가 발생했다..
inputnamevalue 는 해당요소에서 가져오기만 하면 처리 가능하지만,
error 발생시 작성되는 은 공통적이지 않다.

이를 해결하기 위해, 어쩔수 없이 분기문을 가진 함수를 작성한다.
이때 nameparameter 로 받아, 만약 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 시에 생성되는 valueerror 를 줄여보았다.

하지만 문제가 발생했다.

input 에 값이 없다면, error 가 나타나고, input 에 값을 다시 입력하면 error 가 사라져야 한다.

하지만 input 에 값을 입력해도 사라지지 않는다..
다음처럼 말이다...

이를 해결하기 위해서는 해당하는 inputerror 를 비어진 값으로 초기화 해준후,
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)
    }

이렇게 지속적으로 코드를 개선시켜 나가는 연습을 하는것이 좋을 것 같다.

profile
익숙해지면 다 할수 있어!!

0개의 댓글