본문 바로가기
일/개발, IT정보

새로운 개발의 시작 React v18.0 / 이전 버전과의 차이점

 

 

네트워크와 브라우저, 웹 개발 기술이 발전하며 웹페이지들은 갈수록 복잡해지고 동적으로 변화하고 있습니다.

 

이 과정 속에서 수 많은 이벤트 핸들러와 DOM의 관계가 그 변화와 함께 더 복잡해졌습니다.

 

이를 해결하기 위해 Ember, Backbone, AngularJS 등의 프레임워크가 등장해, 자바스크립트의 특정 값이 바뀌면 특정 DOM의 속성이 바뀌도록 연결을 해주어서, 업데이트 하는 작업을 간소화해주었습니다.

 

하지만 이들은 무거운 프레임워크였고, 이러한 시대 흐름에 맞게 등장한 리액트는 이에 비해 간단하고 가벼운 라이브러리로서 인기를 끌기 시작했습니다.

 

이러한 이벤트 핸들러와 DOM간의 복잡한 문제를 해결하기 위해, 리액트는 어떠한 상태가 바뀌었을 때,

그 상태에 따라 DOM을 어떻게 업데이트할지 규칙을 정하는 것이 아니라, 아예 다 날려버리고 처음부터 모든걸 새로 만들어서 보여주자는 아이디어에서 시작됐습니다.

 

그렇기에 리액트는 복잡한 UI의 코드를 컴포넌트 단위로 나눌 수 있도록 했고,

이는 재사용성과 관리 측면에서도 프로그래머들에게 상당한 편리함을 제공했습니다.

 

컴포넌트 단위로 UI를 구성하고, 업데이트가 있다면 새로 만들어서 보여주기 위해 리액트는 Virtual DOM, 즉 가상 DOM이라는 것을 사용합니다.

 

가상 DOM과 실제 DOM을 비교해 업데이트 된 부분이 있다면 그 부분만을 다시 렌더링해서 보여주게 됩니다.

 

이를 통해 빠른 렌더링이 가능하고 사용자에게 앱을 사용하듯 편리하고 직관적인 UI를 제공하게 되었습니다.

 

이외에도 리액트는 SSR(서버사이드렌더링)환경을 지원해 SEO(검색엔진최적화) 등에서도 장점을 가지며,

웹 개발의 미래라고 불릴 정도로 리액트는 그 영향력을 계속해서 키워나가고 있습니다.

 

리액트가 나온지가 언젠데 벌써 버전이 18이라고? 싶으신 분들도 계실 것 같습니다.

 

리액트의 버전 변화를 간략히 나타내면 위와 같습니다.

 

2013년에 0.3.0버전으로 시작해, 2016년에 0.14.7버전이 릴리즈되었습니다.

이 시점에서 개발자들은 버전 1.0.0이 없는 이유에 대해 지속적으로 리액트팀에 물었고 이에 리액트팀이 말했습니다.

“왜 그렇게 버전 1에 집착하는거야. 버전이 그 시스템이 얼마나 성장해있고 안정적인지에 대한 지표야? 우리는 0.14.x 이전 버전에서도 충분히 안정적이고 좋아서 프로덕션에서도 인기가 많았잖아. 정 그러면 우리 다음 버전은 15버전으로 할게!”

 

그리고 정말로 3달정도가 지난 시점에서 리액트의 다음 버전은 15.0.0으로 릴리즈되었습니다.

이후 2017년에 16버전, 2020년에 17버전, 그리고 최근인 올해 3월에는 18버전이 릴리즈 되었습니다.

 

현재 프론트엔드 분야에서는 리액트 18버전에 대한 관심과 인기가 굉장히 많은데요,

그 이유는 여러가지가 있겠습니다만, 릴리즈 노트만 보더라도 18버전의 변경 사항이 상당합니다.

 

