useEffect(setup, dependencies?)
useEffect는 컴포넌트를 외부 시스템과 동기화할 수 있는 React 훅입니다.
_외부 시스템이란 리액트에 의해 제어되지 않는 (API 통신, ref를 통한 dom 제어, window 전역의 이벤트 리스너)
특정 값에 대한 변화 감지
useEffect(() => {
console.log("clicked");
}, [count]); // count 변화 감지
라이프사이클처럼 사용할 수도 있음
useEffect(() => {
console.log("component loaded");
}, []); //컴포넌트 로드 감지 (라이프사이클))
deps에 빈배열만 둘 경우, 컴포넌트가 처음 로드될 때 발생함.
—> 컴포넌트가 마운트된 후, 컴포넌트가 업데이트 되고 난 후, 컴포넌트가 언마운트 되기 전 모두 실행
전역적으로 이벤트 감지
useEffect(() => {
console.log("event");
const handleScroll = () => {
console.log(window.scrollY);
};
document.addEventListener("scroll", handleScroll);
return () => document.removeEventListener("scroll", handleScroll);
// 전역적으로 이벤트를 잡았을 땐 꼭 해제해줘야 한다!!
// --> 여기서 useEffect의 return은 **해당 컴포넌트가 제거될때 실행**이 된다!
}, []);
useRef는 렌더링에 필요하지 않은 값을 참조할 수 있는 React 훅입니다.
import { useRef } from 'react';
function MyComponent() {
const **inputRef** = useRef(null);
return <input ref={**inputRef**} / > // 이렇게 JSX노드의 ref 속성으로 전달되면 **.current**로 접근!!
}
DOM에 직접 접근
function App() {
const inputRef = useRef();
return (
<div>
<input ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>Focus</button>
</div>
);
}
+) 컴포넌트를 통해서도 DOM에 접근 가능
import "./App.css";
import { useRef } from "react";
import Input from "./components/Input";
function App() {
const inputRef = useRef();
return (
<div>
<Input **ref={inputRef**} />
<button onClick={() => **inputRef**.**current**.focus()}>Focus</button>
</div>
);
}
export default App;
부모 컴포넌트인 App에서 하위 컴포넌트인 Input의 DOM요소에 접근하려할때 사용할 수 있다는것
import React from "react";
const Input = React.**forwardRef**((_, ref) => {
return (
<>
Input : <input **ref={ref}** />
</>
);
});
export default Input;
인자로 받은 ref를 DOM요소에 등록해놓으면 부모에서의 ref와 연결되어 접근 가능
** const Input = ({ ref }) => {
등, ref를 props로 넘길수 있지만, forwardRef 사용을 권장한다. ref를 숨길 수 있고 표준이기 때문이다. **
forwardRef
문서
import { forwardRef } from 'react';
const MyInput = forwardRef(function MyInput(props, ref) {
// ...
});
// forwardRef( render함수 ) 형식임
// render 함수는 (props, ref) => { } 혹은 function FuncName(props,ref) { }
(다시 렌더링하지 않을!!) 지역 변수로 사용할 때
useRef는 값이 변경되더라도 다시 렌더링하지 않는다!! ↔useState는 값이 변경될 때 다시 렌더링 한다.
import "./App.css";
import { useRef } from "react";
import Input from "./components/Input";
import AutoCounter from "./components/AutoCounter";
function App() {
const inputRef = useRef();
return (
<div>
<Input ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>Focus</button>
**<AutoCounter />**
</div>
);
}
export default App;
import { useRef, useState } from "react";
const AutoCounter = () => {
const [count, setCount] = useState(0); //state값을 증가시키지만
**const intervalId = useRef();**
const handleStart = () => {
intervalId.current = setInterval(() => { //intervalId값 변경이기 때문에 재렌더링XX
setCount((count) => count + 1);
}, 1000);
};
const handleStop = () => {
clearInterval(intervalId.current);
};
return (
<>
<div>{count}</div>
<button onClick={handleStart}>Start</button>
<button onClick={handleStop}>Stop</button>
</>
);
};
export default AutoCounter;
start를 눌러 1초마다 값이 증가하는게 화면에 그려지지만 재렌더링이 일어나진 않는다.
useRef를 통해 값을 넣어줬으니까.
useMemo는 리렌더링 사이의 계산 결과를 캐시할 수 있는 React 훅입니다.
연산”결과”를 캐싱하는 느낌
const cachedValue = useMemo(calculateValue, dependencies)
// sum() 이 매우 복잡하고 큰 연산이고, n이 상태라면
// const cachedResult = sum(n) 보다
const cachedResult = useMemo( () => sum(n), [n]);
컴포넌트는 1)자신의상태변경, 2)부모로부터의prop변경, 3)부모의상태변경 시 재렌더링된다. 이 모든 경우에서 내부 함수나 연산을 재렌더링하지 않고 dependencies
의 변경시에만 재렌더링한다.
*** React.memo와의 차이
단어가 비슷해서 헷갈리는데.. useMemo()는 특정 값을 캐싱하는 hook 이고, React.memo는 컴포넌트를 memoization하는 HOC이다!! 자식 컴포넌트는 변경될게 없는 데 부모 컴포넌트의 변경때문에 자식까지 재렌더링 되는 것으 막을 때 사용한다.
import React from 'react';
**const Child = React.memo(** () => { ... }**)**
useCallback은 리렌더링 사이에 함수 정의를 캐시할 수 있게 해주는 React 훅입니다.
함수 “정의”를 캐싱하는 느낌
const cachedFn = useCallback(fn, dependencies)
렌더링 시 마지막 렌더링 이후 dependencies가 변경되지 않았다면 동일한 함수 다시 제공!
import { useState, useCallback } from "react";
const useToggle = (initialState = false) => {
const [state, setState] = useState(initialState);
const toggle = useCallback(() => setState((state) => !state), []);
return [state, toggle];
};
export default useToggle;
컴포넌트에서 박복되는 로직을 커스텀훅으로 만들 수 있다.
import { useState, useEffect } from 'react';
export default function SaveButton() {
const [isOnline, setIsOnline] = useState(true);
useEffect(() => {
function handleOnline() {
setIsOnline(true);
}
function handleOffline() {
setIsOnline(false);
}
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
function handleSaveClick() {
console.log('✅ Progress saved');
}
return (
<button disabled={!isOnline} onClick={handleSaveClick}>
{isOnline ? 'Save progress' : 'Reconnecting...'}
</button>
);
}
위와 같은 네트워크 상태 확인 로직을 useOnlineStatus라는 훅으로 추출하면,
import { useState, useEffect } from 'react';
export function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(true);
useEffect(() => {
function handleOnline() {
setIsOnline(true);
}
function handleOffline() {
setIsOnline(false);
}
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
return isOnline;
}
**/// App.js에서 호출은 간결하게 되고 재사용 또한 간단해진다!**
import { useOnlineStatus } from './useOnlineStatus.js';
function StatusBar() {
const isOnline = useOnlineStatus();
return <h1>{isOnline ? '✅ Online' : '❌ Disconnected'}</h1>;
}
function SaveButton() {
const isOnline = useOnlineStatus();
function handleSaveClick() {
console.log('✅ Progress saved');
}
return (
<button disabled={!isOnline} onClick={handleSaveClick}>
{isOnline ? 'Save progress' : 'Reconnecting...'}
</button>
);
}
export default function App() {
return (
<>
<SaveButton />
<StatusBar />
</>
);
}
배우며추가예정
✔️ react.dev 공식문서와 강의를 학습하면서 정리한 내용을 담았습니다! 잘못된 부분 알려주시면 감사하겠습니다 :)