반응형

React가 구성 요소를 파괴하지 않는지 확인하는 방법

 

 

어떤 구성 요소에 문제가 있는지 알게 되면 문제를 훨씬 쉽게 해결할 수 있습니다.

 

Unsplash의 Error 420 📷 사진

React가 변경되지 않은 구성 요소를 다시 렌더링하지 않도록 하는 것이 일반적입니다. React가 인스턴스를 완전히 파괴하고 렌더링할 때마다 처음부터 다시 마운트하는 때를 식별하는 방법을 알고 있습니까?

내가 작업하고 있는 최신 React 기반 텍스트 편집기를 디버깅할 때 각 렌더에서 마운트 해제 및 마운트되는 React 구성 요소를 식별해야 했습니다. 내가 경험한 특정 동작은 편집할 단락 사이를 전환할 때 편집기에서 두 번째 클릭이 필요하다는 것입니다. 첫 번째 클릭은 onBlur를 제대로 실행했지만 편집할 다음 단락에 초점을 맞추지 않았습니다.

처음에는 성능 문제라고 생각하고 브라우저의 성능 탭을 사용하여 작업을 프로파일링하기 시작했습니다. 그만큼 onBlur 이벤트는 내용을 저장하고 텍스트를 다시 구문 분석했으며 큰 파일에 대해 >1000ms가 걸렸습니다.

우리는 성능과 우리가 관리하는 몇 가지 종속성을 최적화하는 데 며칠을 보내고 <500ms로 줄였습니다. 괜찮았어야 했지만 ~50ms밖에 걸리지 않는 작은 파일에서는 여전히 동작이 지속되었습니다. 이렇게 많은 작업이 들어갔습니다. 적어도 앱이 더 반응적이기 때문에 지금은 낭비되지 않았습니다.

버그를 수정하기 위한 두 번째 시도는 React 18의 새로운 기능을 시도하는 것이었습니다. startTransition. 이를 통해 코드 블록을 래핑하고 현재 렌더링에 필수가 아닌 것으로 지정할 수 있습니다.

구성 요소에는 업스트림 저장 전에 이미 적절한 편집 내용이 적용되어 있어야 하므로 내 시나리오에 이상적입니다. 효과가 있었다! 글쎄, 일종의. 클릭하면 새 단락에 초점을 맞출 수 있지만 이제 지연된 렌더링이 발생하면 종료됩니다. 다시 렌더링하기 전에 ~500ms에서 ~200ms로, 초점을 잃기 전에 ~300ms로 나눕니다. 드로잉 보드로 돌아갑니다!

이전 단계에서 배운 것은 구성 요소가 아무리 빨리 렌더링되더라도 여전히 초점을 잃고 있다는 것입니다. 성능 문제가 아니었습니다. 내 의심은 구성 요소가 파괴되고 재사용되지 않는다는 것입니다. 나는 조사를 시작했고 컴포넌트에서 키 속성을 언제 어떻게 사용하는지에 대한 재렌더링을 줄이기 위한 많은 팁과 기타 팁을 찾았습니다. 그들 중 누구도 일하지 않았습니다.

어떤 구성 요소가 마운트 해제되었는지 확인한 다음 그렇지 않을 때 마운트하는 방법을 찾아야 했습니다.

여기 온다 useEffect() 구출에! 우리가 가질 때 useEffect 빈 종속성 배열을 사용하여 첫 번째 렌더링에서만 실행되도록 합니다.

을 추가하다 console.log() 첫 번째 렌더링임을 알려주는 메시지와 함께 예상하지 못한 메시지가 표시되면 비올라! 현재 렌더링 중에 기존 구성 요소가 파괴된다는 것을 알고 있습니다.

마운트 해제 이벤트가 발생하는 시점을 결정하기 위해 두 번째를 추가합니다. console.log() ~로 useEffect 반환 블록에서. 반환 블록 useEffect 마운트 해제할 때 코드를 정리하기 위한 것입니다. 이러한 로그는 마운트 해제 및 마운트가 발생할 때를 보여줍니다.

이것을 의심되는 각 구성 요소에 추가하여 어떤 구성 요소가 파괴되고 재생성되는지 확인할 수 있었습니다. 이제 변경 사항으로 인해 구성 요소가 적절하게 재사용되는 시점을 확실히 알 수 있습니다.

