js 함수 실행 방법이 여러가진데 그걸 배우는 와중에 템플릿 리터럴도 배웠다. 내 개인프로젝트할 때 사용했어서 뭔지는 대충 안다.
템플릿 리터럴
function a(index){
console.log(`a실행, index : ${index}`);
}
결과 :
a실행 : index : undefind
${}
이거만 보면 짜증이 나네.. 여튼 이렇게 활용할 수 있다.
프론트엔드에서는 HTML을 데이터와 결합해서 DOM을 다시 그려야 하는 일이 빈번하기 때문에, 템플릿을 좀 더 쉽게 편집하고 작성해야 할 필요가 있어서, 이러한 기능이 추가되었다고 한다.
내 개인 프로젝트에서 애를 먹었던 이유는 이게 프로젝트 내에서 한번 해석 하고, js에서 한번 해석 하는 과정에서 두번 해석을 하려고 해서 null
을 반환하는 문제가 있었다. 때문에 ${'${}'}
이런식으로 이중으로 감싸야하는 이슈가 있었기 때문이다. (정확한 이유는 아닐지도 모른다. 구글링해서 해결 방법을 찾는 와중에 이런 방법이 있어서 써보니까 해결 되길래 원인이 이런게 아닐까 짐작하는것.)
let a = 1;
let b = 2;
let c = "1 + 2는?";
let sum = `${c} ${a+b}`;
console.log(str); //1 + 2는? 3
여튼 템플릿 리터럴을 간단하게 사용하면 이런 느낌이다.
본론으로 가서 js를 사람들이 싫어하는 이유중 하나. 함수실행 방식이 너무 많다는 것.
console.clear();
function a(index) {
console.log(`a 실행, index : ${index}`);
}
// function b(){
// a();
// }
// const b = function(){
// a();
// }
// const b = () => {
// a();
// }
const index = 2;
// const b = () => a(index);
const b = () => {
a(index);
};
const c = a;
b();
c();
이게 결과가 다 a함수를 실행시킨 결과가 나온다. 모두가 함수 실행 방식이다. 눈에 익혀두자.
여튼 리액트에서 리터럴을 왜 쓰는지는 살짝 알 것 같긴하다. 내 프로젝트에서도 쓴 이유가 spotify card를 생성할 때 변수가 html에 들어가니까 엄청 편했던 것 같다. 그런 느낌이니까 쓰지 않을까.
컴포넌트 리팩토링
우선 전의 포스트의 정수 기록 앱을 컴포넌트 단위로 리팩토링하는 것으로 시작했다.
console.clear();
import React, { useState } from "https://cdn.skypack.dev/react@18";
import ReactDOM from "https://cdn.skypack.dev/react-dom@18";
const NumberRecorderForm = ({
number,
setNumber,
saveRecord,
clearNumbers
}) => {
return (
<>
<div>
<span>숫자 : {number}</span>
<button>취소</button>
</div>
<div>
<button
onClick={() => {
setNumber(number + 1);
}}
>
증가
</button>
<button
onClick={() => {
setNumber(number - 1);
}}
>
감소
</button>
<button onClick={() => saveRecord()}>기록</button>
<button onClick={clearNumbers}>전체기록삭제</button>
</div>
</>
);
};
const NumberRecorderList = ({ recordNums, setRecordNums, removeNumber }) => {
return (
<>
<div>
{recordNums.length == 0 ? (
<div>기록 없음</div>
) : (
<>
<h3>기록</h3>
<ul>
{recordNums.map((recordNum, index) => (
<li key={index}>
<span>
{index + 1}번 : {recordNum}
</span>
<button onClick={() => removeNumber(index)}>삭제</button>
</li>
))}
</ul>
</>
)}
</div>
</>
);
};
const App = () => {
const [number, setNumber] = useState(0);
const [recordNums, setRecordNums] = useState([10, 20, 30]);
const saveRecord = () => {
setNumber(0);
setRecordNums([...recordNums, number]);
};
const clearNumbers = () => {
setRecordNums([]);
};
const removeNumber = (index) => {
setRecordNums(recordNums.filter((_, _index) => _index != index));
};
return (
<>
<NumberRecorderForm
number={number}
setNumber={setNumber}
saveRecord={saveRecord}
clearNumbers={clearNumbers}
/>
<NumberRecorderList
recordNums={recordNums}
setRecordNums={setRecordNums}
removeNumber={removeNumber}
/>
</>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
잘 살펴 보면 메서드가 분리되었다는 것을 확인할 수 있고 App 메서드의 return 에서 태그를 잘 살펴보면 매개변수를 넘겨주면서 제 기능을 하게 만들어주고 있는 것도 확인 할 수 있다. 여기서 매개변수를 어떻게 받는가 조금 헷갈릴 수도 있는데,
const NumberRecorderForm = ({
number,
setNumber,
saveRecord,
clearNumbers
}) => {
/////////////////////////////////////////////
return (
<>
<NumberRecorderForm
number={number}
setNumber={setNumber}
saveRecord={saveRecord}
clearNumbers={clearNumbers}
/>
<NumberRecorderList
recordNums={recordNums}
setRecordNums={setRecordNums}
removeNumber={removeNumber}
/>
</>
);
이렇게 떼어내서 보면 조금 더 직관적으로 보인다. 분리하면서 NumberRecorderForm
과 NumberRecorderList
에서 원래 사용하던 변수들을 쓸 수 없게 되어서 App
메서드 안에 있던걸 매개변수로 다시 넣어주는 작업이라고 생각하면 쉽다.
여기서 li를 또 따로 빼서 한번 더 리팩토링.
const NumberRecordListItem({index, recordNum, removeNumber}) => {
return (
<>
<li key={index}>
<span>
{index + 1}번 : {recordNum}
</span>
<button onClick={() => removeNumber(index)}>삭제</button>
</li>
</>
);
};
const NumberRecorderList = ({ recordNums, setRecordNums, removeNumber }) => {
return (
<>
<div>
{recordNums.length == 0 ? (
<div>기록 없음</div>
) : (
<>
<h3>기록</h3>
<ul>
{recordNums.map((recordNum, index) => (
<NumberRecordListItem
index={index}
recordNum={recordNum}
removeNumber={removeNumber}
))}
</ul>
</>
)}
</div>
</>
);
};
그 부분만 따로 빼서 보면 이런 결과다. 결국 똑같은 일 한거다. 분리했고, 또 그에 필요한 매개변수를 넘겨주는 일이다. 분리한 곳에는 해당 메서드를 태그로 불러오고... 똑같다.
이걸 활용해서 수정버튼으로 index 값을 수정하는 코드를 만들어보았다.
const NumberRecorderList = ({ recordNums, setRecordNums, removeNumber, modifyNumber, number}) => {
return (
<>
<div>
{recordNums.length == 0 ? (
<div>기록 없음</div>
) : (
<>
<h3>기록</h3>
<ul>
{recordNums.map((recordNum, index) => (
<li key={index}>
<span>
{index + 1}번 : {recordNum}
</span>
<button onClick={() => removeNumber(index)}>삭제</button>
<button onClick={() => modifyNumber(index, number)}>수정</button>
</li>
))}
</ul>
</>
)}
</div>
</>
);
};
//////////////////
const modifyNumber = (index, newValue) => {
setRecordNums(
recordNums.map((recordNum, _index) =>
_index === index ? newValue : recordNum
)
);
/////////////////
<NumberRecorderList
recordNums={recordNums}
setRecordNums={setRecordNums}
removeNumber={removeNumber}
modifyNumber={modifyNumber}
number={number}
/>
길어서 축약했다.
내 방식이 틀렸을 수도 있는데, NumberRecorderForm
에서 증가 감소로 입력한 값을 받아서 수정하는 걸 원해서 number
값까지 받아왔고, modifyNumber
메서드를 생성해서 수정 버튼으로 처리했다.
여기서 해보면 된다. (솔직히 제 기능하는데 뭐 틀리고 자시고가 있겠냐만은..)
난 이렇게 했는데 강사님은 input
을 사용하시려고 하는 것 같았다.
리액트 input 관련 글을 참고해서 구현해보자.
const [inputValues, setInputValues] = useState([...recordNums]);
useEffect(() => {
setInputValues([...recordNums]);
}, [recordNums]);
const handleInputChange = (index, value) => {
const newInputValues = [...inputValues];
newInputValues[index] = value;
setInputValues(newInputValues);
};
return (
<>
<div>
{recordNums.length == 0 ? (
<div>기록 없음</div>
) : (
<>
<h3>기록</h3>
<ul>
{recordNums.map((recordNum, index) => (
<li key={index}>
<span>
{index + 1}번 : {recordNum}
</span>
<button onClick={() => removeNumber(index)}>삭제</button>
<input
type="number"
value={inputValues[index]}
onChange={(e) => handleInputChange(index, e.target.value)}
/>
<button
onClick={() => modifyNumber(index, parseInt(inputValues[index]))}
>
수정
</button>
</li>
))}
</ul>
</>
)}
</div>
</>
);
};
이렇게 해봤따
여기서 해봐라!
강사님은 article로 예를 들어서 설명부터 하셨다.
console.clear();
import React, { useState } from "https://cdn.skypack.dev/react@18";
import ReactDOM from "https://cdn.skypack.dev/react-dom@18";
const ArticleDetail = ({ id }) => {
const [editModeStatus, setEditModeStatus] = useState(false);
if (editModeStatus) {
return (
<>
<form>
<div>수정모드</div>
<span>번호</span>
<span>{id}</span>
<div>
<span>제목 : </span>
<input type="text" placeholder="제목 입력" />
</div>
<div>
<span>내용 : </span>
<input type="text" placeholder="내용 입력" />
</div>
<div>
<button type="submit">수정완료</button>
<button type="button" onClick={() => {setEditModeStatus(false)}}>수정취소</button>
</div>
</form>
</>
);
}
const title = "1번 글 제목";
const body = "1번 글 내용";
return (
<>
<h3>{id}번 게시글</h3>
<div>제목 : {title}</div>
<div>내용 : {body}</div>
<button onClick={() => {setEditModeStatus(true)}}>수정</button>
<hr />
</>
);
};
const App = () => {
return (
<>
<ArticleDetail id={1} />
</>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
useState
를 수정 버튼으로 true, 수정 취소 버튼으로 false로 바꾼다. 이는 수정모드와 글을 보는 모드로 바꾼다. 아직 수정기능을 도입하지는 않았고, 우선 이게 어떤 방식으로 이렇게 되는건지 살펴보면 된다.
여기서 핵심은const [editModeStatus, setEditModeStatus] = useState(false);
, 여기와<button type="button" onClick={() => {setEditModeStatus(false)}}>수정취소</button>
여기,<button onClick={() => {setEditModeStatus(true)}}>수정</button>
그리고 여기다.
직접 어떻게 돌아가는지 보면
버튼을 눌러보면 된다. 전환되는 이유는 버튼을 통해 useState
의 상태를 변화시킨다고 보면 된다. 그럼 여기서 의문이 왜 1번 게시글의 제목 내용이 사라지는가?에 대한 대답은 if문을 통해서 return 시키기 때문이라고 이해하면 된다.
이제 이걸 아까 우리 코드에 적용시켜보면 된다.
일단 내가 만든 수정은 뒤로하고, 저 상태 자체를 그대로 옮겨서 구현해보면.
const NumberRecorderListItem = ({ index, recordNum, removeNumber }) => {
const [editModeStatus, setEditModeStatus] = useState(false);
const readView = (
<>
<button onClick={() => setEditModeStatus(true)}>수정</button>
</>
);
const editView = (
<>
<input type="number" placeholder="숫자 입력" min="0" value={recordNum}/>
<button onClick={() => setEditModeStatus(false)}>수정 완료</button>
<button onClick={() => setEditModeStatus(false)}>수정 취소</button>
</>
);
return (
<>
<li key={index}>
<span>
{index + 1}번 : {recordNum}
</span>
<button onClick={() => removeNumber(index)}>삭제</button>
{editModeStatus ? editView : readView}
</li>
</>
);
};
이런 코드가 된다. setEditModeStatus
로 상태관리를 하고, {editModeStatus ? editView : readView}
삼향연산자로 editView
를 보여주느냐 readView
를 보여주느냐를 결정한다.
복잡해 보일 수 있는데 결국 아까 게시글이랑 같은 코드다.
아직 수정은 구현하지 않았다. value={recordNum}
에 문제가 있는지 값이 바뀌지 않는 문제와 아직 수정을 완료하는 로직도 구현이 안됐다.
우선 value={recordNum}
는 useState
를 사용하지 않았기 때문에 고정값이 되어서 수정이 되지 않는 것이다.
const [inputNumberValue, setInputNumberValue] = useState(recordNum);
로 위에 먼저 선언해주고 사용하면 된다.
그리고 아까 내가 만든 수정에서도 사용했던 onchange
를 input
태그 안에서 사용해주면 된다.
<input
type="number"
placeholder="숫자 입력"
value={inputNumberValue} //여기 inputNumberValue를 넣어주면 됨!
onChange={(e) => setInputNumberValue(e.target.value)}
/>
onChange={(e) => setInputNumberValue(e.target.value)}
이걸 사용해서 input
태그 값이 바뀌는 것을 허용하면 된다.
그럼 이제 준비준비는 끝났으니 modify 메서드를 만들어주면 된다.
여튼 종합한 코드를 보면
const NumberRecorderListItem = ({
index,
recordNums,
recordNum,
removeNumber,
setRecordNums
}) => {
const [inputNumberValue, setInputNumberValue] = useState(recordNum);
const [editModeStatus, setEditModeStatus] = useState(false);
const modifyNumber = () => {
if (recordNum == inputNumberValue) {
setEditModeStatus(false);
return;
}
if (!inputNumberValue) {
setEditModeStatus(false);
return;
}
setRecordNums(
recordNums.map((_number, _index) =>
_index == index ? inputNumberValue : _number
)
);
setEditModeStatus(false);
};
const readView = (
<>
<button onClick={() => setEditModeStatus(true)}>수정</button>
</>
);
const editView = (
<>
<input
type="number"
placeholder="숫자 입력"
min="0"
value={inputNumberValue}
onChange={(e) => setInputNumberValue(e.target.value)}
/>
<button onClick={modifyNumber}>수정 완료</button>
<button onClick={() => setEditModeStatus(false)}>수정 취소</button>
</>
);
return (
<>
<li key={index}>
<span>
{index + 1}번 : {recordNum}
</span>
<button onClick={() => removeNumber(index)}>삭제</button>
{editModeStatus ? editView : readView}
</li>
</>
);
};
const NumberRecorderList = ({ recordNums, setRecordNums, removeNumber }) => {
return (
<>
<div>
{recordNums.length == 0 ? (
<div>기록 없음</div>
) : (
<>
<h3>기록</h3>
<ul>
{recordNums.map((recordNum, index) => (
<NumberRecorderListItem
recordNum={recordNum}
recordNums={recordNums}
index={index}
removeNumber={removeNumber}
setRecordNums={setRecordNums}
/>
))}
</ul>
</>
)}
</div>
</>
);
};
이렇게 된다.
실행하는 모습은
이렇다.
'Coding History' 카테고리의 다른 글
JS 리액트 useEffect (0) | 2024.09.27 |
---|---|
JS 리액트 TODO LIST (2) | 2024.09.26 |
리액트! (JS Filter) (1) | 2024.09.24 |
chat 웹앱 수업. (0) | 2024.09.11 |
VO, DTO, Entity의 차이점 (0) | 2024.09.09 |