useEffect(setup,dependencies?)
import { useEffect } from 'react';
import { createConnection } from './chat.js';
function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [serverUrl, roomId]);
// ...
}
import { useEffect } from 'react';
import { createConnection } from './chat.js';
function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [serverUrl, roomId]);
// ...
}
function useChatRoom({ serverUrl, roomId }) {
useEffect(() => {
const options = {
serverUrl: serverUrl,
roomId: roomId
};
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [roomId, serverUrl]);
}
function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');
useChatRoom({
roomId: roomId,
serverUrl: serverUrl
});
// ...
non-React widget의 경우 Effect를 사용하여 state를 React 구성 요소의 현재 상태와 일치시키는 메서드를 호출 할 수 있다.
import { useState, useEffect } from 'react';
import { fetchBio } from './api.js';
export default function Page() {
const [person, setPerson] = useState('Alice');
const [bio, setBio] = useState(null);
useEffect(() => {
let ignore = false;
setBio(null);
fetchBio(person).then(result => {
if (!ignore) {
setBio(result);
}
});
return () => {
ignore = true;
};
}, [person]);
// ...
Effect 내에서 fetch로 데이터를 가져오는 것은 클라이언트 측 앱에서 인기 있는 방법이다. 그러나 이것은 매우 수동적인 접근 방식이며 상당한 단점이 있다.
이 단점들은 React에만 국한되지 않는다. 모든 라이브러리를 사용하여 마운트 시 데이터를 가져오는 데 적용된다. 라우팅과 마찬가지로 데이터 가져오기가 쉽지 않으므로 다음과 같은 접근 방식을 권장한다
function ChatRoom({ roomId }) { // This is a reactive value
const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // This is a reactive value too
useEffect(() => {
const connection = createConnection(serverUrl, roomId); // This Effect reads these reactive values
connection.connect();
return () => connection.disconnect();
}, [serverUrl, roomId]); // ✅ So you must specify them as dependencies of your Effect
// ...
}
종속성을 제거하려면 린터가 종속성일 필요가 없음을 증명해야 한다. 예를 들어 serverUrl 구성 요소 밖으로 이동하여 반응하지 않고 다시 렌더링해도 변경되지 않음을 증명할 수 있다.
const serverUrl = 'https://localhost:1234'; // Not a reactive value anymore
function ChatRoom({ roomId }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
}, [roomId]); // ✅ All dependencies declared
// ...
}
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const intervalId = setInterval(() => {
setCount(count + 1); // You want to increment the counter every second...
}, 1000)
return () => clearInterval(intervalId);
}, [count]); // 🚩 ... but specifying `count` as a dependency always resets the interval.
// ...
}
반응 값 이므로 count를 종속성 목록에 지정해야 한다. 그러나 이로 인해 변경될 때마다 Effect가 정리되고 다시 설정된다. 이 문제를 해결하기 위해 setState를 다음과 같이 전달한다 prev => prev + 1
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const intervalId = setInterval(() => {
setCount(c => c + 1);
}, 1000)
return () => clearInterval(intervalId);
}, []);
// ...
}
effect가 렌더링 중에 생성된 객체 또는 함수에 의존하는 경우 필요 이상으로 자주 실행될 수 있다.
const serverUrl = 'https://localhost:1234';
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
const options = { // 🚩 This object is created from scratch on every re-render
serverUrl: serverUrl,
roomId: roomId
};
useEffect(() => {
const connection = createConnection(options); // It's used inside the Effect
connection.connect();
return () => connection.disconnect();
}, [options]); // 🚩 As a result, these dependencies are always different on a re-render
// ...
그렇기에 렌더링 중에 생성된 개체를 종속성으로 사용하지 말고 Effec 내부에 객체를 만든다.
import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';
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]);
return (
<>
<h1>Welcome to the {roomId} room!</h1>
<input value={message} onChange={e => setMessage(e.target.value)} />
</>
);
}
export default function App() {
const [roomId, setRoomId] = useState('general');
return (
<>
<label>
Choose the chat room:{' '}
<select
value={roomId}
onChange={e => setRoomId(e.target.value)}
>
<option value="general">general</option>
<option value="travel">travel</option>
<option value="music">music</option>
</select>
</label>
<hr />
<ChatRoom roomId={roomId} />
</>
);
}
렌더링 중에 생성된 함수를 종속성으로 사용하지 말고 대신 Effect 내부에 선언한다.
import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';
const serverUrl = 'https://localhost:1234';
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
useEffect(() => {
function createOptions() {
return {
serverUrl: serverUrl,
roomId: roomId
};
}
const options = createOptions();
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [roomId]);
return (
<>
<h1>Welcome to the {roomId} room!</h1>
<input value={message} onChange={e => setMessage(e.target.value)} />
</>
);
}
export default function App() {
const [roomId, setRoomId] = useState('general');
return (
<>
<label>
Choose the chat room:{' '}
<select
value={roomId}
onChange={e => setRoomId(e.target.value)}
>
<option value="general">general</option>
<option value="travel">travel</option>
<option value="music">music</option>
</select>
</label>
<hr />
<ChatRoom roomId={roomId} />
</>
);
}
앱에서 서버 렌더링을 사용하는 경우 구성 요소가 서로 다른 두 환경에서 렌더링된다. 서버에서 렌더링하여 초기 HTML을 생성하고 클라이언트에서 React는 렌더링 코드를 다시 실행하여 해당 HTML에 이벤트 핸들러를 연결할 수 있다. 이것이 잘 작동하려면 초기 렌더 출력이 클라이언트와 서버에서 동일해야 한다.
그러나 클라이언트에 다른 콘텐츠를 표시해야 하는 경우가 있을 수 있다. 그런 경우 이와 같이 구현한다
function MyComponent() {
const [didMount, setDidMount] = useState(false);
useEffect(() => {
setDidMount(true);
}, []);
if (didMount) {
// ... return client-only JSX ...
} else {
// ... return initial JSX ...
}
}
Strict Mode가 켜져 있으면 React는 실제 설정 전에 한 번 더 설정 및 정리를 실행한다. 이는 Effect의 로직이 올바르게 구현되었는지 확인하는 스트레스 테스트이다.
Effect가 무한 주기로 실행되는 경우 다음 두 가지가 참이어야 한다.
정리 기능은 마운트 해제 중뿐만 아니라 종속성이 변경된 모든 재렌더링 전에 실행된다.