React의 가상 DOM, Vue의 가상 DOM과 비교

 

가상 DOM이란?

DOM (Document Object Model)

브라우저가 제공하는 문서 객체 모델

HTML 요소를 트리 구조로 표현하며, 브라우저에서 렌더링되는 실제 DOM을 말한다.

실제 DOM은 업데이트가 발생할때마다 전체 DOM 구조를 다시 계산하고 렌더링한다.

 

가상 DOM (Virtual DOM)

메모리 상에서 실제 DOM의 경량화된 사본을 만드는 개념으로 DOM 상태를 Javascript 객체로 나타내어 빠르게 연산할수 있다. 실제 DOM 조작 전에 변경 사항을 비교하고 필요한 부분만 실제 DOM에 업데이트 하는 방식으로 성능 최적화를 하기 위하여 생성된다.

리액트는 가상 DOM을 업데이트 할때 최소한의 DOM 업데이트를 보장하기 때문에 복잡한 업데이트가 빈번하게 발생해도 실제로 더 빠르게 업데이트할 수 있고, 유지보수에도 용이하다.

 

 

 

React의 가상 DOM

React의 가상 DOM과 비교 알고리즘 

가상 DOM은 메모리상에 존재하는 실제 DOM의 경량 사본으로 리액트는 컴포넌트의 상태나 Props가 변경될 때마다 새로운 가상 DOM을 생성한다.

리액트는 이전의 가상 DOM과 새로 생성된 가상 DOM을 비교(diff)하여 변경된 부분만 계산하는데, 이때 변경이 없으면 건드리지 않고 변경이 있는 부분만 찾아내어 실제 DOM에 반영한다.

<div>
<p>텍스트만 변경될 경우</p>
</div>

텍스트만 변경될 경우 <p> 태그 노드는 유지되고 노드 내부의 텍스트 노드만 변경한다.

 

재조정(Reconciliation)

상태나 Props가 변경되면, UI를 효율적으로 다시 그리는 과정을 말한다.

DOM 트리구조를 위에서 아래로 순차적으로 비교하여(전체 비교는 아니다) 변경된 노드만 업데이트한다.

배열이나 리스트일 경우 key를 사용하여 재사용 여부를 판단해 불필요한 업데이트를 방지한다. 이때 key가 같으면 노드를 재사용하며 다를 경우 새로 생성한다.

 

React의 가상 DOM 업데이트 과정

1. 상태 또는 props 업데이트를 감지하게되면 해당 컴포넌트의 렌더링 함수가 호출된다. (리렌더링)

2. 변경된 상태를 기반으로 새로운 가상 DOM 생성한다.

3. 이전 가상 DOM과 2번에서 만들어진 가상 DOM을 비교하게되는데, 이때 재귀적 비교를 통해 변경된 노드를 찾는다. 이때 전체 가상 DOM을 비교하는게 아니라 최적화 방식으로 특정된 영역의 가상 DOM만을 비교한다.

    ㄴ 동일한 레벨에서 노드 유형이 동일한 경우에만 비교하며, 노드 유형이 다를 경우는 해당 노드를 완전히 교체한다.

    ㄴ key를 활용하여 고유성을 식별하며, key가 없거나 중복되면 리스트를 다시 비교하여 교체한다. (비효율적임)

    ㄴ 동일한 노드와 동일한 하위트리에서 변경사항이 없다고 판단되면, 해당 하위 트리를 비교하지 않는다.

 4. 실제 DOM에 필요한 변경사항을 최소화하여 패치한다.

 

리액트는 컴포넌트를 다시 렌더링 한 뒤 가상 DOM 비교를 통해 효율성을 유지한다.

불필요한 리렌더링을 방지하려면 개발자가 추가적인 최적화 작업(메모이제이션)을 할수있다.

 

 

 

Vue의 가상DOM 업데이트와 비교해볼까?

vue는 종속성 추적과 반응형 데이터 시스템을 활용해 React와는 다른 방식으로 실제 DOM 업데이트를 최적화한다.

ㄴ 종속성 추적 ? 데이터와 이를 사용하는 템플릿 또는 컴포넌트간의 종속성 관계를 추적한다. 이로인해 변경된 데이터와 그에 의존하는 부분만 업데이트가 가능하다. 

