24. 07. 23 TIL
useEffect
= 리액트에서 컴포넌트가 렌더링 되거나 업데이트 도리 때 특정 작업을 수행하도록 설정할 수 있는 훅
이를 통해 컴포넌트의 생명주기 이벤트를 처리할 수 있다.
언제 사용하는지?
-> 1. 렌더링 후 작업 : 컴포넌트가 화면에 렌더링된 이후 특정 작업을 수행하고 싶을 때
-> 2. 컴포넌트 언마운트 시 작업 : 컴포넌트가 화면에서 사라질 때 특정 작업을 수행하고 싶을 때
의존성 배열
= useEffect의 두 번째 인자로 전달되는 배열로, 배열에 있는 값이 변경될 때만 useEffect가 실행됨
비어있을 경우 -> 컴포넌트가 처음 렌더링 될 때만 실행됨
값이 있을 경우 -> value를 넣을 경우 value가 변경될 때마다 useEffect가 실행됨
클린업
= 컴포넌트가 언마운트될 때 실행되는 함수
import React, { useEffect } from "react";
const App = () => {
useEffect(() => {
console.log("Component mounted");
return () => {
console.log("Component unmounted");
};
}, []);
return <div>hello react!</div>;
};
export default App;
이 코드에서 useEffect의 리턴 부분에 있는 함수는 컴포넌트가 언마운트 될 때 실행됨
컴포넌트 라이프사이클
= 리액트 컴포넌트는 생애주기(lifeCycle)을 갖고 있음
이는 컴포넌트가 태어나고, 살아가고, 죽는 과정을 나타냄
useRef
= 리액트에서 값을 저장하고 활용하는 훅 중 하나
* 리렌더링과 상관없이 값을 기억함
용도
1. 저장공간으로 사용 (렌더링을 일으키지 않는 값 저장)
2. DOM 요소에 접근 및 조작 (포커싱 주기)
useState와 useRef의 차이점
- useState : 상태를 관리하며, 상태가 변경되면 컴포넌트가 다시 렌더링됨
- useRef : 값이 변경되어도 컴포넌트가 다시 렌더링되지 않음
useRef로 포커싱주기
= useRef로 **ref 생성 -> useEffect에 **ref.current.focus()로 아이디 입력란에 포커스를 줌 -> ref={**ref}를 통해 input 요소에 **ref를 연결
useContext
= 프롭스 드릴링 문제로 사용...
1. createContext = 컨텍스트 생성
2. useContext = 생성된 컨텍스트를 사용해 현재 값 읽음
3. provider = 생성된 컨텍스트를 하위 컴포넌트에 전달
주의
1. 렌더링 문제 = 프로바이더에서 제공하는 값이 변경되면 useContext를 사용하고 있는 모든 컴포넌트가 리렌더링됨
* 값이 자주 변경되지 않도록 주의하고, 메모이제이션을 사용해 성능을 최적화 해야함
memoization
리렌더링 발생 조건
1. state가 바뀌었을 때
2. props가 변경되었을 때
3. 부모 컴포넌트가 리렌더링 될 때
최적화
(불필요한 리렌더링을 줄이는 것이 중요)
1. React.memo = 컴포넌트를 메모리에 저장하여 필요할 때만 리렌더링
2. useCallback = 함수를 메모리에 저장하여 불필요한 함수 재생성을 방지
3. useMemo = 값을 메모리에 저장하여 불필요한 계산 방지
1. memo (React.memo)
= 부모 컴포넌트가 리렌더링 되더라도 props가 변경되지 않은 자식 컴포넌트를 리렌더링하지 않도록 최적화
ex.
import React, { useState } from 'react';
import Display from './Display';
function App() {
console.log('App 컴포넌트가 렌더링되었습니다!');
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1);
};
return (
<div>
<h1>카운터</h1>
<button onClick={increment}>+</button>
<p>현재 카운트: {count}</p>
<Display />
</div>
);
}
export default App;
import React from 'react';
function Display() {
console.log('Display 컴포넌트가 렌더링되었습니다!');
return <p>이것은 Display 컴포넌트입니다.</p>;
}
export default Display;
app 컴포넌트는 버튼을 클릭하면 count 상태를 업데이트함
그런데 display 컴포넌트는 단순히 텍스트를 보여줌
그래서 버튼을 클릭할 때마다 display 컴포넌트도 함께 리렌더링 되는 문제가 있음
그래서 React.memo로 최적화함
import React from 'react';
function Display() {
console.log('Display 컴포넌트가 렌더링되었습니다!');
return <p>이것은 Display 컴포넌트입니다.</p>;
}
export default React.memo(Display);
그러면 display 컴포넌트는 app 컴포넌트가 리렌더링되더라도 display 컴포넌트의 props가 변경되지 않으면 리렌더링 되지 않음
\
2. useCallback
함수 컴포넌트를 메모이제이션해 특정 조건이 아닌 경우 함수를 다시 생성하지 않도록 함
함수의 메모이제이션을 통해 불필요한 리렌더링을 방지함
import React, { useState } from 'react';
import Button from './Button';
function App() {
const [count, setCount] = useState(0);
// 카운트를 증가시키는 함수
const increment = () => {
setCount(count + 1);
};
console.log('App 컴포넌트가 렌더링되었습니다!');
return (
<div>
<h1>카운트: {count}</h1>
<Button onClick={increment} />
</div>
);
}
export default App;
app 컴포넌트에서 increment 함수를 정의하고 button 컴포넌트에 props로 전달함
import React from 'react';
function Button({ onClick }) {
console.log('Button 컴포넌트가 렌더링되었습니다!');
return <button onClick={onClick}>Increment</button>;
}
export default React.memo(Button);
button 컴포넌트는 React.memo를 사용해 메모이제이션 되어있음
그러나 app 컴포넌트가 리렌더링될 때마다 button 컴포넌트도 리렌더링됨
이는 increment 함수가 매번 새로 생성되기 때문임
그래서 useCallback을 사용함
import React, { useState, useCallback } from 'react';
import Button from './Button';
function App() {
const [count, setCount] = useState(0);
// useCallback을 사용하여 함수 메모이제이션
const increment = useCallback(() => {
setCount((prevCount) => prevCount + 1);
}, []);
console.log('App 컴포넌트가 렌더링되었습니다!');
return (
<div>
<h1>카운트: {count}</h1>
<Button onClick={increment} />
</div>
);
}
export default App;
import React from 'react';
function Button({ onClick }) {
console.log('Button 컴포넌트가 렌더링되었습니다!');
return <button onClick={onClick}>Increment</button>;
}
export default React.memo(Button);
3. useMemo
useMemo는 함수의 반환 값을 메모이제이션해서 동일한 값을 반환하는 함수를 반복해서 호출하지 않고, 이전에 계산된 값을 재사용할 수 있게 함
기본 사용 방법
const value = useMemo(() => {
return 함수();
}, [dependencyArray]);
dependencyArray의 값이 변경될 때만 함수가 호출됨
그 외의 경우에는 메모이제이션된 값을 반환함
예제
import React, { useState } from 'react';
import HeavyComponent from './HeavyComponent';
function App() {
const [count, setCount] = useState(0);
return (
<div>
<h1>카운트: {count}</h1>
<button onClick={() => setCount(count + 1)}>증가</button>
<HeavyComponent />
</div>
);
}
export default App;
일단 여기 heavyComponent가 있음
import React from 'react';
function HeavyComponent() {
const heavyWork = () => {
console.log('무거운 작업 실행 중...');
for (let i = 0; i < 1000000000; i++) {}
return 100;
};
const value = heavyWork();
return (
<div>
<p>무거운 작업 결과: {value}</p>
</div>
);
}
export default HeavyComponent;
이렇게 생겼음
무거운 작업 수행중인데 app 컴포넌트 버튼을 클릭할 때마다 heavyComponent가 렌더링되고, heavyWork 함수가 불필요하게 다시 호출됨
그래서 useMemo를 사용해 heavyWork 함수를 메모이제이션함
그럼 app 컴포넌트가 리렌더링 돼도 heavyComponent의 heavyWork 함수는 처음 한 번만 실행됨
import React, { useState, useEffect, useMemo } from 'react';
function ObjectComponent() {
const [isAlive, setIsAlive] = useState(true);
const [uselessCount, setUselessCount] = useState(0);
const me = useMemo(() => {
return {
name: 'Ted Chang',
age: 21,
isAlive: isAlive ? '생존' : '사망',
};
}, [isAlive]);
useEffect(() => {
console.log('생존여부가 바뀔 때만 호출해주세요!');
}, [me]);
return (
<div>
<div>내 이름은 {me.name}이고, 나이는 {me.age}야!</div>
<button onClick={() => setIsAlive(!isAlive)}>누르면 살았다가 죽었다가 해요</button>
<div>생존여부: {me.isAlive}</div>
<hr />
필요없는 숫자 영역이에요!
<div>{uselessCount}</div>
<button onClick={() => setUselessCount(uselessCount + 1)}>누르면 숫자가 올라가요</button>
</div>
);
}
export default ObjectComponent;
그래서 useMemo를 사용해 me 객체를 메모이제이션함
isAlive 값이 변경될 때만 me 객체가 다시 생성됨
이제 useLessCount가 변경되어도 me 객체는 재생성되지 않고,
useEffect는 불필요하게 실행되지 않음
* useCallback = 함수를 메모이제이션
* useMemo = 함수 결과 값을 메모이제이션
정리
1. useCallback
import React, { useState, useCallback } from 'react';
import Button from './Button';
function App() {
const [count, setCount] = useState(0);
const increment = useCallback(() => {
setCount(count + 1);
}, [count]);
return (
<div>
<h1>카운트: {count}</h1>
<Button onClick={increment} />
</div>
);
}
export default App;
import React from 'react';
function Button({ onClick }) {
return <button onClick={onClick}>Increment</button>;
}
export default React.memo(Button);
= increment 함수가 useCallback 으로 메모이제이션
2. useMemo
import React, { useState } from 'react';
import HeavyComponent from './HeavyComponent';
function App() {
const [count, setCount] = useState(0);
return (
<div>
<h1>카운트: {count}</h1>
<button onClick={() => setCount(count + 1)}>증가</button>
<HeavyComponent />
</div>
);
}
export default App;
import React, { useMemo } from 'react';
function HeavyComponent() {
const heavyWork = () => {
console.log('무거운 작업 실행 중...');
for (let i = 0; i < 1000000000; i++) {}
return 100;
};
const value = useMemo(() => heavyWork(), []);
return (
<div>
<p>무거운 작업 결과: {value}</p>
</div>
);
}
export default HeavyComponent;
= heavyWork 함수의 결과값이 useMemo로 메모이제이션
커스텀 훅
= 리액트에서 제공하는 내장 훅을 사용하여, 반복되는 로직이나 중복되는 코드를 재사용하기 위해 직접 만드는 훅
예를 들어 여러 개의 input 필드를 다룰 때, 같은 로직을 반복해서 작성하는 대신 커스텀 훅을 만들어서 사용하면 코드가 깔끔하고 관리하기 쉬워짐
* 커스텀 훅의 함수 이름은 use로 시작하는 것이 좋음
* 파일 이름은 use로 시작할 필요는 없음
* src 폴더에 보통 hooks 라는 폴더를 생성해서 보관
예제
= 여러 개의 input 필드를 관리할 때, 각각 필드에 state와 event handler를 작성해야 함
// src/App.jsx
import React, { useState } from "react";
const App = () => {
// input의 갯수가 늘어날때마다 state와 handler가 같이 증가한다.
const [title, setTitle] = useState("");
const onChangeTitleHandler = (e) => {
setTitle(e.target.value);
};
// input의 갯수가 늘어날때마다 state와 handler가 같이 증가한다.
const [body, setBody] = useState("");
const onChangeBodyHandler = (e) => {
setBody(e.target.value);
};
return (
<div>
<input type="text" name="title" value={title} onChange={onChangeTitleHandler} />
<input type="text" name="body" value={body} onChange={onChangeBodyHandler} />
</div>
);
};
export default App;
해결
1. 커스텀 훅 만들기
반복되는 로직이나 중복되는 코드를 찾아내 분리
// src/hooks/useInput.js
import React, { useState } from "react";
const useInput = () => {
// 2. value는 useState로 관리하고,
const [value, setValue] = useState("");
// 3. 핸들러 로직도 구현합니다.
const handler = (e) => {
setValue(e.target.value);
};
// 1. 이 훅은 [ ] 을 반환하는데, 첫번째는 value, 두번째는 핸들러를 반환합니다.
return [value, handler];
};
export default useInput;
2. 커스텀 훅 사용하기
// src/App.jsx
import React from "react";
// 커스텀 훅을 불러옵니다.
import useInput from "./hooks/useInput";
const App = () => {
// useInput 훅을 사용하여 title 상태와 그 상태를 변경하는 핸들러를 정의합니다.
const [title, onChangeTitleHandler] = useInput();
// useInput 훅을 사용하여 body 상태와 그 상태를 변경하는 핸들러를 정의합니다.
const [body, onChangeBodyHandler] = useInput();
return (
<div>
{/* title 상태를 관리하는 input 필드입니다. value는 title 상태이고, onChange 이벤트 발생 시 onChangeTitleHandler가 호출됩니다. */}
<input type="text" name="title" value={title} onChange={onChangeTitleHandler} />
{/* body 상태를 관리하는 input 필드입니다. value는 body 상태이고, onChange 이벤트 발생 시 onChangeBodyHandler가 호출됩니다. */}
<input type="text" name="body" value={body} onChange={onChangeBodyHandler} />
</div>
);
};
// App 컴포넌트를 기본으로 내보냅니다.
export default App;