function Cup(){
guest = guest + 1; // 요녀석!
return <h2>Tea cup for guest #{guest}</h2>
}
function MyComponent({ a }){
useEffect(() => {
// 여기의 코드는 첫 렌더링 후에만 실행됩니다.
},[])
useEffect(() => {
// 여기의 코드는 첫 렌더링과 a의 값이 변화할 때 실행됩니다.
},[a])
return <div />;
}
function MyComponent({ a }){
useEffect(() => {
const connection = createConnection();
connection.connect();
return () => {
connection.disconnect();
}
},[])
return <div />;
}
function Form(){
const [firstName, setFirstName] = useState('Taylor');
const [lastName, setLastName] = useState("Swift");
const [fullName, setFullName] = useState("");
useEffect(() => {
setFullName(firstName + ' ' + lastName)
},[firstName, lastName])
}
export default function ProfilePage({ userId }){
const [comment, setComment] = useState("");
useEffect(() => {
setComment('')
},[userId])
}
export default function ProfilePage({ userId }){
return (
<Profile
userId={userId}
key={userId} // 이렇게 key값을 줍니다.
/>
)
}
function Profile({ userId }){
// key가 변하면 이 컴포넌트 및 모든 자식 컴포넌트의 state가 자동적으로 재설정됨
const [comment, setComment] = useState("");
...
}
function List({ items }){
const [isReverse, setIsReverse] = useState(false);
const [selection, setSelection] = useState(null);
useEffect(() => {
setSelection(null);
}, [items])
}
function List({ items }){
const [isReverse, setIsReverse] = useState(false);
const [selection, setSelection] = useState(null);
const [prevItems, setPrevItems] = useState(items);
if(items !== prevItems){
setPrevItems(items);
setSelection(null);
}
}
function List({ items }){
const [isReverse, setIsReverse] = useState(false);
const [selection, setSelection] = useState(null);
const selection = items.find((item) => item.id === selectedId) ?? null
}
const serverUrl = "https://localhost:1234"; // 비반응형 값
function ChatRoom({ roomId }){
// props는 바뀔 수 있으니 반응형 값
const [message, setMessage] = useState(""); // 반응형 값
}
function Example({ message1 = ''}){
const [message2, setMessage2] = useState('');
const click = () => sendUserMessage('message1 or message2')
return (
<>
<button onClick={click}>메세지 보내기</button>
</>
)
}
const connection = createConnection(serverUrl, roomId);
connection.connect();
function MyComponent({ roomId }){
useEffect(() => {
const connection = createConnection(roomId);
connection.connect();
return () => {
connection.disconnect();
}
},[roomId])
return <div />;
}
function MyComponent({ serverUrl, roomId }){
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
}
},[serverUrl, roomId])
return <div />;
}
const serverUrl = 'https://api.example.com'
function MyComponent({ roomId }){
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
}
},[roomId]) // 이제 serverUrl은 비반응형 값이기 때문에 의존성에 추가하지 않아도 됩니다.
return <div />;
}
function ChatRoom({ roomId, theme }){
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.on('connected', () => {
showNotification('Connected', theme);
})
connection.connect();
return () => {
connection.disconnect();
}
},[roomId, theme]) // 이 경우 theme가 의존성에 추가됩니다.
return <div />;
}
function ChatRoom({ roomId, theme }){
const onConnected = useEffectEvent(() => {
// 이렇게 반응형 로직을 비반응형 로직으로 동작할 수 있습니다.
showNotification('Connected', theme);
})
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.on('connected', () => {
onConnected();
})
connection.connect();
return () => {
connection.disconnect();
}
},[roomId]) // 따라서 이제 의존성에 theme는 들어가지 않습니다.
return <div />;
}
function Example(){
const [isHovered, setIsHovered] = useState(false);
const { isMobile } = useMediaQuery();
useEffect(() => {
// 최초 렌더링 시와 isHover가 변화할 때 들어갈 로직을 넣습니다.
}, isMobile ? [] : [isHovered]) // 삼항 연산자를 활용해 의존성을 미디어 환경에 따라 변경합니다.
}
useEffect는 컴포넌트에서 state가 바뀌면 컴포넌트를 렌더링하고 화면을 그리고 useEffect가 실행됩니다.

useLayoutEffect

렌더링 -> 화면그리기 -> useEffect로 인해 렌더링 -> 화면 그리기
function Tooltip() {
const ref = useRef(null);
const [tooltipHeight, setTooltipHeight] = useState(0); // 아직 height 값이 0이기 때문에 height가 0인 상태로 화면에 그립니다.
useEffect(() => {
const { height } = ref.current.getBoundingClientRect();
setTooltipHeight(height); // 실제 높이를 구한 후 리렌더링하고 화면에 다시 그립니다.
}, [])
// ...아래에 작성될 렌더링 로직에 tooltipHight를 사용합니다.
}
function Tooltip() {
const ref = useRef(null);
const [tooltipHeight, setTooltipHeight] = useState(0); // 아직 height 값이 0입니다. 하지만 사용자는 height가 0인 화면을 볼 수 없습니다.
useLayoutEffect(() => {
// useLayoutEffect를 사용했기 때문에 렌더링이 일어난 이후 화면을 그리지 않고 아래 작업을 수행합니다.
const { height } = ref.current.getBoundingClientRect();
setTooltipHeight(height); // 실제 높이를 구한 후 리 렌더링하고 화면을 그립니다.
}, [])
// ...아래에 작성될 렌더링 로직에 tooltipHight를 사용합니다.
}
let isInserted = new Set();
function useCSS(rule){
useInsertionEffect(() => {
if (!isInserted.has(rule)){
isInserted.add(rule);
document.head.appendChild(getStyleForRule(rule));
}
})
return rule;
}
function Button(){
const className = useCSS('...');
return <div className={calssName} />
}
이번 글에서는 리액트에서의 사이드 이펙트에 대해 다뤘는데요. key값을 부여함으로써 useEffect를 없애고 useLayoutEffect를 사용해서 Tooltip을 구현하는 부분이 상당히 인상적이었습니다. useEffect를 최대한 자제하고 의존성을 줄여야 한다는 것은 머리로는 알고 있지만 코드를 짜다보면 적용하기 쉽지 않은 거 같습니다. React 공식 문서를 통해 학습을 했을 때 대부분 배운 내용이지만 이렇게 잘 정리된 글로 복습하니 다시 리마인드되고 좋네요.