이해하기 쉽게 설명하려고 개념 몇개가 생략되었습니다. 궁금한 게 있을 때 질문주시면 답변해드릴게요
SPA(Single Page Application)가 등장하기 전에 웹은 주로 MPA 방식으로 구성되었습니다. 벌써부터 어렵죠? 맞아요 어렵습니다. 하나씩 차근차근 알려드릴게요 SPA은 조금 있다 알려드리고 MPA부터 설명하겠습니다.
영어를 직역해서 해석해보자면 여러 개의 페이지 웹
이라고 해석할 수 있겠죠? 여기서의 여러 개의 page는 우리가 쉽게 접하는 HTML입니다. 우리 나만의 일기장 했을 때 URL EndPoint 별로 HTML을 만들었죠?
다들 기억나시나요? 이거는... 우리 한달도 안 되었어요!!! ㅋㅋㅋㅋ 기억을 잠깐 다시 살려보자면, 수정하기 페이지랑 등록하기 페이지 그리구 메인 페이지 별로 !!! HTML을 만들었다는 사실을 기억하시나요? 이게 MPA입니다. Multi Page Application 여러개의 HTML을 만들어 놓고 URL별로 해당 HTML을 보여주는 형식이 MPA입니다.
그러게요 왜 문제일까요? 왜 문제인지는 네트워크랑 브라우저 렌더링 과정을 알아야하는데, 너무 오래 걸려서 간단하게만 설명하겠습니다. 사용자는 페이지를 이동할 때마다!!!! 백엔드에서 새로운 HTML 페이지 전체 코드를 다 불러오는 거예요 모든 코드를 , 오케 지금도 이해를 못할 수도 있으니깐 예시를 들고올게요
<body>
<main>
<header>
<div class="header-text-container">
<div class="header-text">민지의 다이어리</div>
</div>
<img src="./img/Rectangle1.png" alt="img" class="header-img" />
</header>
<article>
<div class="article-header">
<div class="article-header-container">
<div
class="article-header-font"
onclick="onClickChangePost(event)"
id="article-daily"
>
일기 보관함
</div>
<div
class="article-header-text"
id="article-picture"
onclick="onClickChangePost(event)"
>
사진 보관함
</div>
</div>
</div>
...
제 나만의 일기장에서 html 일부를 들고온건데 실질적으로 500줄
넘게 있습니다. 페이지 이동할 때마다 브라우저는 서버로부터 새로운 html을 불러옵니다. 우리는 간단한 프로젝트를 했겠지만, 네이버 같은 경우, MPA를 채택한다면? 벌써끔찍하죠.. 페이지 이동할 때마다 새로운 html을 불러오고 css을 적용시키고 js를 그 위에 입히고,,, 이 과정이 완료될 때까지 브라우저는 아무것도 안 보여줍니다. 멈춰있다는 거죠. 그럼 사용자 입장에 봐볼까요? 저희도 웹툰이든 어떤 페이지든 1초라도 멈춰있으면 답답한 경험을 느꼈을텐데.. 전 벌써부터 무섭습니다. ㅋㅋㅋ
있어요 장점 있습니다. 가장 큰 장점으로는 SEO(Search Engine Optimization) 입니다. 간단하게 집고만 넘어가겠습니다.. 자꾸 딴 길로 새네.. SEO 직역하자면 검색 엔진 최적화 즉, 브라우저에서 어떤 키워드로 검색을 하면 잘 나온다는 얘기입니다. SPA의 단점이죠. 그리구 초기 렌더링이 빠릅니다. SPA는 모든 페이지를 미리 렌더를 하고 끝나면 해당 페이지만 보내주는데, MPA는 해당 페이지를 들어 갈 때마다 서버로부터 해당 페이지만 불러오기 때문에,, 초기 렌더링은 빠릅니다. 자세히 알고싶으면 구글링해서 검색하는 걸 추천 그냥 넘어가셈..
위와 같은 문제로 2010 초반대에 많은 대기업에서 기존 방식을 박살시키는 SPA를 만들었습니다. 대표적으로 React, Vue, Angular
죠 세 라이브러리 & 프레임워크가 궁금하다면 구글링 ㄱㄱ
SPA(Single Page Application)란 한 페이지(index.html)
로 모든 페이지를 처리한다는 얘기입니다. MPA와 다르게 한 페이지로만 모든 페이지를 관리하기 때문에 페이지 전환할 때마다 새로운 html을 불러올 필요 없어요. 필요한 데이터(파일)만!!! 서버로부터 요청하면 됩니다. 모든 페이지를 다 바꾸는 MPA와 다르게 페이지 내에 일부 component만 변동하기 때문에 로딩 측면에서 MPA에 비해 월등히 우수합니다. 그래서 SPA는 빠른 페이지 전환 / 부드러운 사용자 경험 / 네트워크 트래픽 절감할 수 있고, SPA는 재사용성 / 모듈화 / 컴포넌트 기반 개발을 하기 때문에 개발자들한테 사랑받고 있습니다.
우리는 개발자로써, 코드가 100줄 ?이 뭐야 50줄만 넘어가도 코드를 보기 싫어해야합니다.(나만 그런가) 만약 제가 신입 개발자로 들어갔는데 한 파일에 몇 천줄이 있는 코드를 유지보수 해야한다.? 전 바로 퇴사합니다.. 어케 보는데 살려줘
컴포넌트란? UI(User Interface)를 구성하는 독립적이고 재사용 가능한 코드의 단위.
재사용성 !!!!!!!!!!!!!!!! 위 그림은 우리 실제 게시물 등록페이지 일부를 발취한건데 딱봐도 재사용할 수 있는 컴포넌트 형태가 보이죠!! 제발 보인다고 해주세요!!!!!!!!!!!! 작성자 / 비밀번호 / 제목 / 내용 다 비슷하게 생겼으니깐 이걸 하나의 컴포넌트로 사용할 수 있다는 걸 바로 눈치채야합니다! 만약에 저 컴포넌트화를 시키지 않고 코드를 짠다면? 뭐 짤 수 있겠죠? 근데.. 전 50줄이 넘어가는 코드를 보면 토하는 습관이 있어요.. 분명히 우리는 줄일 수 있는데,, 왜...? 복붙해서 딸깍 하는지?
그리구 SPA의 가장 큰 장점은 컴포넌트화를 시킬 수 있다는건데, 우리는 React를 사용하면서 최대한 이런 것들을 활용하는 게 좋지 않을까요? 또 나중에 나오는데 re-render 관련해서 컴포넌트화를 무조건 해야 성능 측면에서 좋아집니다.
오케 브라우저 , 렌더링 모른다고 가정하고 설명하겠습니다. browser
는 우리가 아는 chrome / safari / firefox
이런게 브라우저입니다. rendering이란 웹 애플리케이션에서 데이터를 시각적으로 변환하여 화면에 출력하는 과정을 의미. 즉, 개발자가 작성한 코드(HTML, CSS, JavaScript)가 사용자가 볼 수 있는 UI(User Interface)로 변환되는 과정이 렌더링입니다. re-render는 우리가 예를 들어서 Hover를 했을 때는 Component가 보인다고 가정하고, Hover를 풀면 Component가 안 보인다고 하는 로직이 있다고 가정해봅시다. 그럼 브라우저는 Event Hover 여부에 따라서 UI를 변경해야겠죠? 이 때!!! re-render
가 발생하는 겁니다.
이제부터 조금 어렵습니다.. 잘 따라오셔야해요..
기존 방식은 어떤 식으로 렌더링 한다고 했죠? 기존 MPA는 서버로부터 html css js 전체를 받아와서 브라우저에서 렌더링(DOM을 그린다.)을 합니다.(Modern Javascript deep dive 38장 브라우저 렌더링 과정 참고) SPA 같은 경우 가상 공간(virtual DOM)에서 DOM(Document Object Model)
미리 paint하고 이후 완성이 되면(DOM Tree가 생성되면) 그대로 브라우저한테 넘겨줍니다.(Commit Phase) 으 벌써부터 끔찍한 용어들이 나오죠? 일단 따라오세요.. 힘내요.. 진짜 익숙해져야 합니다...
우리는 html css js로 코드를 작성하자나요? 근데 브라우저는 그걸 그대로 읽지 못합니다. DOM으로 변환한 이후 실제 브라우저에 UI가 보이는 겁니다.
이 DOM을 만드는 건 MPA는 브라우저에서 처리합니다.
virtual dom
에서 DOM 트리를 생성하고 완성이 되면 실제 브라우저에 적용시킵니다. 브라우저에서 하던 일을 가상 공간에서 하는 게 뭐가 이점인데? 라고 생각할 수 있습니다. 실제로 reflow와 repaint 과정은 아니 다시 DOM 업데이트 하는 과정은 상당히 무거운 작업임 기존 웹은 정적 사이트로써 동적으로 잘 변하지 않았습니다 하지만 동적으로 변하는 게 많아지면서 browser가 모든 일을 한다는 건 부담스러울 수 있습니다. 따라서 가상 공간에서 DOM 변경을 처리하고 실제 업데이트만!!! Browser에서 하면 훨씬 더 성능 측면에서 효율적임.
React는 값이 변하면 re-render를 시키는데 Re-render 되는 조건은 다음과 같습니다.
- State가 변경되면 react는 리렌더링한다.
const [state, setState] = useState(0);
여기서의 setState가 호출되면 React는 리렌더링시킵니다
- Props가 변경되면 react는 리렌더링한다.
function Parent() { const [name, setName] = useState('John'); return <Child name={name} />; } function Child({ name }) { return <div>Hello, {name}!</div>; }
여기서의 name이라는 props가 변경되면 React는 리렌더링시킵니다 당연히 리렌더링이 반드시 되어야 하는 부분에서는 props를 내리는 게 좋습니다. 하지만, 과도하게 props를 내리게 된다면 리렌더링이 될 가능성이 높아지겠죠? 이는 성능 최적화 부분에서 불리합니다.. 그럴 가능성을 우리는 줄이자 이말이죠.. 그리구 가독성 측면에서 좋진 않습니다.. props가 많아지면 그만큼 코드 길이가 늘어날꺼고,,
key 변경과 강제 리렌더링되는 조건도 존재하지만 자주 쓰이지 않아 넘어가겠습니다. 나중에 알고 싶다면 따로 공부하시는 걸 추천 드립니다.
부모 컴포넌트가 리렌더링이 되면 그에 상응하는 자식컴포넌트도 Re-rendering된다!~!!!!!!!!!!!!!!!!!!!!! 왜냐고요? 오늘 수업 6시간 듣고 싶지 않다면 그냥.. 외우세요 부모 컴포넌트가 리렌더가 되면 그 관련 자식 컴포넌트는 리렌더 된다!!!!!!
리액트는 최초 렌더링 시, 컴포넌트 전체를 미리 만들어놓고 해당 컴포넌트들을 조립합니다.
import { useState } from 'react'
function App() {
const [id, setId] = useState('')
const [password, setPassword] = useState('')
return (
<>
<div className="">연습</div>
<Form title={id} onChange={setId} />
<Form title={password} onChange={setPassword} />
</>
)
}
function Form({ title, onChange }) {
return (
<>
<input type="text" value={title} onChange={(event) => onChange(event.target.value)} />
<button>클릭</button>
</>
)
}
export default App
위와 같은 그림으로 리액트는 Virtual DOM에 DOM Tree(current Tree)를 구축합니다. 만약에 input에 값이 입력되면 어떤식으로 동작할까요? 값이 입력되면 onChange Handler가 실행되겠죠? 그럼 Props로 받은 onChange가 실행될거고, 이것은!!!!! setState 라는 사실을 알 수 있습니다.
위에서 리액트는 언제 re-render가 된다고 했죠? state 값이 변동되면 react는 리렌더 된다고 했죠? State가 변동되니깐 해당 컴포넌트는 리렌더가 되는겁니다. 즉!!!!!!! App Component가 리렌더 되는겁니다.
불변의 법칙 react는 부모 컴포넌트가 리렌더 되면!!!!! 자식 컴포넌트는 또한 리렌더가 된다.
즉 우리 같은 경우 APP Component가 변경되면 자식인 div / Form / Form
이 리렌더가 됩니다. 위 코드에서는 전부 리렌더가 되는거예요... 얼마나 비효율적이야.. 우리는 input만 입력했는데 모든 컴포넌트를 리렌더링 시킨다는게.. 우리는 컴포넌트화의 중요성에 대해서 알아야합니다 컴포넌트화를 시켰음에도 불구하고.. APP 전체가 리렌더된다는게... 또,, 리렌더링 조건이 뭐죠? props의 변동입니다. !!! 우리는 props로 state를 내렸기 때문에!!! props가 변동되면,, 자연스레.. 해당 컴포넌트는 리렌더링 되는겁니다.. 참 슬픈일이죠.. 허그덩.. 그래서 살짝 조금만 바꿔보겠습니다. 좀 더 효율적인 리렌더를 하기 위해서.. 조금만 바꿔보겠습니다.
import { useState } from 'react'
function App() {
return (
<>
<div className="">연습</div>
<Form />
<Form />
</>
)
}
function Form() {
const [value, setValue] = useState('')
return (
<>
<input type="text" value={value} onChange={(event) => setValue(event.target.value)} />
<button>클릭</button>
</>
)
}
export default App
이 코드는 어떨까요? 똑같은 상황입니다. input
에 어떤 값을 입력했다고 가정해봅시다. 그럼 onChange Handler에 의해.. setValue가 실행되겠죠? 그럼 뭐라했죠? state가 변한다!! 그럼 해당 컴포넌트는 리렌더링되고, 해당 컴포넌트의 자식 컴포넌트 전부가 리렌더가 됩니다!!!!
즉 파란색 부분만,, 리렌더 된다는 얘기입니다. react는 바뀐 부분만!!! 실제 DOM에 적용시킵니다!!! 전체 APP Component를 다시 적용시키는 것보다는 파란색 부분만 실제 DOM에 Update 시키는 게 좀 더 좋겠죠? 그다음 props를 적게 쓰니깐 가독성도 올라가고,, 리렌더도 막을 수 있고,, !!! 저는 이렇게 코드를 짜는 게 react라고 말하고 싶습니다. SPA의 장점을 극대화하고 재사용성도 높아지고,,!!! 회사에서는 첫번째 코드를 짜는 사람을 선호할까요? 아니면 두번째 코드를 짜는 사람을 선호할까요? 당연히 두번째로 코드를 짜는 사람을 선호하겠죠? 우리는 끊임없이.. 생각을 해야합니다.. 힘내요 (궁금하다면 실제 코드를 console.log 찍어드리겠습니다.)
류지승 비상.. 곧 사망 예정.. BoardsWrite.tsx를 보자마자 담배피러 감.. 나 진짜 코드 이제 안 봐준다..
간단하게 Virtual DOM을 그려봤습니다 많이 생략된..
const [inputs, setInputs] = useState<IInputs>({
writer: "",
password: "",
title: "",
contents: "",
});
민서님 같은 경우 현재 <BoardsWrite />
에서 state를 관리하고 있음. 사실.. 최종 입력된 값을 백엔드로 보내야하기때문에, 부모 컴포넌트에서 value
를 관리해야합니다.. 리렌더링 측면에서 봐봅시다. 예를 들어서 작성자 컴포넌트에서 input의 값이 들어갔다고 가정해봅시다. onChange handler
에 의해 작성자의 input이 입력되었겠죠? 핸들러 함수는 다음과 같습니다.
const onChangeInput = (
event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) => {
const { name, value } = event.target;
setInputs((prev) => {
const newInputs = {
...prev,
[name]: value,
};
return newInputs;
});
};
setInputs가 호출되어서 실질적으로 state인 inputs이 변경되었습니다.. 그럼 뭐죠? 리렌더링이 되겠죠?
허그덩.. 우리는 부모 컴포넌트가 변경되면 모든 자식 컴포넌트가 리렌더링된다는 사실을 알고 있죠? 이제는 알아야합니다.. 덜덜덜 즉 input의 값이 입력될 때마다.. 우리는 boardsWrite
컴포넌트가 리렌더링 되는 겁니다.. 벌써 무섭다야. 그럼 input가 입력되면서도 해당 하는 input component만 리렌더링 시키려면 어떻게 해야할까요?
참 어렵죠 react.. 저도 여전히 어렵습니다.. 우리는 React.memo라는 걸 사용해야합니다..
React.memo 공식 문서
우리는 불변의 법칙이 있죠? 부모 컴포넌트가 변경되면 모든 자식 컴포넌트는,, 리렌더링이 된다는... 그걸 묵살시키는게 Memo입니다.
Memo가 뭐냐 memoization
컴퓨터 프로그램이 동일한 계산을 반복해야 할 때, 이전에 계산한 값을 메모리에 저장함으로써 동일한 계산의 반복 수행을 제거하여 프로그램 실행 속도를 빠르게 하는 기술이다 라고 구글에서 말해주고 있네요. 그니깐 값은 안 변했는데 동일한 계산이 반복 수행되니깐 이를 제거하는 게 React.Memo입니다..
우리는 writer component
가 변경되면 이 컴포넌트만 리렌더 시키면 됩니다.. 어떻게 memoization하냐.. state값(writer / password / title / content)을 props로 넘깁니다.. 그리구 이 값이 바뀔 때만,, re-render 시켜라고 할 수 있는데, 이렇게 해주는 react hook이 Memo라는 것입니다.. 지금 단계에서 어려우니 이렇게 해결할 수 있다..만 알면 좋을 듯 합니다..