일부 컴포넌트는 React 외부의 시스템을 제어하고 동기화해야 할 수 있습니다. 예를 들어 브라우저 API를 사용해 input에 초점을 맞추거나, React 없이 구현된 비디오 플레이어를 재생 및 일시 정지하거나, 원격 서버에 연결해서 메시지를 수신해야 할 수 있습니다. 이 장에서는 React의 “외부”로 나가서 외부 시스템에 연결할 수 있는 탈출구를 배웁니다. 대부분의 애플리케이션 로직과 데이터 흐름은 이러한 기능에 의존해서는 안 됩니다.
이 장에서는
Ref로 값 참조하기
컴포넌트가 일부 정보를 “기억”하고 싶지만, 해당 정보가 렌더링을 유발하지 않도록 하려면 ref를 사용하세요.
const ref = useRef(0);
state처럼 ref는 다시 렌더링하는 사이에 React에 의해 유지됩니다. 다만 state의 설정은 컴포넌트가 다시 렌더링 되지만, ref의 변경은 그렇지 않습니다! ref.current
프로퍼티를 통해 해당 ref의 현재 값에 접근할 수 있습니다.
ref는 React가 추적하지 않는 컴포넌트의 비밀 주머니와 같습니다. 예를 들어 ref를 사용하여 컴포넌트의 렌더링 출력에 영향을 주지 않는 timeout ID, DOM 엘리먼트 및 기타 객체를 저장할 수 있습니다.
Ref로 DOM 조작하기
React는 렌더링 결과물에 맞춰 DOM 변경을 자동으로 처리하기 때문에 컴포넌트에서 자주 DOM을 조작해야 할 필요는 없습니다. 하지만 가끔 특정 노드에 포커스를 옮기거나, 스크롤 위치를 옮기거나, 위치와 크기를 측정하기 위해서 React가 관리하는 DOM 요소에 접근해야 할 때가 있습니다. React는 이런 작업을 수행하는 내장 방법을 제공하지 않기 때문에 DOM 노드에 접근하기 위한 ref가 필요할 것입니다. 예를 들어 버튼을 클릭하면 ref를 사용해 input에 포커스를 옮길 것입니다.
Effect로 값 동기화하기
일부 컴포넌트는 외부 시스템과 동기화해야 합니다. 예를 들어 React state에 따라 React가 아닌 컴포넌트를 제어하거나, 채팅 서버에 대한 연결을 설정하거나, 컴포넌트가 화면에 나타났을 때 분석 로그를 보낼 수 있습니다. 특정 이벤트를 처리하는 이벤트 핸들러와 달리 Effect는 렌더링 후 일부 코드를 실행합니다. 컴포넌트를 React 외부 시스템과 동기화할 때 이를 사용하세요.
Play/Pause를 몇 번 누르고 비디오 플레이어가 isPlaying
prop 값에 어떻게 동기화되는지 확인하세요.
많은 Effect는 스스로 “클린업”하기도 합니다. 예를 들어 채팅 서버에 대한 연결을 설정하는 Effect는 해당 서버에서 컴포넌트의 연결을 끊는 방법을 React에 알려주는 클린업 함수 를 반환해야 합니다.
개발 모드에서 React는 즉시 실행되고 Effect를 한 번 더 클린업합니다. 그래서 "✅ Connecting..."
이 두 번 인쇄되는 것입니다. 이렇게 하여 클린업 함수를 구현하는 것을 잊지 않도록 합니다.
Effect가 필요하지 않은 경우
Effect는 React 패러다임에서 벗어날 수 있는 탈출구입니다. Effect를 사용하면 React를 “벗어나” 컴포넌트를 외부 시스템과 동기화할 수 있습니다. 외부 시스템이 관여하지 않는 경우 (예를 들어 일부 props 또는 state가 변경될 때 컴포넌트의 state를 업데이트하려는 경우) Effect가 필요하지 않습니다. 불필요한 Effect를 제거하면 코드를 더 쉽게 따라갈 수 있고, 실행 속도가 빨라지며, 에러 발생 가능성이 줄어듭니다.
Effect가 필요하지 않은 두 가지 일반적인 경우가 있습니다.
- 렌더링을 위해 데이터를 변환하는 데 Effect가 필요하지 않습니다.
- 사용자 이벤트를 핸들링하는 데 Effect가 필요하지 않습니다.
예를 들어 다른 state에 따라 일부 state를 조정하는 데는 Effect가 필요하지 않습니다.
function Form() { const [firstName, setFirstName] = useState('Taylor'); const [lastName, setLastName] = useState('Swift'); // 🔴 Avoid: redundant state and unnecessary Effect const [fullName, setFullName] = useState(''); useEffect(() => { setFullName(firstName + ' ' + lastName); }, [firstName, lastName]); // ... }
대신에 렌더링하는 동안 가능한 한 많이 계산하세요.
function Form() { const [firstName, setFirstName] = useState('Taylor'); const [lastName, setLastName] = useState('Swift'); // ✅ Good: calculated during rendering const fullName = firstName + ' ' + lastName; // ... }
그러나 외부 시스템과 동기화하려면 Effects가 필요 합니다.
React Effect의 생명주기
Effect는 컴포넌트와 다른 생명주기를 가집니다. 컴포넌트는 마운트, 업데이트 또는 언마운트할 수 있습니다. 반면 Effect는 동기화를 시작하거나 후에 동기화를 중지하는 두 가지 작업만 할 수 있습니다. Effect가 시간에 따라 변하는 props와 state에 의존하는 경우 이 주기는 여러 번 발생할 수 있습니다.
다음 Effect는 roomId
prop의 값에 의존합니다. Props는 다시 렌더링할 때 변할 수 있는 반응형 값 입니다. roomId
가 변경되면 Effect가 다시 동기화 (및 서버에 다시 연결)합니다.
React는 Effect의 의존성을 올바르게 명시했는지 확인하는 린터 규칙을 제공합니다. 위의 예시에서 의존성 목록에 roomId
를 명시하는 것을 잊어버렸다면, 린터가 해당 버그를 자동으로 찾아낼 것입니다.
Effect에서 이벤트 분리하기
이벤트 핸들러는 같은 상호작용을 반복하는 경우에만 다시 실행됩니다. Effect는 이벤트 핸들러와 달리 prop이나 state 변수 등 읽은 값이 마지막 렌더링 때와 다르면 다시 동기화합니다. 때로는 두 동작이 섞여서 어떤 값에는 반응해 다시 실행되지만, 다른 값에는 그러지 않는 Effect를 원할 때도 있습니다. 이 페이지에서 그 방법을 알려드리겠습니다.
Effect 내의 모든 코드는 반응형 이며, 읽은 반응형 값이 다시 렌더링되는 것으로 인해 변경되면 다시 실행됩니다. 예를 들어 다음의 Effect는 roomId
또는 theme
이 변경되면 채팅에 다시 연결됩니다:
이것은 이상적이지 않습니다. roomId
가 변경된 경우에만 채팅에 다시 연결하고 싶습니다. theme
를 전환해도 채팅에 다시 연결되지 않아야 합니다! theme
를 읽는 코드를 Effect에서 Effect Event 로 옮기세요.
Effect Event 내부의 코드는 반응이 아니므로 theme
를 변경해도 더 이상 Effect가 다시 연결하지 않습니다.
Effect의 의존성 제거하기
Effect를 작성하면 린터는 Effect의 의존성 목록에 Effect가 읽는 모든 반응형 값(예를 들어 props 및 State)을 포함했는지 확인합니다. 이렇게 하면 Effect가 컴포넌트의 최신 props 및 State와 동기화 상태를 유지할 수 있습니다. 불필요한 의존성으로 인해 Effect가 너무 자주 실행되거나 무한 루프를 생성할 수도 있습니다. 이 가이드를 따라 Effect에서 불필요한 의존성을 검토하고 제거하세요.
예를 들어 다음 Effect는 사용자가 input을 편집할 때마다 다시 생성되는 options
객체에 의존합니다.
해당 채팅에 메시지를 입력할 때마다 채팅이 다시 연결되는 것을 원치 않을 것입니다. 이 문제를 해결하려면 Effect 내에서 options
객체를 생성하여 Effect가 roomId
문자열에만 의존하도록 하세요.
의존성 목록을 편집하여 options
의존성을 제거하지 않았음을 알 수 있습니다. 그것은 잘못된 방법일 것입니다. 대신 주변 코드를 변경함으로써 의존성을 불필요 하게 만들었습니다. 의존성 목록을 Effect의 코드에서 사용하는 모든 반응형 값의 목록으로 생각하세요. 이 목록에 무엇을 넣을 것인지 의도적으로 선택하는 것이 아닙니다. 이 목록은 당신의 코드를 설명합니다. 의존성 목록을 변경하려면, 코드를 변경하세요.
커스텀 Hook으로 로직 재사용하기
React는 useState
, useContext
, 그리고 useEffect
같은 Hook들이 내장되어 있습니다. 때로는 데이터를 가져오거나 사용자가 온라인 상태인지 여부를 추적하거나 대화방에 연결하는 등 조금 더 구체적인 목적을 가진 Hook이 존재하길 바랄 수도 있습니다. 이를 위해 애플리케이션의 필요에 따라 자신만의 Hook을 만들 수 있습니다.
이번 예제에서는 usePointerPosition
커스텀 Hook은 커서 위치를 추적하는 반면 useDelayedValue
커스텀 Hook은 전달한 값보다 특정 밀리초만큼 “지연”된 값을 반환합니다. 샌드박스 미리보기 영역 위로 커서를 이동하면 커서를 따라 움직이는 점의 흔적을 확인할 수 있습니다.
커스텀 Hook을 생성하고, 함께 구성하고, 서로 데이터를 전달하고, 컴포넌트 사이에서 재사용할 수 있습니다. 앱이 성장함에 따라 이미 작성한 커스텀 Hook을 재사용할 수 있으므로 직접 작성하는 Effect의 수가 줄어들 것입니다. 또한 React 커뮤니티에서 관리하는 훌륭한 커스텀 Hook이 많습니다.
다음은 무엇인가요?
이 장을 한 페이지씩 읽어보려면 Ref로 값 참조하기로 이동하세요!