( 리액트 릴리즈 노트 : https://github.com/facebook/react/blob/main/CHANGELOG.md )

 

그럼 본격적으로 리액트 18버전의 주목할만한 변화에 대해서 간략하게 소개해드리겠습니다.

 

 

그 첫 번째로 Suspense입니다.

 

Suspense는 16버전부터 존재했으나 18버전부터 정식적으로 지원되기 시작했고, 무엇보다 SSR과 연계되며 더욱 강력해졌습니다.

 

Suspense는 Data fetching에 쓰이며 코드를 더 쉽고 아름답게 쓸 수 있도록 해줍니다.

 

위 이미지의 왼쪽 코드가 기존에 리액트에서 데이터를 가져와 상태(state)로 가지고, 그를 바탕으로 렌더링을 하던 코드였습니다.

 

프론트엔드 개발자들은 이를 개선하기 위해 SWR이나 React Query같은 Data fetching 라이브러리를 사용해 오른쪽과 비슷한 코드를 작성했습니다.

 

데이터가 불려왔는지 아닌지에 따라 분기하여 다른 컴포넌트를 렌더링 되도록 하는 것에서 특히 기존보다 편리했습니다.

 

하지만 리액트 팀은 이러한 방식에 의문을 품었습니다.

 

Loading 상태와 컴포넌트에 대한 로직이 실제 표현하고 싶은 컴포넌트의 코드 안에 있는 것은 옳지 않다고 생각했습니다.

 

리액트의 매력 중 하나가 코드를 위에서부터 아래로 차례로 읽어가며 이해하기 쉽다는 것인데, 이는 그렇지 않았다는겁니다.

 

그래서 리액트팀은 React Suspense라는 개념을 도입해, 이제는 실제 컴포넌트 안에서 Loading과 관련된 것들에 대해 신경쓰지 않아도 되도록 개선했습니다.

 

이를 통해 컴포넌트 코드를 쓰고 읽기가 편해지고, Data flow를 이해하기도 훨씬 직관적이게 바뀌었습니다.

 

Suspense는 사실 Client Side Rendering보다 Server Side Rendering과 함께 더욱 강력합니다.

 

SSR에서, 어느 한 컴포넌트가 무거우면 서버에서 그를 렌더링할 동안 유저는 화면에서 아무것도 볼 수 없으나,

Suspense를 사용하면, 그것을 제외하고 보여주다가 렌더링이 끝나면 로딩 컴포넌트를 Streaming HTML을 통해 원래 보여주고 싶었던 컴포넌트의 HTML로 대체가 가능하게 됐습니다.

 

또한 이러한 렌더링 순서에 대한 조작이 가능해졌기 때문에, 더욱 사용자의 입장에서 보기 편하고 사용하기에 좋고, 빠릿하게 느껴지는 웹페이지를 개발할 수 있게 되었습니다.

 

리액트 18의 주목할만한 변화 두 번째는 Server Component입니다.

 

서버 컴포넌트는 서버에서만 존재하는 리액트 코드로, 백엔드, 즉 서버에서 실행됩니다.

덕분에 무거운 라이브러리나 패키지에 의존하는 컴포넌트의 경우, 다운로드와 연산을 유저에게 시키지 않고 서버에서 수행한 후 결과값만을 유저에게 전달할 수 있습니다.

 

서버에서는 패키지와 라이브러리를 다운 받아두고 계속해서 재사용하면 되기 때문에, 유저입장에서 다운로드 해야하는 자바스크립트 코드를 대폭 줄일 수 있고 심지어 zero bundle size까지도 실현할 수 있게 되었습니다.

 

SSR과 무엇이 다르냐 하실 수 있는데, pages 단위로 관리해야했던 SSR과는 달리 이를 그 하위의 컴포넌트 단위로 제어가 가능해졌기 때문에,

그 하위 컴포넌트에서 data fetching을 직접 접근을 한다거나 제어를 할 수 있게 되면서, 컴포넌트 중심적으로 개발을 하고자 하는 리액트의 원래 개념과도 더욱 잘 맞게 되었습니다.

 

서버 컴포넌트가 가지는 또 하나의 장점은 DB와 직접 커뮤니케이션도 가능하다는 것입니다.

클라이언트측에 코드가 존재하는 것이 아니라 서버에 존재하고 그 결과값만을 클라이언트측에 전달하기때문에 보안적인 문제도 해결되었습니다.

리액트 컴포넌트에서 SQL쿼리를 실행할 수 있게 된 것입니다.

 

서버 컴포넌트와 클라이언트 컴포넌트의 분리는 파일명으로 가능합니다.

.server를 붙이면 서버컴포넌트, .client를 붙이면 클라이언트컴포넌트, 아무것도 붙이지 않으면 범용컴포넌트가 됩니다.

 

 

 

현재 리액트 18에선 서버컴포넌트는 프리뷰단계이지만, hydrogen이나 Next.js에서 alpha를 통해 이를 지원하기 시작했고, 추후 리액트는 마이너 릴리즈를 통해 서버 컴포넌트를 정식 배포할 것으로 예고했습니다.

 

이것이 정식으로 릴리즈되고 나면 프론트엔드 개발에 또 하나의 큰 변화가 생길 것 같습니다.

 

세 번째는 Automatic Batching입니다.

 

Automatic Batching은 여러 상태 업데이트를 하나의 리렌더링으로 처리해 성능을 향상시키는 방법입니다.

 

기존엔 한 동작에 의해 여러 상태값을 바꿔야할 경우, 변경되는 상태의 갯수만큼 리렌더링을 발생시켰습니다.

이는 평소엔 큰 퍼포먼스 차이가 없을 수 있어도, API 통신이나 무거운 함수와 연계될 때 특히 성능저하의 요인중 하나가 됩니다.

 

리액트 18버전에선 이를 한 번에 자동으로 처리하도록 변경되었으며, 원한다면 이미지의 오른쪽 아래 코드처럼 flushSync를 사용해 기존처럼 여러번 리렌더링도 가능합니다.

 

네 번째는 Transition입니다.

 

이제 리액트에게 어떤 업데이트가 급하고 어떤 업데이트는 급하지 않은지 리액트에게 알려줌으로써 렌더링 순서를 제어할 수 있게 되었습니다.

 

이미지의 예시와 같이 검색창과 자동완성 검색리스트를 띄운다고 할 때, 사용자는 텍스트 입력은 즉각적으로 나타나도록 기대하고 있고 그 결과는 그만큼까지는 즉각적으로 나타나지 않아도 된다고 기대하고 있습니다.

이 때 검색창의 텍스트는 긴급한 업데이트, 자동완성 리스트는 긴급하지 않은 업데이트로 구분하여, 긴급한 업데이트를 먼저 처리하면서 사용자로 하여금 더 좋은 사용성을 느끼게끔 할 수 있습니다.

 

이러한 업데이트 지연은 startTransition을 사용하여 가능하며, 여기엔 복잡한 렌더링 작업이나 API통신을 통한 업데이트를 하기에 적합합니다.

 

다섯 번째로 useDefferedValue입니다.

 

이제 이를 통해 DOM트리에서 급하지 않은 부분의 리렌더링을 지연할 수 있습니다.

 

우리가 흔히 아는 debounce와 비슷하지만, 고정된 지연시간이 없으므로 동적으로 이전 급한 작업들이 완료되면 리렌더링을 시작합니다.

 

여섯번째로는 useId가 있습니다.

 

참고로 여기서의 id는 리스트의 각 요소별 독립된 id를 뜻하는 것은 아닙니다.

(그건 데이터 자체에 존재해야한다는 것이 리액트팀의 입장입니다.)

 

useId는 SSR 환경에서, 서버와 클라이언트 양쪽 모두에게 안정적인 컴포넌트 ID를 생성할 수 있도록 도와줍니다.

이 때, id는 컴포넌트 부모자식 관계에 해당하는 트리 구조로 unique한 id를 차례로 할당합니다.

 

지금까지 제가 설명드린 것들 이외에도 undefined가 이제 오류를 뱉어내지 않고 렌더링이 가능해졌다던가,

unmount시 메모리 정리 개선으로 메모리 사용량이 최적화되었다던가 하는 여러 개선점이 있습니다.

 

관심있으신 분들은 리액트 공식 블로그를 참조하시면 될 것 같습니다.

리액트 공식 블로그 : https://reactjs.org/blog/2022/03/29/react-v18.html