* 이 포스트는 학습 과정에서 그 내용을 기록한 글이기에 부정확한 정보가 포함될 수 있습니다.
따라서 해당 글은 참고용으로만 봐주시고 틀린 부분이 있다면 알려주시면 감사하겠습니다.
❗ 이 글은 react의 가상 DOM 작동과 새로고침/리렌더링의 연관성을 공부한 내용입니다. 미리 component ,state, props 등에 대한 지식이 있다는 것을 가정하고 작성합니다. 해당 내용을 잘 모르신다면 처음 부분을 제외하고 DOM 부분부터 보시면 됩니다.
이 글은 개인 프로젝트 진행 중 생긴 학습 사항을 기록한 글입니다. 해당 프로젝트에 대한 포스팅은 https://fadet-coding.tistory.com/70를 참고해주세요. 또한 이번 포스트에서 사용된 리액트 코드 작성 방식은 클래스형이 아닌 함수형입니다.
문제 발생
프로젝트를 진행 중 리액트 부분 코드를 작성하다가 꽤 헤맨 부분이 있어서 이번 포스트를 씁니다. 발단은 이렇습니다.
어떤 값을 사용자가 form에 입력했을 때, 그 필드에 입력 값이 바인딩되는, 한마디로 당연히 그 값이 해당 필드에 남아 있는 것이 좋습니다. 따라서 해당 기능을 테스트하기 위해
//<Form> component
<input type="text" name="originQ" placeholder='originQ' value={newinputQ} />
Form이라는 컴포넌트 하나를 만든 뒤 이렇게 tag에 value를 추가하여 변수 newInputQ를 추가하고
//<Form> component
const [newinputQ, setNewinputQ] = useState(props.bindingQ);
newInputQ를 state화한 뒤 props를 사용하여 props.bindingQ로 초기화를 해주고 App으로 돌아가
//App.js
const [bindingQ setBindingQ] = useState('init');
...
<Form Q={bindingQ} onForm={(_originQ, _originA) => {
setBinding(_originQ);
}}></Form>
props.bindingQ에 해당하는 state를 하나 만든 뒤 입력 값을 세팅하게 합니다. 하지만 이 코드까지 작성한 후 submit을 해도 값은 바인딩되지 않습니다. 당연히 아직 form의 value는 값이 변하지 않았기 때문이죠. 따라서 다시 Form 컴포넌트로 돌아가 form 태그 안에 onChange를 사용합니다.
//<Form> component
... value={newInputQ} onChange={event => {setNewInputQ(event.target.value);}}>
제 지식으론 이렇게 코드를 짜면 기능이 작동해야 정상이지만 아무리 해도 바인딩되지 않기에 로그를 찍어보기로 했습니다.
이 로그를 본 후 드는 바로 든 생각은 'state가 비동기 처리되서 값이 자꾸 이상하게 돌아가나?'였습니다만 제 지식으론 아무리 state가 비동기 처리된다해도 한 컴포넌트 내에서 두 개 이상의 state를 사용한 것도 아니고 그게 가능한지 의문이었습니다. 하지만 이 때까진 잘 몰랐기에 그게 문제인 줄 알고 state와 변수도 같이 사용해보고 sync한 처리를 위해 useEffect까지 사용했지만 전혀 해결되지 않았습니다. 결국 axios 문젠가해서 axios 문서도 다시 읽어봤고 submit후 값이 초기화되는지 구글링도 해봤습니다만 도움이 되지 않았습니다.
그렇게 고민하던 때에 콘솔창을 보고 든 생각이 '페이지가 url을 다시불러오네? 새로고침되는건가?'였고 바로 컴포넌트에
event.preventDefault();
추가해서 새로고침을 막았더니 기능이 정상 작동했습니다.
javascript, DOM, 가상 DOM
위 문제를 겪고나니 해결됐다하더라도 왜 그런건지 궁금한건 마찬가지였습니다. 그래서 리액트에 대한 정보를 좀 찾아보게 되었고 그렇게 다시 가상 DOM부터 쭉 훑어봤습니다. 이 과정에서 DOM에 대한건 대충 알고 있었지만 좀 더 자세히 복습하게 되었죠.
일단 본론으로 들어가기 전, 이 포스트를 보시는 분의 대다수는 리액트가 돌아가는 방식에 대해서 아시겠지만 제가 공부한 내용을 복습할 겸 짧게 정리해보겠습니다. 백엔드 개발자를 희망한다면 이 정도만 알아도 되지않을까?싶네요.
웹이 처음 등장하고 js가 등장한 뒤로 돌아가보겠습니다. js가 등장한 뒤 우리는 HTML과 CSS를 업데이트하여 더 반응성 있는 웹 페이지를 만들게 되었고 이것을 가능케 하는게 DOM API라고 생각하시면 됩니다.
*API를 잘 모르신다면 제가 포스팅한 https://fadet-coding.tistory.com/7나 구글링해보시면 됩니다.
DOM이란 Document Object Model의 약자인데 사실 이걸 직역해보면 문서 객체 모델인데 이것을 가지고는 이해하기 어렵습니다. 우리가 전통적으로 웹페이지를 만드는 방법은 바로 'HTML과 CSS'를 사용한 문서를 만드는 겁니다.
하지만 JS의 등장 이후론 JS가 HTML을 업데이트 해야하기 때문에 HTML 문서를 직접 <p>태그 등이나 css를 포함하여 작성하는 것이 아니라 에 '자, <p>태그는 여기에 놓고 css로는 이 태그를 꾸며줘'라는 JS로 작성된 문서를 생성하여 실행하면 브라우저가 JS를 해석하여 페이지를 렌더링해주는 것입니다.
그리고 여기서 DOM이란 JS가 해석할 수 있도록 작성해야하는 각 html들을 트리 구조인 객체 형태로 표현한 설계도입니다. 더 자세한 설명은 refer를 참고해주세요.
그래서 간단히 JS와 DOM의 관계를 말하자면(예시니까 정확하진 않음) 내가 바빠서 이삿짐 센터 직원을 시켜 방 안에 가구를 배치한다고 했을 때
1. 가구 : 실제 HTML 코드
2. 방 안 : 클라이언트가 보는 페이지
3. 센터 직원 : 브라우저
4. 센터 직원에게 전달된 가구 위치가 적힌 문자 : DOM
5. 문자를 적은 언어 : JS
이제 DOM을 JS로 조작하여 웹 페이지를 그린다는 말이 이해되시리라 믿습니다. 시간이 지나 웹이 발달하며 순수 JS로는 공급이 수요를 따라가지 못하자 사용하기 쉽게 여러 라이브러리들이 나옵니다. 그 중 jQuery가 대표적이죠. DOM을 편리하게 조작할 수 있고 어느 브라우저든 호환이 쉬워 지금도 많이 사용하기는 하나 JQuery는 한계가 명확했습니다. 그것은 바로 DOM을 직접 조작해야한다는 것이죠.
DOM을 직접 조작한다는 것은 위의 예시를 그대로 빌려 쓰면 센터 직원에게 전달되는 문자를 계속 보낸다는 겁니다.. 그런데 생각해 보세요. 내가 적은 문자를 보고 직원이 거의 모든 가구를 옮겨놨는데 갑자기 마음에 안 들어서 가구 위치들을 싹다 바꿔달라고 직원에게 문자했다 해봅시다. 만약 통로가 좁은데 거실에 이미 소파를 놔서 통로를 일부 막았습니다. 근데 갑자기 운동 기구를 놔달라고 하면 소파를 또 살짝 옮기고 운동 기구를 놓고 다시 소파를 원위치하고...
마찬가지로 최근 웹은 반응성이 매우 중요하기 때문에 클라이언트에 매우 많은 변화가 발생합니다. 이럴 때마다 DOM을 직접 조작해 엘리먼트들을 수정한다면 여러 문제가 발생하겠죠?
이렇게 브라우저가 시시각각 변하는 DOM을 보고 페이지를 계속 다시 그리느라 브라우저도 자주 죽고 갱신 속도도 더디는 일이 발생합니다. 그리하여 나타난게 오늘 다룰 가상 DOM입니다. 정말 쉽게 한 줄로 이를 설명하자면 '변경사항을 적어뒀다가 일정 시간마다 페이지를 갱신한다.' 정도일 것 같네요.
예시를 이어서 사용하면 이렇습니다. 이삿짐 센터에서 일을 끝내놔도 하도 손님들이 가구 위치를 자꾸 바꿔달라고 떼 쓰길래 프로그램 하나를 개발했습니다. 손님들의 문자를 실시간으로 취합해서 동선이 안 꼬이는 최적의 가구 배치를 알려주고 손님이 문자를 보내면 반영해서 업데이트되는 프로그램입니다. 이게 있으면 나중에 손님이 문자로 추가 요구를 하더라도 기존 끝내 놓은 가구 배치를 바꾸지 않으면서 변경 사항에만 대응할 수 있겠죠? 가상 DOM이 이렇습니다.
가상 DOM의 개념을 간단히 설명하면 다음과 같습니다.
1. 실제 DOM의 상태를 메모리에 저장
2. 데이터가 업데이트되면 실제 DOM을 직접 만지지 않고 가상 돔에 전체 UI가 재렌더링되고 변경 전과 변경 후 가상 DOM의 차이를 메모리 상에서만 비교
3. 변경이 있을 경우 비교한 차이점만을 브라우저가 실제 DOM을 변경
기존에 만약 직접 DOM을 조작한다고 했을때, 예를 들어 코드 중간에 있는 <p id=...>태그를 찾아야한다고 가정해보면 매번 업데이트마다 이 태그를 찾기 위해 문서를 훑어야하니 메모리 누수가 발생하며 속도도 더뎌지고 코드량도 길어질 것입니다. 하지만 가상 DOM을 사용하면 이런 단점들을 보완할 수 있기에 React에서는 이런 가상 DOM을 도입합니다. 리액트에서 JSX를 사용하는 것도 가상 DOM을 사용하기에 나타나는 특징이라 할 수 있죠.
위의 내용까지 읽어보면 뉘앙스가 가상 DOM이 속도 면에서나 유지보스 측면에서나 모두 실제 DOM 조작에 비해 유리할 것 같지만 사실 가상 DOM은 오히려 초보자들 프로젝트 기준에선 거진 DOM보단 느립니다. 하지만 일반적인 경우에선 이 속도의 차이란 건 사실 우리가 느끼지 못할만큼의 미세한 간극이기에 리액트를 쓰는 것이 바닐라 js를 사용하는 것보다 득이 훨씬 많기에 큰 점유율을 가져오는 것이겠죠. 우리가 살펴본 내용은 문제 해결의 이해를 위한 정말 일부분의 이야기만 다룬 것 뿐이니 만약 프론트엔드 쪽 지망이시라면 해당 내용에 대해 더 알아보시는 것을 추천합니다.
라우팅과 렌더링(SSR, CSR)
일단 다음 내용으로 넘어가기 전에 미리 라우팅, 페이지 렌더링의 개념 정도는 알고 계셔야 합니다. 간단히 설명하자면 라우팅이란 쉽게 말해서 한 웹사이트의 홈에서 다른 주소로 넘어가는 과정을 말합니다. 쉽게 글 생성 페이지로 가기 위해 주소창이 https://메인URI/home에서 https://메인URI/create로 가는 과정이라고 생각하시면 됩니다. 페이지 렌더링도 쉽게 말해서 작성된 프로그래밍 코드를 사용자가 시각적으로 볼 수 있게 화면을 그리는 과정입니다. 자세한 설명은 refer에 링크 있습니다.
페이지 렌더링에는 가장 유명한 SSR과 그 상대적인 개념인 CSR 정도만 알면 될텐데, SSR은 서버에서 사용자에게 보여줄 화면을 그러놓고 요청이 오면 화면을 제공하며 CSR은 사용자에게 요청을 받으면 서버가 화면이 아닌 JS를 보내주어 JS를 바탕으로 클라이언트에서 화면을 그립니다. 자세한 설명은 refer에 링크 있습니다.
일반적으로 사람들이 쭉 이용해왔던 웹은 주로 렌더링 중 SSR을 사용해왔습니다. 보통 SSR 방식의 페이지는 라우팅을 할 때 브라우저가 새로고침됩니다.
라우팅할 때 뿐만 아니라 별도의 설정이 없다면 get, post 등의 HTTP 요청시 파라미터를 보내야 하기에 url이 변경되야만하고 이 과정에서 일반적으로 새로고침이 발생합니다.
하지만 CSR 방식으로 렌더링되는 경우 일반적으론 SPA방식(refer에 링크 있습니다.)을 사용하기 때문에 대부분의 경우 위의 SSR 경우처럼 사용자가 사이트 내 다른 기능으로 이동할 때나 HTTP 요청을 보낼 때 페이지가 새로고침되지 않습니다.
문제 해결
본론으로 다시 들어가 앞서 가상 DOM이나 다른 개념들을 간단히 살펴봤던 이유는 글의 맨 처음 생겼던 문제 해결 과정에서 무엇이 문제였는지 알기 위함이었습니다. 그리고 이제 드디어 아까 발생한 문제가 어떻게 해결됐는지 알 수 있을 것 같습니다. 저는 기존에 spring boot를 이용해 주로 프로젝트를 진행하였기에 대부분 thymeleaf같은 템플릿 엔진을 써서 SSR 방식으로 웹을 만들었습니다. (물론 api를 사용해 CSR 방식을 차용한 프로젝트도 존재합니다.) 그렇기에 사실 페이지가 새로고침되고 말고는 크게 상관이 없었습니다.
하지만 리액트를 사용하는 것은 그렇지 않았습니다. 기존에 쓰던 자바든 리액트든 변수라는 개념은 똑같습니다. 이 변수는 내가 새로고침을 한다고 해서 영향이 가지 않습니다. 하지만 리액트의 state는 다릅니다. 내가 setState를 통해 state를 변경하여 가상 DOM이 업데이트된다 해도 페이지가 새로고침되면 아무 의미 없습니다,
새로고침이 된다는 것은 브라우저가 아예 re-fresh된다는 것을 의미하기 때문에 아래 그림처럼 state인 bindingQ가 새로고침 후 초기화됩니다.
따라서 이것을 막기 위해서 preventDefault() 함수를 통해 새로고침이 되지 않도록 막으면 메모리에 state가 변경된 가상 DOM이 유지되고 re-fresh되지 않습니다. 또한 리액트는 url 파라미터를 보낼 때도 기본적으로 페이지가 유지된 채로 보내지게 됩니다.
이를 바꾸는 또 하나의 방법은 페이지를 새로고침하는 onSubmit대신 onClick을 사용하는 겁니다. onClick은 또한 form 내부에 위치해야 작동하는 onSubmit과 다르게 form 내부에 위치하지 않더라도 작동한다는 장점도 있습니다. 그러나 클릭이 아닌 event trigger의 경우 지정해줘야 하며 form 내부에서 작동하는게 아니란 것도 tag 작성에 유불리가 있을 수 있기 때문에 뭘 사용할지 잘 생각해봐야합니다.
이번 포스팅에서 다룬 개념들은 일반적으로 많이 사용되는 내용을 담았으며 라이브러리 등을 이용해 사용할 수 있는 다른 방식은 배제했습니다. 그렇기에 이번 포스팅을 엄격한 잣대로 비판해주시진 않았으면 좋겠습니다... 나중에 공부가 더 된다면 더 자세히 포스팅하도록 하겠습니다.
refer
https://www.youtube.com/watch?v=1ojA5mLWts8
SPA & Routing | PoiemaWeb
단일 페이지 애플리케이션(Single Page Application, SPA)는 모던 웹의 패러다임이다. SPA는 기본적으로 단일 페이지로 구성되며 기존의 서버 사이드 렌더링과 비교할 때, 배포가 간단하며 네이티브 앱과
poiemaweb.com
https://devbirdfeet.tistory.com/217
Network - 브라우저 렌더링 이해하기(Feat. Virtual Dom)
요즘 가장 큰 고민은 브라우저와 react 의 동작원리 자체에 대한 이해도가 부족하다는 것이다. 며칠 전 발생했던 이슈가 motivation 이 되어 제대로 공부를 해보기로하고 이렇게 포스팅을 해본다. 한
devbirdfeet.tistory.com
https://chunggaeguri.tistory.com/entry/HTML-%EB%A0%8C%EB%8D%94%EB%A7%81Rendering%EC%9D%B4%EB%9E%80
[HTML] 렌더링(Rendering)이란?
렌더링(Rendering)이란? 렌더링은 웹사이트 코드를 사용자가 웹 사이트를 방문할 때 보게 되는 대화형 페이지로 바꾸는 웹 개발에 사용되는 절차입니다. 이 용어는 일반적으로 HTML, CSS, JavaScript 코
chunggaeguri.tistory.com
https://m.blog.naver.com/ljsun4336/220544726584
[네트워크] Routing(라우팅) 개념
* 라우트와 라우팅의 차이점(route와 routing의 차이점) - 라우트, 라우팅, 라우팅 프로토콜을 비교할 수 ...
blog.naver.com
[ 기술 스터디 ] SSR과 CSR의 차이
자강두천
velog.io
https://velog.io/@ru_bryunak/SPA-%EC%82%AC%EC%9A%A9%EC%97%90%EC%84%9C%EC%9D%98-SSR%EA%B3%BC-CSR
SPA 그리고 SSR과 CSR
렌더링에 관해선 이전 글에서 작성했지만 다시 한번 간단하게 말하고 가자면, 렌더링이란 사용자가 웹 페이지에 접속했을 때 서버로부터 HTML 파일을 받아 화면에 그려주는 것이다. SSR(Server Side R
velog.io
'React' 카테고리의 다른 글
R2_React Context API의 필요성(feat. state, props와 리렌더링) (0) | 2023.02.17 |
---|