(React) 2. React 사용 후기 - 이해해보기

김동우·2021년 7월 25일
0

wecode

목록 보기
18/32
post-thumbnail

시리즈 순서가 꼬였습니다.

이전 글은 해당 링크 에서 봐주세요.


잠깐! 시작하기 전에

이 글은 wecode에서 실제 공부하고(이제 사전 스터디는 아닙니다.), 이해한 내용들을 적는 글입니다. 글의 표현과는 달리 어쩌면 실무와는 전혀 상관이 없는 글일 수 있습니다.

또한 해당 글은 다양한 자료들과 작성자 지식이 합성된 글입니다. 따라서 원문의 포스팅들이 틀린 정보이거나, 해당 개념에 대한 작성자의 이해가 부족할 수 있습니다.

설명하듯 적는게 습관이라 권위자 발톱만큼의 향기가 날 수 있으나, 엄연히 학생입니다. 따라서 하나의 참고자료로 활용하시길 바랍니다.

글의 내용과 다른 정보나 견해의 차이가 있을 수 있습니다.
이럴 때, 해당 부분을 언급하셔서 제가 더 공부할 수 있는 기회를 제공해주시면 감사할 것 같습니다.


서론

React를 사용하고, 나름대로 이것저것 알아본 것을 정리한 글입니다.

생각보다 공식문서가 친절해서 내용을 설명하는 것은 의미가 없으니 제 생각을 자유롭게 기술하겠습니다.

React - 재조정

위 링크를 참고하시면 보다 필요한 지식을 습득하실 수 있으니, 굳이 제 포스팅을 읽지 않으셔도 될 것 같습니다.

그럼 시작하겠습니다.

React를 이해해보자.

React는 바닐라에 비해 비교적 친절한 경고를 해줍니다.

하향식 개발에 몰두해 놓칠 수 있는 먼 미래의 개선사항까지도 사전에 경고해주고, 원치 않는 상황이 발생할 수 있다는 걱정이 많은 라이브러리입니다.

즉, 우리가 가지고 있는 다양한 문제점들을 바로 잡아내는 깐깐하지만 손해볼 일 없게 만들어주는 친구입니다.

1. 사용자를 걱정한다?

React는 기존 바닐라와는 상당히 다른 방식으로 사고하는 것을 요구합니다.

완성된 상태의 정보를 받는다는 표현을 사용하는 만큼 하향식에서 부모-자식 state 설계는 쉽지 않고, props의 경우도 어떻게 걸러 전달할지 사용자가 깊게 고민해야 합니다.

때로는 직접 짠 구조에 결함이 생겨 계층의 위로 역주행하며 방해사항을 제거해야 하는 경우도 있습니다.

이런 상황은 정말 어지럽기만 합니다.

지금 당장의 문제를 해결하기 위해서는 그런 방식이 번거롭고 불편합니다. 스트레스도 상당하고요.

그럼에도 React는 완성된 이후도 함께 고민해준다는 점에서 상당히 지능적인 라이브러리입니다.

Array.map()-key 예시를 생각해보면, 제 생각에 React는 어쩌면 사용자를 염려하는 것 같습니다.

힘들었던 설계과정을 거쳤는데, 이후 이런 문제가 해당 층에서 발생했을 때 다시 그 과정을 반복할 셈이냐? 하고 물어보는 것 같다는 생각이 듭니다.

undefined, null에 해당하는 상황은 경고 자체가 스트레스지만요 😭

이제 칭찬은 여기까지 하고, React라는 친구가 어떤 생각을 하고 있는지, 왜 그렇게 행동하는지를 바닥에서 생각해봅시다.

2. 극한의 효율

먼저, React는 효율을 우선적으로 생각합니다.

그렇기에 React에 존재하는 render() 메소드는 정말 훌륭한 생각인 것 같습니다.

전체를 reRender 하는 것이 아닌, 변경사항을 정확히 판단하고 다시 그려낸다는 사실은 기존 바닐라에 비하면 혁명에 가까운 수준입니다.

