const [변수명, set함수명] = useState(초기값);
useState는 비동기적으로 실행하기 때문에 뒤의 코드가 먼저 혹은 동시에 실행 될 여지가 있어서, 순서대로 실행되어야 한다면 useEffect를 사용한다.
import React, { useState } from "react";
function Counter(props) {
const [count, setCount] = useState(0);
return (
<div>
<p>총 {count}번 클릭했습니다.</p>
<button onClick={() => setCount(count + 1)}>
클릭
</button>
</div>
);
}
export default Counter;
Class Component의 생명주기처럼 작동한다.
기본적으로 이펙트 함수
는 '처음 렌더링된 이후'와 '업데이트로 인한 재렌더링 이후'에 실행된다.
// 형식
useEffect(이펙트 함수, 의존성 배열);
// 이렇게 자주 사용합니다
useEffect(() => {
// 컴포넌트가 마운트 된 이후,
// 의존성 배열에 있는 변수들 중 하나라도 값이 변경되었을 때 실행됨
// 의존성 배열에 빈 배열([])을 넣으면 마운트와 언마운트시에 단 한번씩만 실행됨
// 의존성 배열 생략 시 컴포넌트 업데이트 시마다 실행됨
...
return () => {
// 컴포넌트가 마운트 해제되기 전에 실행됨
...
}
}, [의존성 변수1, 의존성 변수2, ...]);
ComponentDidMount()와 ComponentDidUpdate() 역할
import React, { useState, useEffect } from "react";
function Counter(props) {
const [count, setCount] = useState(0);
// document의 title을 업데이트한다(의존성배열 생략)
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>총 {count}번 클릭했습니다.</p>
<button onClick={() => setCount(count + 1)}>
클릭
</button>
</div>
);
}
export default Counter;
import { useState, useEffect, useLayoutEffect } from 'react'
function App() {
const [value, setValue] = useState(100);
// 커져랏 버튼을 누를 때마다 1000px로 갔다가 300px로 바뀜
console.log(value)
useEffect(() => {
if (value >= 1000) {
setValue(300);
console.log(value)
}
}, [value]);
// 커져랏 버튼을 누를 때마다 그려지기 전에 300px로 정해짐
console.log(value)
// useLayoutEffect(() => {
// if (value >= 1000) {
// setValue(300);
// console.log(value)
// }
// }, [value]);
return (
<div>
{/* <div style={{ width: value, height: value, backgroundColor: 'blue', transition: '1s all' }}></div> */}
<div style={{ width: value, height: value, backgroundColor: 'blue' }}></div>
<button onClick={() => {setValue(1000)}}>커져랏!</button>
<button onClick={() => {setValue(200)}}>작아져랏!</button>
</div>
)
}
export default App;
useEffect vs useLayoutEffect에 관해서 더 알고 싶으면 여기 블로그가 도움이 됩니다.
// useMemo는 콜백함수와 의존성배열을 인자로 받는다
const memoizedValue = useMemo(
() => {
// 연산량이 많은 작업을 수행하여 결과를 반환
return computeExpensiveValue(의존성 변수1, 의존성 변수2);
},
[의존성 변수1, 의존성 변수2]
);
❗ useMemo 사용 전
렌더링 ▶ Component함수 호출 ▶ 모든 내부 변수 초기화
import React from "react";
function Component() {
const value = calculate();
return <div>{value}</div>;
}
function calculate() {
return 10;
}
export default Component;
만약 위 코드에서 calculate()가 무겁다면 비효율적일 것이다. calculate()는 무의미하게 value라는 변수에 반복적으로 같은 값을 할당하기 때문이다. 이럴 때 useMemo를 통해 Memoization을 해줄 수 있다.
❗ useMemo 사용 후
렌더링 ▶ Component함수 호출, Memoization ▶ 렌더링 ▶ Component함수 호출, Memoize된 값을 재사용
import React, { useMemo } from "react";
function Component() {
const value = useMemo(() => calculate(), []);
return <div>{value}</div>;
}
function calculate() {
return 10;
}
export default Component;
calculate를 다시 호출하지 않고 메모리에서 꺼내와서 재사용한다.
import React, { useState, useMemo } from "react";
const hardCalculate = (number) => {
console.log("어려운 계산!");
for (let i = 0; i < 999999999; i++) {}
return number + 10000;
};
const easyCalculate = (number) => {
console.log("짱 쉬운 계산!");
return number + 1;
};
function App() {
const [hardNumber, setHardNumber] = useState(1);
const [easyNumber, setEasyNumber] = useState(1);
// const hardSum = hardCalculate(hardNumber);
const hardSum = useMemo(() => {
return hardCalculate(hardNumber)
}, [hardNumber])
const easySum = easyCalculate(easyNumber);
return (
<div>
<h3>어려운 계산기</h3>
<input
type="number"
value={hardNumber}
onChange={(e) => setHardNumber(parseInt(e.target.value))}
/>
<span> + 1 = {hardSum}</span>
<h3>쉬운 계산기</h3>
<input
type="number"
value={easyNumber}
onChange={(e) => setEasyNumber(parseInt(e.target.value))}
/>
<span> + 1 = {easySum}</span>
</div>
);
}
export default App;
useMemo
useMemo(실행될 것, [값])라고 할 때 useMemo는 렌더링 전
에 실행되어 메모이제이션한 것을 반환해준다. 값이 바뀌었는지 확인하고 렌더링 전
에 저장된 것을 보내주는 것이다.
useEffect
반면 useEffect는 렌더링 후
에 일어난다. 렌더링 후
에 상태가 업데이트 되었을 때를 감지하여 동작한다.
import React, { useState, useEffect } from "react";
function App() {
const [number, setNumber] = useState(0);
const [isKorea, setIsKorea] = useState(true);
// 삼항연산자 isKorea가 참이면 '한국', 거짓이면 '외국'
const location = isKorea ? '한국' : '외국';
// useEffect 추가해서 location이 바뀔 때만 실행(최초 렌더링 시 1회 실행됨)
useEffect (() => {
console.log('useEffect 호출')
}, [location]);
// ✅location이 원시타입(string)이 아니라, 오브젝트라면? -> useMemo 사용(다음 코드로 확인하세요)
return (
<div>
<h2>하루에 몇끼 먹어요?</h2>
<input
type="number"
value={number}
onChange={(e) => setNumber(e.target.value)}
/>
<hr />
<h2>어느 나라에 있어요?</h2>
<p>나라: {location}</p>
<button onClick={() => setIsKorea(!isKorea)}>비행기 타자</button>
</div>
);
}
export default App;
import React, { useState, useEffect, useMemo } from "react";
function App() {
const [number, setNumber] = useState(0);
const [isKorea, setIsKorea] = useState(true);
// 삼항연산자 isKorea가 참이면 '한국', 거짓이면 '외국'
// 오브젝트인 경우 다른 메모리상 공간에 저장된다.
// 계속 주소가 바뀌므로 useEffect가 계속 호출된다.
// const location = {
// country: isKorea ? '한국' : '외국';
// }
const location = useMemo(() => {
return {
country: isKorea ? '한국' : '외국',
};
}, [isKorea]);
// useEffect 추가해서 location이 바뀔 때만 실행(최초 렌더링 시 1회 실행됨)
useEffect (() => {
console.log('useEffect 호출')
// 뭔가 오래걸리는 작업..
}, [location]);
return (
<div>
<h2>하루에 몇끼 먹어요?</h2>
<input
type="number"
value={number}
onChange={(e) => setNumber(e.target.value)}
/>
<hr />
<h2>어느 나라에 있어요?</h2>
<p>나라: {location.country}</p>
<button onClick={() => setIsKorea(!isKorea)}>비행기 타자</button>
</div>
);
}
export default App;
useMemo()와 유사하지만 값이 아닌 함수를 반환한다.
const memoizedCallback = useCallback(
() => {
doSomething(의존성 변수1, 의존성 변수2);
},
[의존성 변수1, 의존성 변수2] // 의존성 배열
);
const refContainer = useRef(초기값);
import React, { useRef } from "react";
function TextInputWithFocusButton(props) {
const inputElem = useRef(null);
const onButtonClick = () => {
// `current`는 마운트된 input element를 가리킴
inputElem.current.focus();
};
return (
<>
<input ref={inputElem} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
export default TextInputWithFocusButton;