useEffect로 라이프 사이클 제어하기

 

리액트 컴포넌트의 라이프 사이클이란 리액트 컴포넌트가 생성, 업데이트, 제거되는 과정을 말한다.

각 과정에서 특정한 메서드들이 호출되어 컴포넌트의 상태를 관리하거나 필요한 작업을 수행할 수 있다. 

 

 

리액트 컴포넌트의 라이프 사이클은 크게 마운트(Mount, 탄생), 업데이트(Update, 갱신), 언마운트(Unmount, 사망)로 구분한다.

  • 마운트(Mount) : 컴포넌트를 페이지에 처음 렌더링할 때
  • 업데이트(Update) : State나 Props의 값이 바뀌거나 부모 컴포넌트가 리렌더해, 자신도 리렌더될 때
  • 언마운트(Unmount) : 더 이상 페이지에 컴포넌트를 렌더링 하지 않을 때

라이프 사이클을 이용하면 컴포넌트가 처음 렌더링될 때 특정 동작을 하도록 만들거나, 업데이트할 때 적절한지 검사하거나, 페이지에서 사라질 때 메모리를 정리하는 등 여러 유용한 작업을 단계에 맞게 할 수 있다. 이를 라이프 사이클 제어(Lifecycle Control)라고 한다. 리액트 훅의 하나인 함수 useEffect를 이용하면 사이클을 쉽게 제어할 수 있다.

 

 

useEffect

함수 useEffect는 어떤 값이 변경될 때마다 특정 코드를 실행하는 리액트 훅이다. 이를 "특정 값을 검사한다"라고 표현한다.

예를 들어 useEffect를 이용하면 컴포넌트의 State 값이 바뀔 때마다 변경된 값을 콘솔에 출력하게 할 수 있다.

 

1. useEffect로 라이프 사이클 제어하기

01) 컴포넌트의 3단계 라이프 사이클 중 업데이트(Update)가 발생하면 특정 코드를 실행해 보겠다.

(...)
function App() {
   const [count, setCount] = useState(0);
   const [text, setText] = useState('');
   const handleSetCount = (value) => {
      setCount(count + value);
   };
   const handleChangeText = (e) => {
      setText(e.target.value);
   };
   useEffect(() => {
      console.log('컴포넌트 업데이트!');
   });
(...)
}
export default App;

 

 

두 번째 요소인 의존성 배열에 아무것도 전달하지 않으면, useEffect는 컴포넌트를 렌더링 할 때마다 콜백 함수를 실행한다.

 

세 번에 걸친 console.log("컴포넌트 업데이트!")는 컴포넌트를 처음 페이지에 렌더링 하는 마운트 시점 한 번과 컴포넌트를 리렌더 하는 업데이트 시점 두 번의 결과이다.

 

 

 

 

 

 


 

02) 이번에는 useEffect에서 마운트 시점은 제외하고 업데이트 시점에만 콜백 함수를 실행해 보겠다.

 

페이지에 처음 렌더링할 때는 콜백 함수를 실행하지 않고 리렌더 될 때만 실행하겠다는 뜻이다. 이를 위해 useRef 이용해 보겠다.

**useRef
useRef는 마치 개발자의 개인 보관함 같아. 거기에는 컴포넌트 간에 공유하고 싶은 정보나, DOM 요소에 접근하고 싶을 때 사용돼. 이걸로 다양한 트릭을 부릴 수 있어!
1. DOM조작 : useRef를 사용하면 컴포넌트 안에서 마음껏 DOM을 조작할 수 있어. 마치 나만의 조종실을 가지고 있는 것 같아. 예를 들어, 특정 버튼을 누르면 특정 요소를 바로 조작하는 거 말이야.
const myInputRef = useRef();

const focusInput = () => {
  myInputRef.current.focus();
};

return (
  <div>
    <input ref={myInputRef} />
    <button onClick={focusInput}>Focus Input</button>
  </div>
);​


2. 이전 값 유지 : useRef는 컴포넌트가 리엔더링 되어도 값이 계속 유지돼. 이걸로 마치 전투 중에도 캐릭터의 상태를 유지하는 것처럼 사용할 수 있어.

const myInputRef = useRef();

const focusInput = () => {
  myInputRef.current.focus();
};

return (
  <div>
    <input ref={myInputRef} />
    <button onClick={focusInput}>Focus Input</button>
  </div>
);


3. 컴포넌트 간 데이터 공유 : useRef를 사용하면 컴포넌트 간에 데이터를 편하게 공유할 수 있어. 이걸로 마치 전역 스토리처럼 사용할 수 있어.

const sharedData = useRef('Shared Value');

// 컴포넌트1
sharedData.current = 'Updated Value';

// 컴포넌트2
console.log(sharedData.current); // 출력: 'Updated Value'


4. 라이프 사이클 훅 대용 : 클래스 컴포넌트의 라이프 사이클 훅을 함수형 컴포넌트에서 useRef로 대체할 수 있어. 마치 컴포넌트가 마운트 됐을 때, 언마운트됐을 때의 동작을 감시하는 것처럼 말이야.

useEffect(() => {
  // 컴포넌트 마운트 시 동작
  return () => {
    // 컴포넌트 언마운트 시 동작
  };
}, []);


그러니까, useRef는 마치 내가 개발 중인 프로젝트의 작은 보물상자 같아. 필요할 때마다 열어보면서 다양한 도구들을 꺼내 쓰는 느낌이야!

 

