effect 안에서 호출할 수 있으면서도, event handler와 같이 동작하는 것
특정하게 의존성을 지정하지 않아도 안에 상태가 들어있으면 해당 상태에 의해 호출되는 형식인듯 하다. 그래서 useEffect에서의 의존성을 지울 수 가 있다..?!
import { useState, useEffect } from 'react';
import { experimental_useEffectEvent as useEffectEvent } from 'react';
export default function Timer() {
const [count, setCount] = useState(0);
const [increment, setIncrement] = useState(1);
const onTick = useEffectEvent(() => {
setCount(c => c + increment);
});
useEffect(() => {
const id = setInterval(() => {
onTick();
}, 1000);
return () => {
clearInterval(id);
};
}, []);
return (
<>
<h1>
Counter: {count}
<button onClick={() => setCount(0)}>Reset</button>
</h1>
<hr />
<p>
Every second, increment by:
<button disabled={increment === 0} onClick={() => {
setIncrement(i => i - 1);
}}>–</button>
<b>{increment}</b>
<button onClick={() => {
setIncrement(i => i + 1);
}}>+</button>
</p>
</>
);
}
import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';
const serverUrl = 'https://localhost:1234';
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
// Temporarily disable the linter to demonstrate the problem
// eslint-disable-next-line react-hooks/exhaustive-deps
const options = {
serverUrl: serverUrl,
roomId: roomId
};
useEffect(() => {
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [options]);
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} />
</>
);
}
겉보기에는 문제가 없어보이는데, options 가 렌더링 될 때마다 바뀌는 dependency라서 계속 생성되고 있는 상황이다. 즉, 반응형 값이 아니다.
options를 안으로 옮겨오고, dependecy를 roomId로 바꿔주면 해결된다.
function ChatRoom({ options }) {
const [message, setMessage] = useState('');
useEffect(() => {
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [options]); // ✅ All dependencies declared
// ...
객체를 dependency로 두면 위험하다. 이렇게 되면 부모 컴포넌트가 리렌더링할 때마다 Effect가 재실행됨. 그런데 왜 그런걸까? 앞에서 살펴봤듯이 객체는 Object.is()로 달라진 것을 식별하기 때문에 React는 렌더링 때마다 매번 달라진다고 판단할 수밖에 없음.
function ChatRoom({ options }) {
const [message, setMessage] = useState('');
const { roomId, serverUrl } = options;
useEffect(() => {
const connection = createConnection({
roomId: roomId,
serverUrl: serverUrl
});
connection.connect();
return () => connection.disconnect();
}, [roomId, serverUrl]); // ✅ All dependencies declared
// ...
외부에서 객체 정보를 읽어 온 후에 의존성에 넣어주는 것이 안전. 함수도 동일함!
function ChatRoom({ getOptions }) {
const [message, setMessage] = useState('');
const { roomId, serverUrl } = getOptions();
useEffect(() => {
const connection = createConnection({
roomId: roomId,
serverUrl: serverUrl
});
connection.connect();
return () => connection.disconnect();
}, [roomId, serverUrl]); // ✅ All dependencies declared
// ...
커스텀 훅은 state 자체가 아닌 상태적인 로직(stateful logic) 을 공유함. 완전히 독립적인 상태를 가짐.
function StatusBar() {
const isOnline = useOnlineStatus();
// ...
}
function SaveButton() {
const isOnline = useOnlineStatus();
// ...
}
커스텀 훅은 컴포넌트와 함께 렌더링됨. 그래서 항상 최신 props와 state를 받음.
import { useEffect } from 'react';
import { createConnection } from './chat.js';
import { showNotification } from './notifications.js';
export function useChatRoom({ serverUrl, roomId }) {
useEffect(() => {
const options = {
serverUrl: serverUrl,
roomId: roomId
};
const connection = createConnection(options);
connection.connect();
connection.on('message', (msg) => {
showNotification('New message: ' + msg);
});
return () => connection.disconnect();
}, [roomId, serverUrl]);
}
커스텀 훅의 이름은 코드를 자주 작성하지 않는 사람이라도 무엇을 하고, 무엇을 취하고, 무엇을 반환하는지 짐작할 수 있을 정도로 명확해야 함
useData(url)
useImpressionLog(eventName, extraData)
useChatRoom(options)
외부 시스템과 동기화 할 때 전문 용어를 사용할 수 있음
useMediaQuery(query)
useSocket(url)
useIntersectionObserver(ref, options)
커스텀 생명주기는 사용하지 말 것. 생명주기 훅은 React 패러다임에 잘 맞지 않음.
useMount(fn)
useEffectOnce(fn)
useUpdateEffect(fn)