ReactNextCentral

리액트 고급 기법

Published on
이 장에서는 리액트 컴포넌트에서 외부 시스템과 동기화하고 정보를 "기억"하는 고급 기법을 다루며, 커스텀 훅을 활용하여 로직을 재사용하는 방법을 소개합니다.

일부 컴포넌트는 리액트 외부 시스템을 제어하고 동기화해야 할 수도 있습니다. 예를 들어, 브라우저 API를 사용하여 입력란에 포커스를 맞추거나, 리액트를 사용하지 않고 구현된 비디오 플레이어를 재생하고 일시정지하거나, 원격 서버로부터 메시지를 연결하고 수신하기 위해 컴포넌트를 제어해야 할 수 있습니다. 이 장에서는 리액트를 벗어나 외부 시스템에 연결할 수 있는 탈출구를 배우게 됩니다. 애플리케이션의 대부분의 로직과 데이터 흐름은 이러한 기능에 의존하지 않아야 합니다.

이 장에서는

Ref를 사용하여 값 참조하기

컴포넌트가 일부 정보를 "기억"하길 원하지만, 해당 정보가 새로운 렌더링을 트리거하지 않도록 하려면 ref를 사용할 수 있습니다.

const ref = useRef(0);

ref는 상태와 마찬가지로 리액트에 의해 리렌더링 간에 보관됩니다. 그러나 상태를 설정하면 컴포넌트가 리렌더링됩니다. ref를 변경하면 그렇지 않습니다! ref의 현재 값을 ref.current 속성을 통해 액세스할 수 있습니다.

ref는 리액트가 추적하지 않는 컴포넌트의 비밀 포켓과 같습니다. 예를 들어, ref를 사용하여 timeout ID, DOM 요소 및 컴포넌트의 렌더링 출력에 영향을 주지 않는 다른 객체를 저장할 수 있습니다.

Ref를 사용하여 DOM 조작하기

리액트는 렌더 출력과 일치하도록 자동으로 DOM을 업데이트하므로 컴포넌트는 종종 DOM을 직접 조작할 필요가 없습니다. 그러나 때로는 리액트에 의해 관리되는 DOM 요소에 액세스해야 할 수도 있습니다. 예를 들어, 노드에 포커스를 맞추거나 해당 노드로 스크롤하거나 크기와 위치를 측정하기 위해 DOM 요소에 액세스해야 할 수 있습니다. 리액트에서 이러한 작업을 수행하는 내장 방법은 없으므로 DOM 노드에 대한 ref가 필요합니다. 예를 들어, 버튼을 클릭하면 ref를 사용하여 입력란에 포커스를 맞출 수 있습니다.

효과와 동기화하기

일부 컴포넌트는 외부 시스템과 동기화해야 할 수 있습니다. 예를 들어, 리액트 상태에 따라 리액트 외부의 비-리액트 컴포넌트를 제어하거나, 서버 연결을 설정하거나, 컴포넌트가 화면에 표시될 때 분석 로그를 보내고 싶을 수 있습니다. 이벤트 핸들러가 특정 이벤트를 처리하는 데 사용되는 반면, 효과는 렌더링 후에 코드를 실행할 수 있습니다. 리액트 외부 시스템과 컴포넌트를 동기화하기 위해 효과를 사용하세요.

Play/Pause 버튼을 몇 번 눌러보고 isPlaying 속성 값에 따라 비디오 플레이어가 동기화되는 것을 확인하세요:

많은 효과는 "정리(clean up)"도 수행합니다. 예를 들어, 채팅 서버에 연결을 설정하는 효과는 컴포넌트를 해당 서버에서 연결 해제하는 정리 함수를 반환해야 합니다.

개발 중에 리액트는 효과를 추가로 한 번 실행하고 정리합니다. 이것이 "✅ Connecting..."이 두 번 출력되는 이유입니다. 이렇게 함으로써 정리 함수를 구현하지 않도록 잊지 않도록 합니다.

효과가 필요하지 않을 수도 있습니다