ㄴ 반응형 데이터 모델 ? reactive 또는 ref 같은 API로 반응형 데이터를 관리한다. 이 데이터는 기본적으로 proxy를 활요하여 데이터의 변화가 생기면 이를 즉시 감지하고 필요한 업데이트를 수행한다.

vue는 데이터 중심, react는 컴포넌트 중심으로 가상 DOM을 다룬다고 이해하면된다.

 

vue의 가상 DOM 업데이트 과정

1. reactiva, ref등을 통해 데이터 변경을 감지한다. 어떤 데이터가 어떤 DOM 노드와 연결되어있는지 추적한다. (종속성 추적)

2. 종속된 데이터가 변경된 경우에만 가상 DOM을 생성한다

3. 이전 가상 DOM과 2번에서 생성한 가상 DOM을 비교한다. (종속된 데이터가 변경된 부분만 비교)

4. 실제 DOM에 필요한 변경사항을 최소화하여 패치한다.

 

vue의 가상 DOM이 효율적인 경우?

부분 업데이트가 많은 경우에 종속성 추적으로 필요한 부분만 업데이트가 가능하고,

반응형 데이터 시스템을 활용하면 (reactive. ref) 객체 내부 속성 변경도 쉽게 감지하기에 같은 데이터 구조를 사용할 경우  vue가 더 효율적이라고 할 수 있다.

 

 

 

정말 가상 DOM이 빠를까?

가상 DOM을 사용하는 프레임워크가 성능상의 한계를 보일수 있다는 점에서 '가상 DOM이 무겁다'라는 주제가 제기되었다고한다. 이로인해 가상 DOM을 사용하지 않거나 더 가벼운 방식을 채택한 프레임워크, 라이브러리들이 등장했다. (ex: svelte, solid.js...)

가상 DOM은 실제 DOM 조작 전 변경 사항을 계산하는 단계가 존재하고, 메모리를 사용하여 저장하고 관리해야한다.

 

항상 가상 DOM이 좋은 것은 아니다.

프로젝트 규모나, 업데이트되는 빈도 혹은 구조에 따라 실제 성능이 다를 수 있다.

예를 들어 DOM의 변경사항이 거의 없거나 간단할때에 실제 DOM이 더 빠를 수 있다.

소규모 애플리케이션에서 DOM의 변화가 거의 없을 경우, 가상 DOM의 비교(diff) 연산이나 메모리 사용등이 불필요할 수 있다. 따라서 가상 DOM이 유리한 경우는 복잡하거나 빈도 높은 업데이트가 발생할때 유리할 것이다. (대규모 어플리케이션 일수록 유리)

 

 

리액트 생명주기

 

리액트 컴포넌트는 생성(Mount), 갱신(Update), 제거(Unmount) 주기를 가지게됩니다.

해당 주기에서 사용되는 메소드에 대해 알아보겠습니다.

 

 

생성(Mount)

constructor(props)

메소드를 바인딩 하거나 state를 초기화하는 작업이 없을 경우 사용합니다.

만약, 해당 작업이 없다면 사용하지 않아도됩니다.

 

getDerivedStateFromProps(nextProps, prevState)

props로 받아 온 값을 state로 넣어주고 싶을때 사용합니다.

state를 변경할 때는 setState가 아닌 반환 값으로 변경해야하며 반환 값이 null일 경우에는 아무일도 발생되지 않습니다.

 

render()

반드시 구현되어야하는 유일한 메소드입니다.

이 메소드가 호출되면 this.props와 this.state의 값을 활용하여 값을 반환합니다.

render는 컴포넌트의 state를 변경하지 않고 호출될 때마다 동일한 결과를 반환합니다.

해당 메소드는 브라우저와 직접적으로 상호작용하지 않습니다.

 

componentDidMount(prevProps, prevState)

컴포넌트가 생성된 직후에 호출됩니다. (즉, render 후에 호출)

setState를 통해 작업하면 render가 두번 호출되므로 DOM 노드가 있어야하는 초기 작업에 사용하는 경우를 제외하고는 setState를 사용하지 않습니다. (= state의 초기화는 constructor에서 작업합니다.)