물론 애초에 설계 자체가 컴포넌트로 쪼개져 있어 그렇지만, render() 개념이 없었다면 컴포넌트 분리의 개념은 퇴색되었을거란 생각을 합니다.

다시 돌아와서, React는 웹의 전체를 통째로 바꾸는 것을 지향하지 않습니다.

그렇기에 각 컴포넌트를 따로 그려내야 하고, 변경사항이 존재하지 않는 컴포넌트에 대해서는 고려하지 않는 방식을 고수합니다.

이를 위해 기존의 노드 트리와 변경 이후의 노드 트리의 일치여부를 비교하고, 변경 결과를 빠르게 반영합니다.

이 과정을 최대한 빨리 진행하기 위해 React는 전체가 아닌, 변경점이 존재하는 컴포넌트의 위치에서 가장 변경 가능성이 있는 컴포넌트를 빠르게 탐색합니다.


1. 전체를 순회하며 변경 컴포넌트까지 도달하는 방식이 아니라 변경된 컴포넌트에서 시작해 하향식 탐색을 진행합니다.

2. 탐색 결과만 reRender 하는 방식을 통해 전체를 reRender 해야 하는 상황을 회피합니다.

여기까지는 정말 많이 들어본 말이고, React 사용자라면 누구나 다 아는 사실일니다.

그럼 아래 나름의 제한사항에 대해서도 한 번 생각해봅시다.

저의 조약돌만한 지식들을 열심히 모으면 아래와 같은 당연한 사실이 왜 그런지 나름 그럴듯한 이유를 댈 수 있을 것 같습니다.

  1. React는 state에 직접적으로 접근하는 것을 용인하지 않는다.

  2. React에서 부모 state에 자식이 접근하는 방법은 부모의 state를 변경하는 메소드를 자식에 props로 넘겨주어야 한다.

  3. key값은 고유하다.

이제 하나씩 왜 그래야만 하는지 생각해봅시다.

state 직접 접근 금지

React가 하향식으로 최적화를 진행한다면 state는 상당히 중요한 역할을 담당하고 있습니다.

