2020년 10월 20일 React는 v17을 release했습니다.
8월 10일에 RC(Release Candidate)를 발표한지 두 달 만에 정식 버전의 출시네요.
글을 쓰는 지금은 2021년 5월입니다. 이렇게 늦게 글이 올라오는 이유는, 블로그 이전하면서 예전 글을 퍼오는 작업을 게을리했기 때문이에요.
현재는 v17.0.2
까지 release되었습니다.
2017년 9월 26일에 v16이 release된 이후 3년 만에 이루어진 Major Update인 만큼, 변경사항이 상당히 많습니다. 아래 이미지는 github의 Change log 입니다.
그러나 이러한 Change log만으로는 언뜻 눈에 들어오지 않기 때문에, React team은 변경사항에 관해 React Blog에 정리해서 적어두었습니다. 이 글에서는 해당 블로그의 글을 번역/정리해 보았습니다.
특징
No New Features
React v17에서는 개발자들이 사용할만한 새로운 기능이 추가되는 대신에, 리액트 자체를 더욱 쉽게 업그레이드 하는 것에 초점을 맞췄습니다. 이제 리액트의 어떤 버전이 관리하는 트리 안에 다른 버전의 리액트를 더욱 안전하게 삽입할 수 있고, 다른 기술로 구축한 App에 더욱 쉽게 내장할 수 있도록 합니다.
Gradual Upgrades
React 17은 점진적인 업그레이드를 가능하게 합니다. React 15에서 16으로 업그레이드를 하면 일반적으로 한 번에 전체 앱을 업그레이드 하게 되고 대부분의 경우 큰 문제없이 작동하지만, 코드베이스가 몇 년 전에 쓰여졌고 적극적으로 유지보수되지 않았다면 문제가 생길 가능성이 있었습니다. 물론 이전에도 여러 버전의 React를 사용할 수는 있었지만 이것은 이벤트 핸들링에 문제를 야기했습니다. 이 문제를 해결하기 위해 React는 점진적인 업그레이드를 지원하기로 결정했지만, 여전히 가장 좋은 것은 전체 앱을 한 번에 업그레이드 하는 것입니다.
Changes to Event Delegation
React 16을 포함한 이전 버전에서 Event는 document.addEventListener()
를 통해서 처리되었습니다. 하지만 React 17에서는 rootNode.addEventListener()
를 사용합니다.
이러한 변경점들로 인해서 React v17부터는 한 HTML에서 여러 개의 React가 dom을 생성하는 경우
와 다른 기술로 빌드된 앱 일부에 React를 적용하는 경우
에서 더욱 안전하게 React를 사용할 수 있게 해줍니다.
Gradual Upgrade항목에서 언급했던 이벤트 핸들링 문제를 해결하기 위한 대책이라고 볼 수 있겠습니다.
일반적으로는 Component에서 Event Handler를 작성하는 React 코드는 다음과 같습니다.
const handleButtonClick = () => {
console.log('Button Clicked!')
}
<버튼 onClick={handleButtonClick} />
이것은 결국 Vanilla JS에서 다음과 같은 형태로 변환될 것입니다.
button.addEventListener('click', handleButtonClick);
하지만 대부분의 Event에서 React는 실제로 Event를 선언하는 DOM에 Binding하지 않고 document에 직접 연결합니다.
이를 이벤트 위임 (Event Delegation)
이라고 부릅니다.
React는 처음 출시됐을 때부터, 내부적으로 모든 Event를 document에 위임해주고 있었고, 해당 document에서 Event가 발생하면 React event system
을 통해 실제 발생한 component를 찾고, Event Bubbling
을 통해 상위 컴포넌트로 전달합니다.
그러나 여러 개의 React가 존재하는 경우, 모두 하나의 document에 Event를 위임하게 되고, 이는 여러 Side Effect를 유발하게 되었습니다. 그것이 이번 React v17에서는 해결된 것입니다.
Other Breaking Changes
v17로 업그레이드를 할 때 React team은 Facebook product code에서 10만개 이상의 컴포넌트 중 20개 미만의 컴포넌트만을 변경했을 뿐입니다. 대부분의 앱도 마찬가지로 큰 문제 없이 업그레이드 할 수 있을 것으로 기대됩니다.
Effect cleanup timing
useEffect
를 사용할 때, cleanup timing을 보다 일관적으로 동작하도록 변경하였습니다.
useEffect(() => {
return () => {
// 이곳에서 cleanup이 이루어졌습니다. (componentWillUnmount Lifecycle과 동일)
}
}, [])
많은 경우에 rendering을 지연시킬 이유는 없으므로, useEffect
는 변경사항이 스크린에 반영된 직후에 async하게 동작하도록 변경되었습니다. 만일 동기적으로 동작하기를 바라는 경우에는 useLayoutEffect
를 사용하면 됩니다.
class형 component에서 사용되고 있는 componentWillUnmount
의 경우 동기적으로 동작합니다.
기존의 useEffect는 이와 마찬가지로 동작했기에 페이지 변경처럼 큰 페이지 전환 시 성능 저하를 유발했습니다.
이 변경 사항이 반영되면 화면이 업데이트 된 직후에 cleanup이 실행되어 성능을 개선할 수 있습니다.
또, React v17에서는 cleanup을 사용할 때 dom tree에 위치한 순서와 같은 순서대로 실행되도록 보장해줍니다.
Consistent Errors for Returning Undefined
기존에는 모든 컴포넌트에서의 Undefined Return은 항상 Error처리 되어왔으나, forwardRef
, memo
에 대해서는 그렇게 동작하지 않았는데요, React v17 부터는 이 또한 Error로 처리됩니다. 만일 아무 것도 Rendering하길 원하지 않는다면 return null
을 통해 해결할 수 있습니다.
Native Component Stacks
Browser에서 Error가 발생하면 함수 이름과 해당 위치를 알려주고 있습니다.
하지만 종종 JS Stack으로 React tree 구조를 파악하기에 충분하지 않은 경우가 있었습니다.
아마 당신은 Button
에서 Error가 발생했다는 사실이 아니라 어디에 있는 Button
에서 Error가 발생했는지가 궁금할 겁니다.
React v17에서는 Component stack이 JS stack과는 다르게 결합됩니다.
이것을 통해 개발환경에서 완전히 기호화된 React Component Stack tracing이 가능해졌습니다.
현재 브라우저에서는 함수의 Stack frame을 가져올 수 있는 방법이 없습니다.
따라서 React가 Error를 포착한 경우, 그것은 해당 Component 내부에서 임의의 Error를 throw하고 catch해서 Component stack을 재구성합니다.
결론
아무래도 Major upgrade인만큼 해당 upgrade를 위해서는 수많은 노력이 필요할 것으로 판단됩니다.
하지만 앞으로도 계속해서 React를 사용해 나갈 계획이 있다면, 추후에 제공될 API들을 매끄럽게 사용하기 위해서 React v17로의 upgrade는 필수적이라고 생각합니다.
다행히도 React v17은 하위 버전의 React가 상위 버전의 React로 upgrade하는 작업을 수월하게 도와주는 역할을 하는 upgrade입니다. Upgrade 과정을 비약적으로 단축시킬 수 있을지 아닐지는 직접 해봐야 알겠지만,
아무리 React team이 100,000개 Component를 대상으로 테스트를 해봤다고는 할지라도
, 상당히 개발자의 부담을 덜어주는 upgrade라는 생각이 들었습니다.