데이터 구독을 설정하기 좋은 위치이며 componentWillUnmount()에서 구독 해제 작업을 반드시 수행해야합니다. (timer, fetch, axios 등)

 

 

갱신(Update)

getDerivedStateFromProps(nextProps, prevState)

생성과 동일

 

shouldComponentUpdate(nextProps, nextState)

컴포넌트 업데이트 직전에 호출되는 메소드입니다.

props  state가 변경 되었을 때 리렌더링 여부를 반환 값으로 정합니다. 

*사용을 잘 안하는 이유

리액트 컴포넌트의 기본 동작은 매 state의 변화마다 다시 렌더링을 수행하는 것이기 때문에 잘 사용되지 않습니다.

해당 메소드는 성능 최적화를 목적으로 두고 있기 때문에 렌더링을 방지하는 목적으로 사용할 경우 버그로 이어질 수 있다. shouldComponentUpdate가 false를 반환하면 리렌더링이 일어나지 않으므로 그 다음 순서의 메소드들은 호출되지 않습니다.

 

render():

생성과 동일

 

getSnapshotBeforeUpdate(prevProps, prevState)

렌더링 결과가 실제 돔에 반영되기 직전에 호출됩니다.

메서드의 이름에서처럼 업데이트 되기 직전에 snapshot(props & states)을 확보하는게 목적입니다.

반환 값이 componentDidUpdate의 세번째 인자로 전달됩니다.

 

componentDidUpdate(prevProps, prevState, snapShot)

최초 렌더링에서는 호출되지 않으며 돔 요소가 업데이트 된 이후 호출되는 메소드입니다. 

getSnapshotBeforeUpdate를 구현했고 리턴 값이 존재한다면 세번째 인자로 넘겨받으며 반환값이 없다면 해당 인자는 undefined입니다.

setState를 사용할 수 있지만 조건문으로 감싸지 않는 경우 무한루프가 되는 경우가 발생할 수 있으므로 주의해야합니다. 이전과 현재의 props를 비교하여 네트워크 요청을 보내야할 경우 이 메서드를 사용합니다.

 

 

제거(UnMount)

componentWillUnmount()

컴포넌트가 DOM에서 삭제된 후 실행되는 메소드로 소멸단계에서 호출되는 유일한 메소드입니다.

componentDidMout에서 생성된 작업등을 정리할 때 사용합니다. (timer, fetch, axios 등)

실행 직후에는 이제 컴포넌트가 리렌더링 되지 않으므로 setState를 사용하면 안됩니다.

 

 

오류처리 (공식문서)

제어 흐름 조작하는데 사용하면 안되며 해당 메소드들은 자기 자신(컴포넌트)에 대한 오류를 감지할 수 없고 하위에 존재하는 컴포넌트에 대한 오류만을 감지할 수 있습니다.

 

getDerivedStateFromError(error)

에러가 발생한 뒤 UI 렌더링을 제어하기 위해 사용한다.

 

componentDidCatch(error, errorInfo)

에러 정보를 기록하려고 사용합니다.

*공식사이트에서 설명하길, 해당 메소드에서 setState를 통해 UI를 렌더링 할 수 있으나 추후 릴리즈에서 사용할 수 없게할 것이라고 적혀있습니다.

 

 


 

참고

https 적용을 진행하던 중에 오류가 나서 해결을 못하고 있었습니다.ㅠ

강의대로 진행했는데, 왜???

이러던 중에 질답게시판에 제로초님이 블로그글을 참고하여 진행하라고 댓글을 달아두신걸 봤고....

 

제가 나중에 헷갈릴가봐.. 나름대로 추가 정보를 정리하여 글을 작성합니다.

약간의 난관들도 있었지만, 다행이 성공했습니다.!

 

제로초님 블로그를 거의 대부분 참고했습니다.

www.zerocho.com/category/NodeJS/post/5ef450a5701d8a001f84baeb

 

(NodeJS) nginx와 let's encrypt로 SSL 적용하기(+자동 갱신)