이건 React reRender에 대한 그림 (https://www.oreilly.com/library/view/learning-react-native/9781491929049/ch02.html)에서 볼 수 있습니다.

React에서 state를 갖는 컴포넌트의 위치는 reRender, 변경된 노드 트리를 구현하는 데 있어 하나의 기준이 됩니다.

일례로, setState() 메소드 호출은 state 변경을 React가 감지하고, render 를 호출할 수 있게 합니다.

이처럼 React는 state 변경 확인과 reRender를 이용해 빠른 탐색과 변경이 가능한 하나의 솔루션을 구현했습니다.

그렇기에 직접 state 값만 슬쩍 변경하는 것을 절대 용인할 수 없게 됩니다.

솔루션 자체가 state 변경 - reRender 로 이루어져 있는데, state만 변경되고 render() 호출이 없다면 당연히 반쪽짜리 솔루션이 됩니다.

이는 React 제작자의 의도를 무시하는 행위가 아닐까 생각합니다.

부모의 state 변경은 부모에서

React는 state의 변경으로 해당 node를 정확히 알아냅니다.

과정에서 state 내의 정보를 props로 전달받은 하위 노드들 중 변경사항이 있는 노드를 확인합니다.

그런데 이 과정을 완벽히 수행하기 위해서는 변경된 state의 위치를 정확히 알아야 합니다.

또한 자식에서 부모의 state를 변경하는 경우, 해당 state를 활용하는 다른 형제 노드에게도 영향을 끼칠 수 있다는 표현이기도 합니다.

그러기 위해서는 state는 부모-자식 어디든 해당 컴포넌트만의 독자적인 데이터가 되어야 하며, 해당 데이터를 활용하는 다른 하위 컴포넌트들의 내부 state 선언에 props를 할당해주는 것이 아니라 props로 부모의 state를 변경하는 메소드를 전달받는 방식을 택해야만 합니다.

setState()를 부모에서 호출해야 이전 항목의 솔루션이 완벽하게 동작하기 때문이죠.

만약 자식에서 전달받은 props를 부모에서의 setState()가 아닌 방식으로 임의로 변경한다면 어떻게 될까요?

해당 변경 내용에 대한 render는 발생하지 않을거고, state를 공유하고 있는 형제들은 부모에서의 이벤트(함수 호출 등)가 발생하기 전까지 변경사항을 모르게 됩니다.

이는 마찬가지로 React가 원치 않음에도 비효율적인 방식을 고려해야 하는 또 하나의 이유가 됩니다.

즉, 제작자의 의도를 완전히 무시하는 행위죠.

key는 고유한 값이며, 필수적이다.

우리는 Array.map() 등의 메소드로 DOM을 만들어낼 때, key가 필요하다는 경고를 받게 됩니다.

이는 React가 트리를 비교하는 방법을 이용하고 있기 때문입니다.

state 변경 이후, 변경이 반영된 트리와 기존 트리를 비교하고 render() 과정에서 DOM element에 key가 존재하면 React는 어떤 점이 변경되었는지 빠르게 반영할 수 있습니다.

예시로, <li> element를 만들어야 하는 상황에서 우리는 Array.map()을 활용할 수 있습니다.

이 때, 기존 li들과 변경된 li들이 key값을 가지지 않는 상태에서 맨 앞에 요소를 추가해야 한다면, React는 비효율적인 방식으로 reRender 해야 합니다.

전체를 비교하고, 종속을 유지한 상태로 맨 앞 요소를 추가-> 이후 내용을 한칸씩 밀어내는 구조로 <ul> 태그 내부를 전부 reRender합니다.

단, key값을 보유하고 있다면 동일한 key 값을 가지는 요소들과 새 엘리먼트의 위치구조를 파악한 다음, 그냥 추가와 이동만 하면 됩니다.

내부 전체를 다시 그리는 비효율적인 작업을 요구하지 않게 되는 것이죠.

이는 React가 지향하는 극한의 효율에 영향을 끼치는 요구사항입니다.

알고리즘 성능에 대한 조건

여기는 그냥 알아만 두는 내용입니다.

React는 휴리스틱 알고리즘을 사용하는데, 이 알고리즘이 최대한의 성능을 내는데에는 2가지 조건이 붙습니다.

  1. 동일한 타입의 컴포넌트끼리만 트리를 비교하니, 비슷한 결과물을 도출하는 컴포넌트가 둘 이상 존재하게 된다면 같은 타입으로 만드는것을 추천한다.

  2. key는 무조건 고유하고, 유일한, 유니크한 값이어야만 한다. Math.random() 등의 메서드를 활용해서 render() 호출마다 매번 변하는 값을 할당할 경우, 자식 컴포넌트의 state가 유실되거나 전체 성능에 악영향을 끼친다.

우선 저에게 가장 와닿는건 2번인데, 2번이 왜 그런지도 알아보려면 한참 먼 이야기 같습니다.

공식문서가 쉬우면서도 상당히 심오한 개념이 많습니다. 😭

마치며

적당히 적고 넘어가려 했던 글이 생각보다 길어졌습니다.

위 글을 바탕으로 앞으로는 다양한 개념에 접근해보고자 합니다.

휴리스틱 알고리즘, 이진 탐색 트리 등의 개념은 앞으로 조금씩 깊게 봐야겠습니다.

또한 React 자체 개념에 대해서도 조금 더 공부해야 할 것 같습니다.

다음엔 아마도 render - componentDid~ 에 대한 구조를 뜯어볼까 합니다.

뭐 이전처럼 refactoring 과정에서 느낀점이 있으면 그걸 적기도 할 것 같습니다.

주말이니 개념에 충실한 하루를 보낼 수 있었네요.

그럼 이만 마치겠습니다. 읽어주셔서 감사합니다.

0개의 댓글