useEffect(() => {
if (verbose) console.log(‘ComponentName: Mount/First Render’);
return (() => {
if (verbose) console.log(‘ComponentName Unmount/Destroyed’);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

이 작업을 수행하는 다른 방법이 있을 수 있지만 이것은 실망스러운 버그를 해결하는 데 정말 도움이 되었습니다!

렌더링이 예상되는 경우에만 발생하도록 이를 더 많은 구성 요소에 추가하는 것을 단순화하기 위해 위의 코드만 포함된 사용자 지정 후크를 만들었습니다. 구성 요소 이름과 자세한 플래그에 대해 두 개의 props가 전달됩니다. 그렇게 하면 디버깅할 때만 로그를 켤 수 있습니다.

Unsplash의 Patrick Tomasso의 사진

결국 내 편집자의 포커스 버그에 대한 수정은 한 줄의 변경이었습니다. 내 구성 요소 중 일부에서 props에 전달되는 구성 요소를 렌더링하고 있었습니다. 좀 더 구체적으로 말하자면, props 제공 구성 요소로 덮어쓰여진 몇 가지 기본 구성 요소가 있었습니다.

const components = {
...DEFAULT_PROPS.components,
...props.components,
};

내 첫 번째 시도는 그것을 포장하는 것이 었습니다. useDeepCompareMemo() ~로부터 use-deep-compare React의 버전을 제공하는 라이브러리 useEffect, useCallback, useMemo 종속성에서 개체와 배열을 심층적으로 비교할 수 있습니다.

하지만 props로 전달된 컴포넌트를 아무리 메모해 놓아도 문제가 해결되지 않았습니다. 그것은 해서는 안 되는 메시지를 여전히 인쇄하고 있었습니다.

const components = useDeepCompareMemo(() => (
{ ...DEFAULT_PROPS.components, ...props.components }
), [props.components]); 
// props.components appeared to still be new each time

구성 요소를 병합하고 제공된 소품을 사용하는 논리로 인해 상위 구성 요소는 각 렌더링에서 사용자 정의 구성 요소 기능이 새롭다고 생각하게 되었습니다. 나는 그들이 결코 변하지 않기를 바랐다. 이 문제를 해결하기 위해 해당 정의를 다음으로 래핑했습니다. useMemo() 빈 종속성으로 첫 번째 렌더링에서만 정의되었는지 확인하기 위한 배열입니다.

const components = useMemo(() => (
{ ...DEFAULT_PROPS.components, ...props.components }
), []); 
// if non-empty dependencies, components render from scratch each time

이제 빈 종속성 배열을 사용했으므로 useMemo() 병합을 래핑하면 내 콘솔에 로그가 없습니다. 더 이상 구성 요소를 처음부터 파괴하고 재구축할 필요가 없습니다. 이 얼마나 아름다운 광경인가!

이것의 가장 좋은 점은 각 렌더에서 마운트 해제 및 마운트를 최소화하면 이미 최적화된 500ms 미만의 큰 파일을 ~250ms까지 렌더링할 수 있다는 것입니다!

조사하는 동안 필요하지 않은 JSX 블록을 메모화하려는 몇 가지 방법도 찾았습니다. 나는 그것들을 제거하고 구성 요소의 반환 블록에서 JSX를 렌더링했습니다.

나는 React가 내 도움 없이 언제 이 블록을 다시 렌더링할지 관리한다는 것을 깨달았습니다. 이것은 변경으로 인해 추가 렌더링이 발생하지 않는다는 것을 확실히 알았기 때문에 가능했습니다.

버그를 수정하는 데 며칠을 보내는 것은 답답할 수 있으며 솔루션은 작은 변경이었습니다. 명목상의 영향은 있지만 근본 문제를 해결하지 못하는 코드를 최적화하는 데 많은 시간을 할애할 수 있습니다.

React가 언제 각 렌더에서 구성 요소의 인스턴스를 파괴하고 다시 생성하는지 식별할 수 있는 것이 중요합니다.

내가 도움이 된 간단한 접근 방식은 React의 useEffect() 부부가있는 빈 의존성 배열 console.log() 마운트 및 마운트 해제 이벤트가 발생할 때 인쇄할 행. 어떤 구성 요소에 문제가 있는지 알게 되면 문제를 훨씬 쉽게 해결할 수 있습니다.

반응형
  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기