Removing Effect Dependencies
Effect 의존성 제거하기
- Effect는 의존성 배열의 반응형 값을 통해 컴포넌트의 최신 props와 state와 동기화 상태를 유지할 수 있다.
- 다만, 불필요한 의존성으로 Effect가 자주 실행되거나 무한 루프를 생성할 수 있다.
Dependencies should match the code
의존성은 코드와 일치해야 합니다.
- effect 내부에서 반응형 값을 참조하지만, 의존성 요소로 지정하지 않았을 경우 lint 에러가 발생한다.
To remove a dependency, prove that it’s not a dependency
의존성을 제거하려면 의존성이 아님을 증명하세요
- 의존성 요소는 선택이 불가능하다.
- 그러므로, Effect 내부의 반응형 값은 의존성에 선언되어야만 한다.
- 의존성을 제거하기 위해선 필요없음을 린터에게 ‘증명’해야 한다.
const serverUrl = 'https://localhost:1234';
const roomId = 'music'; // 더이상 반응형 값이 아님
function ChatRoom() {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
}, []); // ✅ All dependencies declared
// ...
}
To change the dependencies, change the code
의존성을 변경하려면 코드를 변경하세요
- 의존성 목록에서 제외하고 싶다면 Effect 내부의 반응형 값 참조 코드를 변경해야한다.
Removing unnecessary dependencies
불필요한 의존성 제거하기
- 의존성 배열의 모든 요소의 변경으로 매번 Effect가 재실행되기를 원하지 않을 것 이다.
Should this code move to an event handler?
이 코드를 이벤트 핸들러로 옮겨야 하나요?
Effect 내부에 들어가야만 하는 코드인지 체크한다.
function Form() {
const [submitted, setSubmitted] = useState(false);
const theme = useContext(ThemeContext);
useEffect(() => {
if (submitted) {
// 🔴 Avoid: Event-specific logic inside an Effect
post('/api/register');
showNotification('Successfully registered!', theme);
}
}, [submitted, theme]); // ✅ All dependencies declared
function handleSubmit() {
setSubmitted(true);
}
// ...
}
- 현재 테마에 따라 알림 메시지 스타일을 변경
- 폼을 제출한 뒤 테마를 전환하면 `theme`가 변경되고 Effect가 다시 실행되어 동일한 알림이 표시된다.
- 사용자 관점에서 이는 폼을 sumbit했을 때 발생해야하므로 로직을 핸들러 내부에 넣어야 한다.
function Form() {
const theme = useContext(ThemeContext);
function handleSubmit() {
// ✅ Good: Event-specific logic is called from event handlers
post('/api/register');
showNotification('Successfully registered!', theme);
}
// ...
}
Is your Effect doing several unrelated things?
Effect가 여러 관련 없는 일을 하고 있나요?
- 관련없는 fetch api가 서로에게 영향을 미치고 있다.
- country props를 기반으로 cities state를 네트워크에 동기화하려고 합니다.
- city state를 기반으로 areas state를 네트워크에 동기화하려고 합니다.
function ShippingForm({ country }) {
const [cities, setCities] = useState(null);
const [city, setCity] = useState(null);
const [areas, setAreas] = useState(null);
useEffect(() => {
let ignore = false;
fetch(`/api/cities?country=${country}`)
.then(response => response.json())
.then(json => {
if (!ignore) {
setCities(json);
}
});
// 🔴 Avoid: A single Effect synchronizes two independent processes
if (city) {
fetch(`/api/areas?city=${city}`)
.then(response => response.json())
.then(json => {
if (!ignore) {
setAreas(json);
}
});
}
return () => {
ignore = true;
};
}, [country, city]); // ✅ All dependencies declared
- 해결방법으로 각각 다른 Effect로 분리한다.
- 서로 다른 것을 동기화하므로 분리가 적합하다.
- 중복이 염려된다면 custom hooks로 추출한다.
Are you reading some state to calculate the next state?
다음 state를 계산하기 위해 어떤 state를 읽고 있나요?
- 새로운 메시지를 messages state에 업데이트하고 있다.
- messages가 의존성으로 추가됨으로 새 메시지가 올 때 마다 채팅이 다시 연결된다.
- 업데이터 함수를 사용해 새로운 배열로 업데이트하도록 변경한다
function ChatRoom({ roomId }) {
const [messages, setMessages] = useState([]);
useEffect(() => {
const connection = createConnection();
connection.connect();
connection.on('message', (receivedMessage) => {
setMessages(msgs => [...msgs, receivedMessage]);
});
return () => connection.disconnect();
}, [roomId]); // ✅ All dependencies declared
// ...
Do you want to read a value without “reacting” to its changes?
값의 변경에 ‘반응’하지 않고 값을 읽고 싶으신가요?
- isMuted가 변경될 때마다 Effect가 실행된다.
- isMuted의 변경에 반응하지 않도록 Effect event로 추출한다.
import { useState, useEffect, useEffectEvent } from 'react';
function ChatRoom({ roomId }) {
const [messages, setMessages] = useState([]);
const [isMuted, setIsMuted] = useState(false);
const onMessage = useEffectEvent(receivedMessage => {
setMessages(msgs => [...msgs, receivedMessage]);
if (!isMuted) {
playSound();
}
});
useEffect(() => {
const connection = createConnection();
connection.connect();
connection.on('message', (receivedMessage) => {
onMessage(receivedMessage);
});
return () => connection.disconnect();
}, [roomId]); // ✅ All dependencies declared
// ...
Wrapping an event handler from the props
props를 이벤트 핸들러로 감싸기
- 부모 컴포넌트가 렌더링할 때마다 다른 onReceiveMessage 함수를 전달한다고 가정
- 이벤트 핸들러는 부모 컴포넌트의 렌더링 마다 참조값이 변경되므로 Effect가 실행된다.
- onReceiveMessage props에 반응하지 않도록 Effect event로 추출한다.
As-is
function ChatRoom({ roomId, onReceiveMessage }) {
const [messages, setMessages] = useState([]);
useEffect(() => {
const connection = createConnection();
connection.connect();
connection.on('message', (receivedMessage) => {
onReceiveMessage(receivedMessage);
});
return () => connection.disconnect();
}, [roomId, onReceiveMessage]); // ✅ All dependencies declared
// ...
To-be
function ChatRoom({ roomId, onReceiveMessage }) {
const [messages, setMessages] = useState([]);
const onMessage = useEffectEvent(receivedMessage => {
onReceiveMessage(receivedMessage);
});
useEffect(() => {
const connection = createConnection();
connection.connect();
connection.on('message', (receivedMessage) => {
onMessage(receivedMessage);
});
return () => connection.disconnect();
}, [roomId]); // ✅ All dependencies declared
// ...
Separating reactive and non-reactive code
반응형 코드와 비반응형 코드 분리
- roomId props의 변경으로 방문을 로깅하고, 모든 로그에 notificationCount을 포함하고 싶다.
- 하지만, notificationCount 변경으로 로깅이 트리거되는 것을 원하지 않는다.
- notificationCount 반응하지 않도록 Effect event를 사용한다.
function Chat({ roomId, notificationCount }) {
const onVisit = useEffectEvent(visitedRoomId => {
logVisit(visitedRoomId, notificationCount);
});
useEffect(() => {
onVisit(roomId);
}, [roomId]); // ✅ All dependencies declared
// ...
}
Does some reactive value change unintentionally?
일부 반응형 값이 의도치 않게 변경되나요?
- options 객체는 컴포넌트 내부에 있으므로 반응형 값이다.
- 반응형 값은 의존성 요소로 지정해야한다.
- 그러나 input으로 message state를 업데이트할 경우 채팅 연결이 다시 동기화된다.
- options 객체는 매 렌더링마다 새로운 객채로 생성되기 때문에 채팅이 다시 연결되는 것이다.
- 결국 참조형 데이터의 특성으로 인해 발생하는 문제다.
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
const options = {
serverUrl: serverUrl,
roomId: roomId
};
useEffect(() => {
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [options]); // ✅ All dependencies declared
// ...
Move static objects and functions outside your component
정적 객체와 함수를 컴포넌트 외부로 이동
- 위의 문제를 해결하는 방법중 하나로 컴포넌트 외부로 options객체를 분리한다.
- 이 경우 의존성으로 선언하지 않아도 되기 때문이다.
const options = {
serverUrl: 'https://localhost:1234',
roomId: 'music'
};
function ChatRoom() {
const [message, setMessage] = useState('');
useEffect(() => {
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, []); // ✅ All dependencies declared
// ...
Move dynamic objects and functions inside your Effect
Effect 내에서 동적 객체 및 함수 이동
- 특정 반응형 값에 의존하는 경우 외부로 분리가 불가능한데, 이럴땐 Effect 내부로 위치시킨다.
- roomId는 객체나 함수가 아니기 때문에 의도치 않게 주소값 변경으로 인한 Effect가 재실행되지 않는다.
const serverUrl = 'https://localhost:1234';
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
useEffect(() => {
const options = {
serverUrl: serverUrl,
roomId: roomId
};
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [roomId]); // ✅ All dependencies declared
// ...
참조
https://react-ko.dev/learn/removing-effect-dependencies
'Programming > React' 카테고리의 다른 글
State management - 상태관리 (0) | 2024.03.09 |
---|---|
React 공식문서 정리 - 커스텀 훅으로 로직 재사용하기 (1) | 2023.12.29 |
React 공식문서 정리 - ref로 DOM 조작하기 (1) | 2023.12.19 |
배열 key props (0) | 2023.04.28 |
useState의 렌더링, 함수형 업데이트 (0) | 2023.04.28 |