효과는 리액트 패러다임을 벗어나는 탈출구입니다. 효과를 사용하면 리액트를 벗어나 외부 시스템과 컴포넌트를 동기화할 수 있습니다. 외부 시스템이 없는 경우(예: 일부 props 또는 상태가 변경되었을 때 컴포넌트의 상태를 업데이트하려는 경우) 효과가 필요하지 않을 수 있습니다. 불필요한 효과를 제거하면 코드를 이해하기 쉽고 실행 속도가 빨라지며 오류가 적어집니다.

효과가 필요하지 않은 두 가지 일반적인 경우가 있습니다.

  • 렌더링을 위해 데이터를 변환할 필요가 없는 경우
  • 사용자 이벤트를 처리할 필요가 없는 경우

예를 들어, 다른 상태에 기반하여 일부 상태를 조정해야 하는 경우 효과가 필요하지 않습니다.

function Form() {
  const [firstName, setFirstName] = useState('Taylor');
  const [lastName, setLastName] = useState('Swift');

  // 🔴 피해야 할: 중복된 상태와 불필요한 효과
  const [fullName, setFullName] = useState('');
  useEffect(() => {
    setFullName(firstName + ' ' + lastName);
  }, [firstName, lastName]);
  // ...
}

대신 렌더링하는 동안 최대한 많이 계산하세요:

function Form() {
  const [firstName, setFirstName] = useState('Taylor');
  const [lastName, setLastName] = useState('Swift');
  // ✅ 좋은 방법: 렌더링 중에 계산됨
  const fullName = firstName + ' ' + lastName;
  // ...
}

그러나 외부 시스템과 동기화하기 위해서는 효과가 필요합니다.

반응형 효과의 라이프사이클

효과는 컴포넌트의 라이프사이클과 다른 라이프사이클을 가지고 있습니다. 컴포넌트는 마운트(mount), 업데이트(update), 언마운트(unmount)될 수 있습니다. 효과는 무언가를 동기화하기 시작하고 나중에 그 동기화를 중단하는 두 가지 작업만 할 수 있습니다. 이러한 사이클은 효과가 시간이 지나면서 변경되는 props 및 state에 의존하는 경우 여러 번 발생할 수 있습니다.

이 효과는 roomId props의 값에 의존합니다. props는 반응형 값이므로 다시 렌더링할 때 변경될 수 있습니다. roomId이 변경되면 효과가 다시 동기화되고(서버에 다시 연결됨)입니다.

리액트는 효과의 종속성이 올바르게 지정되었는지 확인하기 위해 린터 규칙을 제공합니다. 위의 예제에서 종속성 목록에 roomId을 지정하지 않고 빼먹으면 린터가 해당 버그를 자동으로 찾아줍니다.

이벤트와 효과를 분리하기

공사 중
이 섹션은 안정 버전의 리액트에서 아직 출시되지 않은 실험적인 API에 대해 설명합니다.

이벤트 핸들러는 동일한 상호작용을 다시 수행할 때만 다시 실행됩니다. 그러나 효과는 이전 렌더링과 달리 props 또는 state와 같은 읽은 값이 다를 경우 다시 동기화됩니다. 때로는 일부 값에는 해당 동작을 원하지만 다른 값에는 원하지 않는 동작의 혼합이 필요할 수 있습니다.

효과 내의 모든 코드는 반응적입니다. 반응적인 값을 읽는 경우 해당 값이 리렌더링으로 인해 변경된 경우 다시 실행됩니다. 예를 들어, 이 효과는 roomId 또는 theme 값이 변경된 경우에만 다시 채팅에 연결합니다.

이것은 이상적이지 않습니다. theme을 변경하더라도 채팅에 다시 연결하고 싶지 않을 것입니다! 코드에서 theme을 읽는 부분을 효과 이벤트로 이동하여 문제를 해결하세요:

효과 이벤트 내의 코드는 반응적이지 않으므로 theme을 변경해도 효과가 다시 연결되지 않습니다.

효과 종속성 제거하기

효과를 작성할 때, 린터는 효과가 읽은 모든 반응형 값(예: props 및 state)을 효과의 종속성 목록에 포함했는지 확인합니다. 이를 통해 효과가 컴포넌트의 최신 props 및 state와 동기화되도록 보장합니다. 불필요한 종속성은 효과를 너무 자주 실행하거나 무한 루프를 생성할 수 있습니다. 종속성을 제거하는 방법은 상황에 따라 다릅니다.