리눅스에서 letsencrypt를 직접 설치합시다. 2020년 9월부로는 기존 방식보다 snap을 통해서 설치하는 게 더 쉬운 것 같습니다(https://snapcraft.io/certbot) 기존 방식 wget https://dl.eff.org/certbot-auto

www.zerocho.com

 

각 서버 모두 진행해야합니다. (프론트, 백 둘다)

다만 포트가 같으면 오류가 납니다.

 

 

설치

Certbot를 통해 Let's Encrypt SSL 인증서를 발급받습니다.

무료로 제공해주고 있습니다. 

이 인증서가 있어야 https를 사용할 수 있습니다.

인증서는 무료이지만, 3개월마다 갱신해줘야합니다.

sudo snap install certbot --classic
sudo apt-get install nginx

(apt-get은 우분투에서 사용하는 패키지 관련 명령어 도구입니다.)

 

 

nginx.conf 수정

그 후에 설정 하나를 수정해줘야합니다.

vim 에디터를 통해 수정해줄 것입니다.

vim /etc/nginx/nginx.conf

만약 이렇게 쳤는데.

권한을 요구한다면 sudo 를 통해 권한을 높여줍니다.

명령어 앞에 sudo를 붙이고 vim ~~를 입력하면 됩니다.

 

까만창이 뜨는데, 방향키로 움직일 수 있고 수정하기 위해서 

a를 누르면 INSERT 모드로 변경됩니다.

include 로 작성된 하단에 작업합니다.

include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;

// ===============작성해야하는 것
	server {
		server_name naver.com;
        listen 80;
        location / {
			proxy_set_header Host $host;
            proxy_pass http://127.0.0.1:3060;
            proxy_redirect off;
        }
    }
// ===============작성해야하는 것

server_name에 naver.com을 넣어놨는데, 본인의 사이트를 넣습니다.

 

저장할땐 ESC를 눌러서 커서를 제거한 후 :wq!를 입력합니다.

 

 

포트 확인

그 후에 80포트로 등록했으니 80포트가 사용되고 있는지 확인합니다.

사용되고 있다면 진행이 안되기 때문입니다.

sudo lsof -i tcp:80

80포트 확인

사용 중이라면 kill 해줍니다.

만약 내가 kill을 어떻게 하는 지 모르겠다면 아래와 같이 입력하세요.

위에서 PID는 (프로세스아이디) 19888이므로 아래와 같이 명령어를 입력합니다.

kill -9 19888

 

 

인증서 발급

그 후 인증서를 받기위해 명령어를 입력!

sudo certbot --nginx

 

이메일입력

 

이메일을 입력해줍니다.

그 후 약관동의를 다 해줍니다.

 

약관동의 1
약관동의2
설정 마지막

1번 입력 후 엔터를 쳐줍니다.

 

 

nginx.conf 설정

그 다음 다시 vim 에디터를 통해 작업을 합니다.

sudo vim /etc/nginx/nginx.conf

들어가보면 내가 작성하지 않은 코드들이 추가로 작성되어있습니다.

인증서를 발급 받으면서 작성된 코드들인데, 주석으로 #managed by Certbot과 같이 뒤에 적혀있습니다.

(제가 줄 정렬을 새로한 거라 그런데, 원본은 정렬이 안되어있습니다.)

nginx.conf 

 

 

default 수정

nginx.conf 2

 

nginx.conf 파일에서 include /etc/nginx/sites-enabled/*;가 작성되어있습니다.

때문에.. include가 되는 파일 중에 vim에디터를 통해 default 파일을 수정해줘야합니다.

vim /etc/nginx/sites-enabled/default

안에 server로 설정된 부분을 # <- 을 통해 주석처리합니다.

:wq!를 통해 저장하고 vim 에디터를 나옵니다.

 

 

nginx start

sudo service nginx start

 

 

참고

에러가 난다면 해 볼 수 있는 행동들입니다.

 

0. 무조건 상태먼저 확인

nginx 에러 도움말

systemctl status nginx.service

 

아래와 같이 status를 확인할 수 있습니다.

저 같은 경우에는 오타였습니다.

status 확인

 

 

1. 포트확인 후 kill

과정 중에 설명이 되어있습니다.  설명대로 다시 진행합니다.

sudo lsof -i tcp:80 // or 443
kill -9 프로세스아이디

 

 

2. 문법체크

sudo nginx -t

 

문법 체크 결과

ok, successful 이 아니라면 해당 파일을 확인해봐야합니다. (오타나 설정을 잘못 입력했는지)

 

 

3. 서버가 켜있던 상태에서 vim에디터를 통해 설정을 바꾼거라면 reload

sudo service nginx reload

 

 

nginx 명령어

// 시작
sudo service nginx start

// 재시작
sudo service nginx restart

// 중지
sudo service nginx stop

// 상태
sudo service nginx status

// 설정 
sudo service nginx reload

// 설정 파일 문법 체크
sudo nginx -t

 

 

 

dangerouslySetInnerHTML

dangerouslySetInnerHTML은 브라우저 DOM에서 innerHTML을 사용하기 위한 React의 대체 방법입니다.

일반적으로 코드에서 HTML을 설정하는 것은 사이트 간 스크립팅 공격에 쉽게 노출될 수 있기 때문에 위험합니다.

따라서 React에서 직접 HTML을 설정할 수는 있지만, 위험하다는 것을 상기시키기 위해 dangerouslySetInnerHTML을 작성하고 __html 키로 객체를 전달해야 합니다.

function createMarkup() {
  return {__html: 'First &middot; Second'};
}

function MyComponent() {
  return <div dangerouslySetInnerHTML={createMarkup()} />;
}

 

위의 글에서 주의 깊게 읽어야하는 부분은 2가지입니다.

코드에서 HTML을 설정하는 것은 사이트 간 스크립팅 공격에 쉽게 노출됩니다.

=> 이것은 innerHTML이나 dangerouslySetInnerHTML이나 동일합니다.

 

위험하다는 것을 상기 시키기 위해 dangerouslySetInnerHTML을 작성하고, __html 키로 객체를 전달해야합니다.

dangerously...^^..

 

동작은 동일하게 HTML을 삽입합니다.

하지만 innerHTML을 사용하면 DOM노드가 수정되었을때 수정된 것을 알 수 있는 방법이 없다고 합니다.

따라서 dangerouslySetInnerHTML을 사용하여 가상 DOM과 실제 DOM을 비교하여 변경된 것이 있다면 리렌더링이 될 수 있도록 해야합니다.

 

 

잘못된 예시

const App = () => {
	const str = 'Hello!';
    
    return(
    	<div>
        	{str}
        </div>
    )
};

ReactDOM.render(
	<App />, document.getElementById('root');
)

 

 

사용 예시1

const App = () => {
	const str = 'Hello!';
    
    return(
    	<div dangerouslySetInnerHTML={{__html: str}}></div>
    )
};

ReactDOM.render(
	<App />, document.getElementById('root');
)

 

 

사용 예시2

const App = () => {
    const markup = () => {
    	return {__html : 'Hello'}
    };
    
    return(
    	<div dangerouslySetInnerHTML={markup()}></div>
    )
};

ReactDOM.render(
	<App />, document.getElementById('root');
)

 

 

간단히 알아보는 XXS

사이트 간 스크립팅 공격 or 크로스 사이트 스크립팅 (XSS, cross-site scripting)

  • 웹 애플리케이션의 취약점 중 하나
  • 관리자가 아닌 이가 페이지에 악성 스크립트를 삽입할 수 있는 취약점
    • 예를 들면.. 게시판에 악성 스크립트가 담긴 글을 올려 사이트를 공격 할 수 있음
  • 악성 스크립트를 통해 해커가 사용자의 정보(쿠키, 세션등)를 탈취하거나 비정상적인 기능을 수행
  • 주로 다른 웹 사이트와 정보를 교환하는 식으로 작동하므로 사이트 간 스크립팅이라고 함
  • 취약점을 방지하기 위해서는 사용자의 입력 값을 검사하고 사용해야함

 


출처 & 참고

위키백과

ko.reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml

ko.wikipedia.org/wiki/%EC%82%AC%EC%9D%B4%ED%8A%B8_%EA%B0%84_%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8C%85

+ Recent posts