TIL-2024.04.30 - React - 006. Hooks
질문
1. Class Component 에서는 Life Cycle Methods를 사용해서 수명주기 동안 다양한 작업을 수행할 수 있는데, Functional Component에서는 어떻게 할 수 있어?
2. 각각 종류를 알려줘.
Hooks
- 함수형 컴포넌트에서 상태(state)와 다른 React 기능들을 사용 가능.
- 클래스 컴포넌트에서만 사용할 수 있던 기능들을 함수형 컴포넌트에서도 사용할 수 있음.
Hooks 종류
- useState
- useEffect
- useReducer
- useMemo
- useCallback
- useRef
- useContext (Context API시 논의)
- 커스텀 Hooks
useState
- 함수형 컴포넌트에서 상태를 추가
- useState를 사용하면 함수형 컴포넌트에서도 상태를 관리
예제:
import React, {useState} from 'react';
const Info = () => {
// useState: 변수명, 설정 함수 , 초기 값
const [name, setName] = useState('');
const [nickname, setNickname] = useState('');
const onChangeName = e => {
setName(e.target.value);
};
const onChangeNickname = e => {
setNickname(e.target.value);
};
return (
<div>
<div>
<input type="text" value={name} onChange={onChangeName} placeholder="이름"/>
<input type="text" value={nickname} onChange={onChangeNickname} placeholder="닉네임"/>
</div>
<div>
<div>
<b> 이름 : </b> {name}
</div>
<div>
<b> 닉네임 : </b> {nickname}
</div>
</div>
</div>
)
};
export default Info;
useEffect
- 함수형 컴포넌트에서 부수 효과(side effects)를 수행
- 예를 들어 데이터를 가져오거나 구독을 설정하는 등의 작업을 수행.
예제:
import React, {useState, useEffect} from 'react';
const Info = () => {
// useState: 변수명, 설정 함수 , 초기 값
const [name, setName] = useState('');
const [nickname, setNickname] = useState('');
// useEffect는 기본적으로 랜더링되고 난 직후마다 실행되며, 두 번째 파라미터 배열에 무엇을 넣는지에 따라 실행되는 조건이 다름.
// 1. 랜더링 될때 (생성 및 업데이트)될때마다 실행 > 값이 변경되면 재실행
useEffect(() => {
console.log('기본 useEffect', name, nickname);
});
// 2. 빈 [] > 마운트 될때만 실행 값이 변경되어도 재실행 X >
useEffect(() => {
console.log('[] 사용한 useEffect', name, nickname);
}, []);
// 3. []안 명시 > 특정 값이 업데이트될때만 실행
useEffect(() => {
console.log('특정 값이 업데이트 될때만 실행되는 useEffect', name, nickname);
}, [name]);
// 4. cleanUp -1 > 빈 배열 사용한 경우 : unMounted 될 경우 호출
useEffect(() => {
// 컴포넌트가 unMounted / update 되기 직전에 어떠한 작업을 수행하고 싶을 경우 cleanUp 함수 반환
return () => {
console.log("cleanUp 함수 사용하여, unMounted 되기 전에 실행:: ");
};
}, []);
// 5. cleanUp -2 > [] 안 특정 값 사용: unMounted 될 경우 호출
useEffect(() => {
// 컴포넌트가 unMounted / update 되기 직전에 어떠한 작업을 수행하고 싶을 경우 cleanUp 함수 반환
return () => {
console.log("cleanUp 함수 사용하여, name 이 update 되기 전에 실행::", name);
};
}, [name]);
const onChangeName = e => setName(e.target.value);
const onChangeNickname = e => setNickname(e.target.value);
return (
<div>
<div>
<input type="text" value={name} onChange={onChangeName} placeholder="이름"/>
<input type="text" value={nickname} onChange={onChangeNickname} placeholder="닉네임"/>
</div>
<div>
<div>
<b> 이름 : </b> {name}
</div>
<div>
<b> 닉네임 : </b> {nickname}
</div>
</div>
</div>
);
};
export default Info;
useReducer
- 함수형 컴포넌트에서 Redux와 비슷한 방식으로 상태를 관리.
- useReducer를 사용하여 복잡한 상태 로직을 더 쉽게 관리.
예제
import React, {useReducer} from "react";
/*
* useReducer 는 더 다양한 컴포넌트 상황에 따라 다양한 상태를 다른 값으로 업데이트 해 주고 싶을 때 사용.
* reducer 는 현재 상태, 그리고 업데이트를 위해 필요한 정보를 담은 액션 값을 전달받아 새로운 상태로 반환하는 함수.
* reducer 에서 새로운 상태를 만들 때는 반드신 불변성을 지켜야 함.
*/
const reducer = (state, action) => {
// action.type 에 따라 다른 작업 수행
switch (action.type) {
case 'INCREMENT':
return {value: state.value + 1};
case 'DECREMENT':
return {value: state.value - 1};
default:
return state;
}
};
const Counter = () => {
/**
* useReducer(reducer 함수, 해당 리듀서의 기본값)을 parameter를 받아서, state & dispatch 함수를 받아옴.
* State > 현재 가리키고 있는 상태
* Dispatch > Action 을 발생시키는 함수
* */
const [state, dispatch] = useReducer(reducer, {value: 0});
return (
<div>
<p> 현재 카운터 값은 {state.value} 입니다</p>
<button onClick={() => dispatch({type: 'INCREMENT'})}>+1</button>
<button onClick={() => dispatch({type: 'DECREMENT'})}>-1</button>
</div>
)
};
export default Counter;
useMemo
- useMemo 훅은 계산 비용이 많은 연산의 결과를 기억하고, 의존성이 변경될 때만 재계산
- 주로 계산 비용이 많은 연산을 최적화하는 데 사용.
예제
import React, {useMemo, useState} from "react";
/*
* useMemo > 내부에서 발생하는 연산을 최적화
* useEffect 와 비슷하게, [] 안의 값을 줘서, 해당 값이 변경될 때만 수행
*/
const getAverage = (numbers) => {
if (numbers.length === 0) return 0;
const sum = numbers.reduce((a, b) => a + b);
return sum / numbers.length;
};
const Average = () => {
const [list, setList] = useState([]);
const [number, setNumber] = useState('');
const onChange = e => {
setNumber(e.target.value)
};
const onClick = () => {
const newList = [...list, parseInt(number)];
setList(newList);
setNumber('');
};
const listArr = list.map((item, index) => {
return (<li key={index}>{item}</li>);
});
const avg = useMemo(() => getAverage(list), [list]);
// []안의 내용이 바뀔 때만, getAverage 함수 수행. 여기서는 list 변할 때만, getAverage() 호출
return (
<div>
<input type="number" value={number} onChange={onChange}/>
<button onClick={onClick}>등록</button>
<ul>{listArr}</ul>
<div>
{/*<b>평균값: </b> {getAverage(list)}*/}
{/*이 상태로 사용되면, 인풋이 수정될 때마다 호출됨으로 메모리 낭비*/}
{/* useMemo를 사용하여 최적화 작업 수행*/}
<b>평균값: </b> {avg}
</div>
</div>
);
};
export default Average;
useCallback
- useCallback 훅은 콜백 함수를 메모이제이션하여 의존성이 변경될 때만 새로운 콜백 함수를 생성.
- 주로 자식 컴포넌트에 콜백 함수를 전달할 때 사용되며, 불필요한 렌더링을 방지하여 성능을 최적화
예제
import React, {useMemo, useCallback, useState} from "react";
/*
* useCallback > 랜더링 성능 최적화를 위해 사용되며, 컴포넌트가 리렌더링될 때마다, 새로 만들어진 함수를 사용해주는데 이를 막기 위해 사용되는 메모이제이션 기술 (함수의 반환 값을 캐시하여 동일한 인자가 입력될 때 이전에 계산된 값을 반환하는 기술)
* 1번째 parameter > 생성하고 싶은 함수
* 2번째 parameter > 배열을 넣어, 어떤 값이 바뀌었을 때 함수를 새로 생성하는지 명시
* 빈 배열 [] 인 경우, 컴포넌트가 랜더링될 때 만들었던 함수를 계속 사용 (매 렌더링마다 새로 생성)
* 배열안에 값을 넣을 경우 [number, list] 인 경우, number 혹은 list가 바뀔 때 새로 만들어진 함수를 사용
*/
const getAverage = (numbers) => {
if (numbers.length === 0) return 0;
const sum = numbers.reduce((a, b) => a + b);
return sum / numbers.length;
};
const Average = () => {
const [list, setList] = useState([]);
const [number, setNumber] = useState('');
// 빈 배열을 사용하는 경우, 컴포넌트가 처음 렌더링될 때만 함수 생성
const onChange = useCallback(e => {
setNumber(e.target.value);
}, []);
// 차 있는 배열을 사용하는 경우, number | list 가 변경될 때만, 새로 생성됨
const onClick = useCallback(() => {
const newList = [...list, parseInt(number)];
setList(newList);
setNumber('');
}, [number, list]); // number | list 가 변했을 때만 수행
const listArr = list.map((item, index) => {
return (<li key={index}>{item}</li>);
});
const avg = useMemo(() => getAverage(list), [list]);
// []안의 내용이 바뀔 때만, getAverage 함수 수행. 여기서는 list 변할 때만, getAverage() 호출
return (
<div>
<input type="number" value={number} onChange={onChange}/>
<button onClick={onClick}>등록</button>
<ul>{listArr}</ul>
<div>
{/*<b>평균값: </b> {getAverage(list)}*/}
{/*이 상태로 사용되면, 인풋이 수정될 때마다 호출됨으로 메모리 낭비*/}
{/* useMemo를 사용하여 최적화 작업 수행*/}
<b>평균값: </b> {avg}
</div>
</div>
);
};
export default Average;
useRef
- useRef 훅은 컴포넌트의 렌더링과 상관없이 변수를 유지하고자 할 때 사용
- 주로 DOM 요소의 참조를 보관하거나, 이전 값을 유지하는 데 사용
예제
import React, {useMemo, useState, useCallback, useRef} from "react";
/*
* useRef > 함수형 컴포넌트에서 ref를 더 쉽게 사용할 수 있도록 함.
* ref 안의 값이 바뀌어도, 컴포넌트가 렌더링되지 않음.
* 목표: 등록 버튼을 클릭한 경우, 포커스가 인풋쪽으로 넘어가도록 함
*/
const getAverage = (numbers) => {
if (numbers.length === 0) return 0;
const sum = numbers.reduce((a, b) => a + b);
return sum / numbers.length;
};
const Average = () => {
const [list, setList] = useState([]);
const [number, setNumber] = useState('');
const inputEl = useRef(null);
// 빈 배열을 사용하는 경우, 컴포넌트가 처음 렌더링될 때만 함수 생성
const onChange = useCallback(e => {
setNumber(e.target.value);
}, []);
// 차 있는 배열을 사용하는 경우, number | list 가 변경될 때만, 새로 생성됨
const onClick = useCallback(() => {
const newList = [...list, parseInt(number)];
setList(newList);
setNumber('');
inputEl.current.focus(); // 객체 안의 current 값이 실제 element를 가리켜, 포커스가 인풋쪽으로 넘어감.
}, [number, list]); // number | list 가 변했을 때만 수행
// []안의 내용이 바뀔 때만, getAverage 함수 수행. 여기서는 list 변할 때만, getAverage() 호출
const avg = useMemo(() => getAverage(list), [list]);
const listArr = list.map((item, index) => {
return (<li key={index}>{item}</li>);
});
return (
<div>
<input type="number" value={number} onChange={onChange} ref={inputEl}/>
<button onClick={onClick}>등록</button>
<ul>{listArr}</ul>
<div>
{/*<b>평균값: </b> {getAverage(list)}*/}
{/*이 상태로 사용되면, 인풋이 수정될 때마다 호출됨으로 메모리 낭비*/}
{/* useMemo를 사용하여 최적화 작업 수행*/}
<b>평균값: </b> {avg}
</div>
</div>
);
};
export default Average;
custom Hooks (using useReducer)
예제
import React, {useReducer} from "react";
/*
* customHooks > 스스로 만든 Hooks
* 목표: 여러 개의 인풋을 관리하기 위해 useReducer로 작성했던 로직을 useInputs라는 Hook으로 분리
*/
const reducer = (state, action) => {
return {
...state,
[action.name]: action.value
};
};
const useInputs = (initialForms) => {
const [state, dispatch] = useReducer(reducer, initialForms);
const onChange = e => dispatch(e.target);
return [state, onChange];
};
export default useInputs;
답변
1. Class Component 에서는 Life Cycle Methods를 사용해서 수명주기 동안 다양한 작업을 수행할 수 있는데, Functional Component에서는 어떻게 할 수 있어?
- Hooks를 통해 Class Component의 Life Cycle Methods 처럼 사용할 수 있음.
2. 각각 종류를 알려줘.
- useState, useEffectm useReducer, useMemo, useCallback, useRef, custom Hooks