예를 들어, 이 효과는 입력을 편집할 때마다 options 객체가 다시 생성되는 경우에 의존합니다.

채팅이 채팅에서 메시지를 입력할 때마다 다시 연결되지 않기를 원하지 않습니다. 이 문제를 해결하려면 options 객체의 생성을 효과 내부로 이동하여 효과가 roomId 문자열에만 의존하도록 만드세요:

options 종속성을 제거하기 위해 종속성 목록에서 options 종속성을 삭제하고 시작하는 것은 잘못된 방법입니다. 대신, 종속성이 더 이상 필요하지 않게 된 주변 코드를 변경하여 문제를 해결합니다. 종속성 목록을 효과 코드에서 사용하는 모든 반응형 값의 목록으로 생각하세요. 목록에 무엇을 넣을지 일부러 선택하지 않습니다. 목록은 코드를 설명합니다. 종속성 목록을 변경하려면 코드를 변경하세요.

커스텀 훅으로 로직 재사용하기

리액트는 useState, useContext, useEffect와 같은 내장된 Hooks를 제공합니다. 때로는 더 구체적인 목적을 위한 Hook이 있으면 좋을 것 같은데 예를 들어 데이터를 가져오거나 사용자 온라인 여부를 추적하거나 채팅방에 연결하기 위한 Hook 등입니다. 이를 위해 애플리케이션의 필요에 맞게 커스텀 훅을 만들 수 있습니다.

이 예제에서는 usePointerPosition 커스텀 훅이 커서 위치를 추적하고, useDelayedValue 커스텀 훅이 특정 시간(밀리초)만큼 전달한 값보다 "느린" 값을 반환합니다. 미리보기 영역 위에 커서를 움직여 커서를 따라 이동하는 점들의 이동 경로를 확인하세요:

커스텀 훅을 만들고, 함께 조합하고, 데이터를 전달하며, 컴포넌트 간에 재사용할 수 있습니다. 앱이 커짐에 따라 커스텀 훅을 직접 작성하는 대신 이미 작성한 커스텀 훅을 재사용할 수 있게 되므로 효과를 수작업으로 작성하는 일이 점점 줄어들게 될 것입니다. 리액트 커뮤니티에서도 많은 훌륭한 커스텀 훅이 유지되고 있습니다.

다음에 알아보기

Refs: 값 참조

Ref는 리액트 컴포넌트에서 렌더링과 무관한 정보를 저장하는데 사용되며, 이 문서에서는 ref의 활용과 주의사항에 대해 설명하고 있습니다.

View

Refs: DOM 조작

리액트에서는 refs를 사용하여 DOM 노드에 액세스하고 조작하는 방법을 설명하는 문서입니다.

View

Effect: 동기화

리액트의 효과(Effect)를 사용하여 컴포넌트를 외부 시스템과 동기화하고, 효과와 이벤트의 차이, 효과를 선언하는 방법, 그리고 효과가 필요하지 않을 때 처리하는 방법에 대해 설명합니다.

View

Effect: 필요성 검토

리액트의 Effect 훅을 사용하여 외부 시스템과 동기화할 때 필요성을 검토하고, 비용이 많이 드는 계산을 최적화하며, 이벤트 핸들러와 로직을 공유하는 방법 등에 대해 설명합니다.

View

Effect: 라이프사이클

효과(Effect)는 리액트 컴포넌트의 라이프사이클과는 다르게, 데이터 동기화를 시작하고 중지하는 역할을 하는데 사용되며, 올바른 종속성 설정을 통해 최신 데이터를 유지합니다.

View

Effect: Event를 분리

리액트에서 이벤트 핸들러와 효과(Effect)의 차이를 설명하고, 언제 어떤 것을 선택해야 하는지, 이펙트 코드 일부를 반응형으로 만들지 않는 방법, 그리고 이펙트를 사용하여 최신 프롭과 상태를 읽는 방법에 대해 다룹니다.

View

Effect: 종속성 제거

리액트의 이펙트에서 불필요한 종속성을 식별하고 제거하는 방법을 안내합니다.

View

사용자 정의 훅: 로직 재사용

커스텀 훅을 작성하고 활용하여 리액트 컴포넌트 간에 로직을 공유하는 방법을 소개합니다.

View