오늘은 코드를 개선해보며 놀아보도록 한다.
일단 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)
}
이렇게 지속적으로 코드를 개선시켜 나가는 연습을 하는것이 좋을 것 같다.