20201020

React 17 — 변화보다 업그레이드 루틴

React 17 정식 나왔다. 공식 블로그 제목부터 "No New Features". 동의한다. 유저 페이싱 기능 변화는 거의 없음. 대신 업그레이드 루틴을 정리하고 18 준비(concurrent)를 위한 토대를 닦는 릴리스. 실제 작업한 기록.

실제 중요한 변화

1. 이벤트 위임 타겟이 document → root. 16까지는 모든 합성 이벤트(onClick 등)가 document에 붙어 있었다. 이게 여러 React 트리가 한 페이지에 공존할 때(마이크로프론트엔드, 위젯 삽입) 이벤트 가로채기 순서가 꼬이는 원인. 17은 ReactDOM.render(el, rootEl)의 rootEl에 이벤트가 붙는다.

실제 이득: 레거시 코드(jQuery)와 React가 섞인 페이지에서, jQuery에서 e.stopPropagation()했는데도 React가 잡아버리던 현상이 사라진다. 또 17 + 16 트리를 한 페이지에 섞어 돌리는 "부분 업그레이드"가 가능해진다. 우리는 레거시 관리자 내부에 일부 페이지만 React 화했던 적이 있어서 이 변경이 반갑다.

2. 새 JSX 트랜스폼. 16까지는 컴포넌트 파일마다 import React from 'react'가 필수(JSX가 React.createElement로 변환되므로). 17 + Babel 7.9(@babel/preset-react 7.9.0 이상) 또는 TS 4.1부터 automatic runtime 지원. 빌드 타임에 필요한 함수만 자동 import.

// babel.config.js
module.exports = {
  presets: [
    ['@babel/preset-react', { runtime: 'automatic' }]
  ]
}

번들 사이즈 미세 감소(프로젝트 크기에 비례해서 몇 KB). 더 큰 건 개발 편의. 우리 레포에 import React from 'react'가 800번 나오던 거 전부 codemod로 제거 가능.

npx react-codemod update-react-imports

3. useEffect cleanup 타이밍. 16까지는 언마운트 시 cleanup이 동기적으로 실행 → 화면이 잠깐 잠김. 17부터는 렌더 이후 비동기로. 무거운 cleanup(구독 해제, 타이머 정리)이 UI를 막지 않음. 대신 cleanup 완료 전에 다음 렌더가 일어날 수 있으므로 cleanup 내부에서 mutate하는 코드는 다시 한번 확인.

4. 일관된 error handling. componentDidMount/componentDidUpdate의 에러가 unmount를 강제하도록 변경. 이전에는 에러 후에도 컴포넌트가 일부 남아서 crash가 퍼지는 경우 있었음.

업그레이드 과정

우리 프로젝트는 react-scripts 3.x(CRA)였고 17 쓰려면 react-scripts 4.x로 동반 올림. eslint, babel, webpack, postcss 쪽 peer dep이 쫙 바뀐다.

npm i react@17 react-dom@17 react-scripts@4
# peer dep 경고가 과격하면
npm i --legacy-peer-deps

그 외 실수하기 쉬운 포인트.

  • enzyme 어댑터가 17 대응이 늦어서 문제. 공식 enzyme-adapter-react-17이 아직 unofficial. 우리는 이 기회에 @testing-library/react로 전환 시작. 테스트 코드 꽤 많이 갈아엎어야 함
  • 사용하던 외부 라이브러리 peer deps: react-router 5.x OK, redux/react-redux 7.2 OK, material-ui 4.x OK(5는 별도 마이그 계획). react-dnd는 11 이상 필요
  • 클래스 컴포넌트에서 UNSAFE_componentWillMount 사용 중이면 경고. strict mode면 더 요란함. 이 기회에 리팩터

개발 경험 변화

  • HMR(react-refresh)은 react-scripts 4에서 기본. fast-refresh가 안정적이고 상태 보존이 좋다
  • Strict Mode 이중 렌더링: 18 concurrent 준비로 effect의 순수성을 확인하는 진단 모드. 17도 영향 있으니 개발 환경에서 켜서 잡아내는 게 좋다
  • TypeScript 4.1 + automatic JSX runtime이 깔끔. tsconfig의 "jsx": "react-jsx"

성능 실측

우리 admin SPA(Redux + react-router, 120개 라우트) 기준.

  • 초기 번들: 412KB → 408KB (automatic jsx 효과로 -4KB)
  • TTI(로컬): 1.92s → 1.88s. 유의미한 차이 아님. "기능은 없다"는 공식 발표 그대로
  • HMR 반영 속도: 1.4s → 0.9s(react-refresh 적용 효과)

결론

17은 18(concurrent features) 가는 정거장. 지금 업그레이드 해둬야 18 전환 비용이 줄어든다. 체감은 애매하지만 "ImportReact 전부 제거", "enzyme → RTL 전환", "Strict Mode 켜서 잠재 버그 훑어보기" 이 세 가지를 할 명분이 생기는 게 크다. 팀 코드베이스 정화 핑계로 삼자.

남은 숙제: webpack 4 → 5, material-ui 4 → 5 베타 둘 다 큰 건이라 다음 분기로 밀어둠.

댓글 없음: