리액트를 쓸 때는 useState가 있으니까 동적으로 데이터를 받아와서 뿌려줄 때 데이터가 변하는 경우가 있을 때(ex. filter기능) setState안에 새로운 데이터를 넣어주기만 하면 자동으로 데이터가 변했다.
그래서 아무 생각 없이 편하게 이걸 쓰기만 해왔다.
이번에 바닐라 자바스크립트로 작은 미니 게임을 만드는데 필터 기능을 추가하다가 데이터 변환이 있을 때 화면 UI를 어떻게 바꿔주지 하는 새로운 고민에 빠졌다.
내가 구현하고자 하는것은 전체 아이템이 화면에 뿌려진 상태에서 특정 필터링 버튼을 클릭하면 그 필터링에 해당되는 아이템만 보여지도록 하는 것이다.
현재 모든 아이템 데이터가 화면에 로드 되어진 상태이다.
이제 저기서 Blue
라고 적힌 버튼을 누르면 아이템 중 Blue색상만 필터링 되어 보여지도록 구현하려고 한다.
처음에 구현한 코드는 아래와 같다.
function filterBlueItem() {
loadBlueItemData().then((items) => {
items.map((item) => shopItemList.append(makeShopListItem(item)));
});
}
blueButton.addEventListener('click', filterBlueItem);
이때까지의 나는 바보같이.. 리액트 state의 상태만 생각하고 바닐라 자바스크립트에서의 동작은 전혀 예상도 못한채 Blue
버튼을 클릭했다.
대체 왜 블루 컬러로 필터링 되어서 나올거라고 생각했는지는 모르겠지만 결과화면은 다음과 같았다.
이미 로드 된 전체 아이템 밑에 필터링된 데이터가 붙는 것이었다.
순간 아 맞다 이거 리액트 아니지.. 라는 생각과 그럼 바닐라 자바스크립트에서는 어떻게 구현해야하지 하는 2가지 생각이 동시에 들었다.
일단 처음으로 생각난건 remove 근데 ㅋㅋ remove를 쓰니까 해당 노드 자체가 없어져버리더라구(..당연?)
function filterBlueItem() {
shopItemList.remove();
loadBlueItemData().then((items) => {
items.map((item) => shopItemList.append(makeShopListItem(item)));
});
}
당황해서 무슨일이 일어난거야 하고 개발자 도구의 element탭을 살펴봤더니 remove메서드 달아준 노드 자체가 없어져벌임.....
그래서 구글링해보니 메서드 달아준 노드를 없애는 거더라
흠 그러면 또 뭐가 있을까,, 그럼 childNode 뭐 이런거 삭제해주는 게 있지 않을까? 하고 찾아봤음. removeChild 메서드를 찾음 써봄
function filterBlueItem() {
shopItemList.removeChild(document.querySelector('.item'));
loadBlueItemData().then((items) => {
items.map((item) => shopItemList.append(makeShopListItem(item)));
});
}
그랬더니 이게 분명 화면에 데이터가 바뀌는데 아무리 버튼을 연타해도 사라지질 않는거다. 처음에 한 생각은 아 지워야 할 데이터가 하나가 아닌데 하나만 넣어줘서 (document.쿼리 어쩌고로 하나 넣어준 부분을 생각하고) 한꺼번에 안지워지는건가? 라는 생각에 document.querySelectorAll
을 쓰면 되는구나!! 하고 했는데 당연히 안됨.
뭐지 싶어서 일단 필터링 된 데이터를 추가하는 부분을 잠시 주석 처리하고 removeChild 메서드만 실행시켜보니 아 ... 이게 노드를 한꺼번에 지워주는게 아니고 하나씩 삭제해주는거였구나..^^
그래서 다른 키워드로 구글링을 했다. clearchildren at once js
그리고 for문을 써봐라, while문을 써봐라 라는 스택오버플로우의 글도 봤는데 난 여기서 for문을 돌리기가 싫은거다!
그리고 마지막으로 찾은 글에서 innerHTML = '';
은 어떻겠니? 라는 걸 봄.
아 근데 사실 이거 너무 치트키인가 싶고 이래도 되는건가 싶은 생각이 살짝 들었다.
네 그리고 성공했습니다.
function filterBlueItem() {
shopItemList.innerHTML = '';
loadBlueItemData().then((items) => {
items.map((item) => shopItemList.append(makeShopListItem(item)));
});
}
깔끔하죠?
사실 더 좋은 방법이 있을텐데(아마..) 내가 아직 모르는 걸거다.
자바스크립트에서 모듈로 저 makeShopListItem을 뺀 다음에 if문으로 버튼이 클릭할 때마다 다른 데이터를 보내주면..? 맞음 리액트 props 생각함..
나중에 리액트나 넥스트로 다시 만들어봐야지
이 부분 만들면서 리액트가 얼마나 편리한가 다시 한 번 감사하는 시간을 가졌다. 끝-!
react에서는 어떻게 setState로 state만 바꿨는데 화면이 자동으로 바뀔까요? 답은 공식문서에 나와있어요! react는 state가 바뀔때마다 리렌더링을 한다고 합니다.
그럼 어떻게 그것을 가능하게 할까요?
바로 virtual DOM을 이용해서 그것을 구현합니다. react 내부에서 virtual DOM끼리 비교하고 다른 부분을 실제 DOM에 반영하게 됩니다. 바로 선언형 UI를 구현할 수 있도록 해주죠. 즉, 개발자가 어떻게 UI에 변경사항을 반영할지 걱정할 필요가 없이 데이터(상태)를 보여주는 것에 집중할 수 있는 것이죠. (선언형 UI에 대해서 찾아보세용. 참고 - https://flutter-ko.dev/docs/get-started/flutter-for/declarative)
React에서 렌더링이 무엇인지 생각해보면 좋을 것 같아요! 우아한 형제들 기술 블로그에 React를 직접 구현해보는 글이 있는데 그것도 링크 남겨드릴게요:)
<참고>
https://techblog.woowahan.com/8311/
https://beta.reactjs.org/learn/render-and-commit
https://ko.reactjs.org/docs/reconciliation.html