(...)
function App() {
   const [count, setCount] = useState(0);
   const [text, setText] = useState('');
   const handleSetCount = (value) => {
      setCount(count + value);
   };
   const handleChangeText = (e) => {
      setText(e.target.value);
   };
   
   const didMountRef = useRef(false);	// **추가
   useEffect(() => {	// ** 수정
      if (!didMountRef.current) {
         didMountRef.current = true;
         return;
      } else {
         console.log('컴포넌트 업데이트!');
      }
   });
(...)
}
export default App;

 

 

*useRef는 함수 컴포넌트에서 값이 계속 도와주는 훅이다. 주로 컴포넌트가 마운트 되었는지 여부를 기록하거나, 업데이트 중인지 여부를 확인하는 데 사용된다.

여기서 *didMountRef는 컴포넌트가 마운트 되었는지 여부를 나타내는 변수로 사용된다.

*useEffect는 컴포넌트가 최초로 렌더링 될 때와 업데이트될 때마다 실행되기 때문에, 첫 렌더링 때만 특정 동작을 수행하고자 할 때 *useRef를 활용하는 것이 흔한 패턴이다.

 

1. 마운트 시점 체크 : *didMountRef.current를 통해 현재 컴포넌트가 마운트 되었는지 여부를 확인해. 초기에는 false이고, 첫 렌더링 시에는 *useEffect 내에서 *didMountRef.current를 true로 설정함으로써 마운트 상태를 체크해.
여기서 * .current 는 *useRef로 생성된 객체에서 현재 값에 접근하는 방법이야.
*useRef로 생성된 객체는 *{ current:...} 형태를 가지며, 여기서 *current 프로퍼티가 현재 값에 해당해.
예를 들어, 다음과 같이 useRef를 사용하면 :
const didMountRef = useRef(false);​

 

*didMountRef는 다음과 같은 객체를 가지게 돼 :
{
  current: false
}​


*didMountRef.current는 이 객체의 *current프로퍼티에 접근하는 것이고, 여기서 현재 값이 false로 초기화돼 있어.
이 값은 나중에 업데이트되거나 변경될 수 있어.

if (!didMountRef.current) {
  // 첫 렌더링 시 동작
  didMountRef.current = true;
  return;
}​

2. 업데이트 시점 체크 : 컴포넌트가 최초 렌더링 이후에 업데이트되면 else 블록 내의 코드가 실행돼. 즉, 최초 렌더링 시에는 실행되지 않고, 이후에는 업데이트 시에만 실행되는 코드를 작성할 수 있어.
else {
  console.log('컴포넌트 업데이트!');
}​

 


이렇게 함으로써 특정 동작을 컴포넌트가 최초로 렌더링될 때와 업데이트될 때로 나눌 수 있어.

 

정리하자면 useEffect에서 의존성 배열을 인수로 전달하지 않으면 마운트, 업데이트 시점 모두 콜백 함수를 호출한다.

그러나 코드처럼 콜백 함수 내부에서 조건문과 Ref 객체로 특정 시점에만 코드를 실행하게 만들 수 있다. 즉, 마운트 시점(didMountRef = false)에 호출하면 아무것도 출력하지 않고 함수를 종료하고, 업데이트 시점 (didMountRef = true)에 호출하면 문자열을 콘솔에 출력한다.


 

03) 컴포넌트의 마운트 제어하기 - 이번에는 컴포넌트의 마운트 시점에 실행되는 코드를 실행해 보겠다.

(...)
function App() {
   const [count, setCount] = useState(0);
   const [text, setText] = useState('');
   const handleSetCount = (value) => {
      setCount(count + value);
   };
   const handleChangeText = (e) => {
      setText(e.target.value);
   };
   
   const didMountRef = useRef(false);	// **추가
   useEffect(() => {	// ** 수정
      if (!didMountRef.current) {
         didMountRef.current = true;
         return;
      } else {
         console.log('컴포넌트 업데이트!');
      }
   });
   useEffect(() => {
      console.log('컴포넌트 마운트');
   }, []);
(...)
}
export default App;

 

**
1. [ ] (빈 의존성 배열) : 이 경우에는 효과가 한 번만 실행되면, 마운트 시에만 실행돼. 따라서 컴포넌트가 업데이트될 때는 실행되지 않아.
2. undefined (의존성 배열을 생략) : 이 경우에도 효과가 한 번만 실행되며, 마운트 시에만 실행돼. *useEffect에서 의존성 배열이 생략되면 디폴트로 undefined로 취급되어 마운트 시에만 효과가 실행돼.

차이는 주로 개발자의 의도에 따라 다르지만, 일반적으로 명시적으로 빈 배열을 사용하는 것이 의도를 명확하게 전달하고, 나중에 코드를 유지보수하기에도 도움이 된다고 생각돼. 만약에 어떤 효과를 특정 상황에서만 실행하고 싶다면
[ ] 를 사용하고, 항상 실행하고 싶다면 생략 또는 undefined를 사용할 수 있어.

 

 

 

'React' 카테고리의 다른 글

JSX  (1) 2024.01.01
리액트 - 2  (0) 2024.01.01
리액트 - 1  (0) 2024.01.01
React ?  (0) 2024.01.01
useEffect에 대해서  (0) 2023.08.18