[TIL] 230410 - 리액트 공식 문서 (Ref 전달하기 ~ 성능 최적화)
2023-4-10
- TIL
- Ref 전달하기
- Fragment
- 고차 컴포넌트
- 다른 라이브러리와 통합하기
- JSX 이해하기
- 성능 최적화
리액트 공식 문서 사내 스터디에 참여하고 있다. 인강이나 책으로 리액트를 학습했다고 해도, 리액트는 공식 문서가 잘 되어있어서 공식 문서 학습을 많이들 추천한다고 한다. 고급 안내서까지 읽어보니 딥한 내용들도 많이 접하게 되어 좋다.
다만 공식 문서에는 아직 클래스형 컴포넌트를 이용한 예제가 많이 나와서 내용이 머리에 바로 바로 흡수되지가 않는다. 공식문서는 최근 리뉴얼되어 베타 버전이 출시 되어 있어, 베타 버전 공식문서를 조금씩 참고하였다.
이전 문서들은 스터디를 참여하였으나 따로 정리하면서 읽진 않았다. 이번에는 좀 더 확실히 문서를 읽기 위해 TIL로 정리하며 읽는다. (4월 10일의 TIL이지만 4월 6일부터 학습하였다.)
범위
- Ref 전달하기
- Fragment
- 고차 컴포넌트
- 다른 라이브러리와 통합하기
- 성능 최적화
Ref 전달하기
const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton">
{props.children}
</button>
));
// 이제 DOM 버튼으로 ref를 직접 받을 수 있습니다.
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
ref
전달은 컴포넌트를 통해 자식 중 하나에ref
를 자동으로 전달하는 기법- 예를들면 특정
input
,button
태그에 포커스를 해야 하는 상황이 될 수 있겠다. - 부모 컴포넌트에서
createRef
를 사용하여ref
를 생성하고, 자식 컴포넌트에서forwardRef
를 이용하여 부모 컴포넌트에서 생성한ref
를 전달받아 특정dom
요소에ref
를 지정한다. forwardRef
를 사용은 주목할만한 변경사항이다. 사이드 이펙트가 발생할 가능성이 높아짐. 따라서 조건부로 실행하지 않는 것을 권장한다.ref
는prop
이 아니다.key
와 마찬가지로ref
는 React에서 다르게 처리한다.- 일반
prop
에ref
값을 담아서 전달할수도 있다. forwardRef
사용 시 DevTools에 forwardRef로 나타난다. forwardRef에 이름이 있는 렌더링 함수를 전달하면 ForwardRef(myFunction)와 같이 노출됨.
Fragment
render() {
return (
<>
<ChildA />
<ChildB />
<ChildC />
</>
);
}
- Fragments는 DOM에 별도의 노드를 추가하지 않고 여러 자식을 그룹화할 수 있다.
- Fragments에 key가 있다면
<React.Fragment>
문법으로 명시적으로 선언해야 한다. - key는 Fragment에 전달할 수 있는 유일한 어트리뷰트이다.
고차 컴포넌트
const EnhancedComponent = higherOrderComponent(WrappedComponent);
- 컴포넌트 로직을 재사용하기 위한 React의 고급 기술. React API의 일부가 아니며, React의 구성적 특성에서 나오는 패턴.
- 컴포넌트는
props
를 UI로 변환하는 반면에, 고차 컴포넌트는 컴포넌트를 새로운 컴포넌트로 변환합니다. - 규모가 큰 애플리케이션에서 DataSource를 구독하고
setState
를 호출하는 동일한 패턴이 반복적으로 발생한다고 가정해봅시다. 그렇게 된다면 이 로직을 한 곳에서 정의하고 많은 컴포넌트에서 로직을 공유할 수 있게 하는 추상화가 필요하게 됩니다. 이러한 경우에 고차 컴포넌트를 사용. - HOC는 변경(mutation)대신에 입력 컴포넌트를 컨테이너 구성요소로 감싸서 조합(composition)을 사용.
- 단일 인수 고차 컴포넌트는 Component => Component 특징을 가지고 있다. 출력 타입이 입력 타입과 동일한 함수는 정말 쉽게 조합할 수 있다.
주의사항
- render 메서드 안에서 고차 컴포넌트를 사용하지 말것. 매번 새로 렌더링됨.
- 컴포넌트의 정의 바깥에 HOC를 적용하여 컴포넌트가 한 번만 생성되도록 함.
- static 메서드는 반드시 따로 복사
이해 안됨
컨벤션: 래핑된 컴포넌트를 통해 관련없는 Props 전달하기 - 고차 컴포넌트는 컴포넌트에 기능을 추가합니다. 고차 컴포넌트는 정의(contract)를 과감하게 변경해서는 안됩니다. 고차 컴포넌트에서 반환된 컴포넌트는 래핑된 컴포넌트와 비슷한 인터페이스가 있어야합니다.
render() {
// 이 HOC에만 해당되므로 추가된 props는 걸러내어 이 HOC에 전달되지 않도록 합니다.
const { extraProp, ...passThroughProps } = this.props;
// 이 Props는 일반적으로 Status값 또는 Instance method 입니다.
const injectedProp = someStateOrInstanceMethod;
// wrapped component에 props를 전달합니다.
return (
<WrappedComponent
injectedProp={injectedProp}
{...passThroughProps}
/>
)
}
이번 학습 중 고차 컴포넌트 부분이 가장 어려웠다. 고차 컴포넌트 파트는 나중에 다시 읽어봐야겠다.
다른 라이브러리와 통합하기
- React는 React의 외부 DOM에서 일어나는 변화를 인식하지 못함. 다른 라이브러리에서 DOM을 다루면 React는 알 수가 없다.
- 다른 라이브러리와 통합하는 것이 어렵다는 것은 아니다. 몇가지만 염두에 두면 된다.
- 충돌을 피하는 가장 쉬운 방법은 React 컴포넌트가 업데이트되지 않게 막는 것입니다. React가 업데이트할 필요가 없는 빈
<div>
같은 요소를 렌더링하면 된다. - React는 일반적으로 시작 시에 단일 루트 React 컴포넌트를 DOM에 로드하는 데 사용되지만
createRoot()
는 앱처럼 크거나 버튼처럼 작은 UI의 독립적인 부분에 대해 여러 번 호출 할 수 있다. (Facebook에서 React를 사용하는 방식) - 위 방식으로 기존 프로젝트에 순차적으로 React를 적용하고 이후
ReactDOM.createRoot()
호출을 계층 구조 상위로 옮길 수 있다.
createRoot()
사용에 대한 내용이 가장 와 닿았다. React로 사이트를 새로 구축하는 것이 불가능하다면 운영하고 있는 사이트에 조금씩 적용해 볼 수 있겠다.
JSX 이해하기
- 근본적으로, JSX는
React.createElement(component, props, ...children)
함수에 대한 문법적 설탕을 제공할 뿐입니다. - JSX 내에서도 점 표기법을 사용하여 React 컴포넌트를 참조할 수 있습니다. 이 방법은 하나의 모듈에서 복수의 React 컴포넌트들을 export 하는 경우에 편리하게 사용.
const MyComponents = {
DatePicker: function DatePicker(props) {
return <div>Imagine a {props.color} datepicker here.</div>;
}
}
function BlueDatePicker() {
return <MyComponents.DatePicker color="blue" />;
}
- 리액트 컴포넌트는 반드시 대문자로 시작해야한다.
- React element 타입에 일반적인 표현식은 사용할 수 없다. element 타입을 지정할 때 일반적인 표현식을 사용하고자 한다면 대문자로 시작하는 변수에 배정한 후 사용.
- props의 기본값은
true
- props에 해당하는 객체를 이미 가지고 있다면, 스프레드 연산자를 사용해 전체 객체를 그대로 넘겨줄 수 있다. 스프레드 연산자는 유용하지만 불필요한 prop을 컴포넌트에 넘기거나 유효하지 않은 HTML 속성들을 DOM에 넘기기도 하니 꼭 필요할 때만 사용하는 것을 권장한다.
function App2() {
const props = {firstName: 'Ben', lastName: 'Hector'};
return <Greeting {...props} />;
}
- React 컴포넌트는 element로 이루어진 배열을 반환할 수 있다.
render() {
// 리스트 아이템들을 추가적인 엘리먼트로 둘러쌀 필요 없습니다!
return [
// key 지정을 잊지 마세요 :)
<li key="A">First item</li>,
<li key="B">Second item</li>,
<li key="C">Third item</li>,
];
}
false
,null
,undefined
와true
는 유효한 자식입니다. 그저 렌더링 되지 않을 뿐이다. 아래와 같은 경우에 유용함. (0
과 같은 falsy 값들은 렌더링 됨)
<div>
{showHeader && <Header />}
<Content />
</div>
성능 최적화
앱을 개발할 때는 개발 모드를, 사용자에게 앱을 배포할 때는 프로덕션 모드를 사용해야 한다. 개발모드에서는 React에서 코드를 체크해 경고 해주는 기능이 있기 때문에 React를 더 크고 느리게 만든다.
애플리케이션에서 긴 목록(수백 또는 수천행)을 렌더링하는 경우 ‘windowing’이라는 기법을 사용할 것을 추천. 이 기법은 주어진 시간에 부분 목록만 렌더링하여, 컴포넌트를 다시 렌더링하는 데 걸리는 시간과 생성된 DOM 노드의 수를 크게 줄일 수 있다.
- 널리 알려진 windowing 라이브러리: react-window, react-virtualized
몇몇 상황에서 컴포넌트를 업데이트할 필요가 없다는 것을 알고 있다면
shouldComponentUpdate
에서false
를 반환하여 전체 렌더링 프로세스를 건너뛰게 할 수 있다. 대부분의 경우shouldComponentUpdate()
를 직접 작성하는 대신React.PureComponent
에서 상속 받을 수 있습니다. 그것은 현재와 이전의prop
과state
의 얕은 비교로shouldComponentUpdate()
를 호출하는 것과 같다.- 최신 React에서는 함수형 컴포넌트 사용을 권장한다. 함수형 컴포넌트에서는 컴포넌트를
memo
로 감싸는 것으로 위와 같은 처리를 할 수 있다. 참고 memo
는PureComponent
와 다르게 new state와 old state를 비교하지 않는다. 함수형 컴포넌트에서는memo
를 사용하지 않아도,set
함수를 호출했을때 state가 같다면 기본적으로 리렌더링을 막도록 되어있다.
- 최신 React에서는 함수형 컴포넌트 사용을 권장한다. 함수형 컴포넌트에서는 컴포넌트를
memo
는 얕은 비교를 수행한다. 때문에, 배열이나 객체 내부의 값이 변경되었을때는 컴포넌트가 리렌더링 되지 않기 때문에memo
사용 시 주의해야한다.데이터의 불변성을 지켜줘야함
리액트의 state 변화 감지 기준은 "콜스택의 주소값"이다. 리액트는 콜스택의 주소값만을 비교하여 상태 변화를 감지한다.이를 "얕은 비교"라고 한다. 리액트의 빠른 state 변화 감지를 할 수 있도록 해주는 장점이자, 불변성을 지켜야하는 이유이다. 원시타입의 변화의 메모리 영역값이 변경하지 않는, 불변성을 유지한채로 새로운 메모리 영역에서 변경된 값이 저장 되기 때문에 콜스택 의 주소값의 변화가 감지되지만, 참조 타입은 콜스택에 메모리 힙의 주소만을 저장하고, 값은 메모리 힙에 저장,변경되기 때믄에 참조 타입의 값을 변경하면 콜스택의 주소값은 변경이 없어 react는 state의 변경이 없다고 감지하기 때문에 변경된 state는 재랜더링되지 않는다.
windowing이라는 표현이 낯설지만 우선 긴 목록을 렌더링 할때 성능 문제가 있다면 이 단어를 떠올릴 것 같다.
memo
에 대한 내용은 좀 더 깊게 파보고 싶다.효율적인 프로덕션 필요를 위한 Brunch, Browserify, Rollup에 대한 설치 방법들이 나왔는데, 정확히 어떤 기능을 하는지는 나와있지 않았다. 이런 것들이 있다는 것은 알았으니 이후 운영에 성능 이슈가 있다면 해당 패키지들을 떠올려 볼 수 있겠다.
수정이 필요한 부분 혹은 더 나은 방법을 알고계신가요?
댓글로 알려주시면 저에게 큰 도움이 됩니다! 😊💜