useState는 가장 기본적인 Hook이며, 함수 컴포넌트에서도 가변적인 상태를 지닐 수 있게 도와준다.
함수 컴포넌트에서 상태를 관리해야 한다면 이 Hook을 사용하면 된다.
useState를 사용한 간단한 Counter 컴포넌트
// Counter.js
import React, { useState } from 'react';
const Counter = () => {
const [state, setState] = useState(0);
return (
<div>
<p>Count: <p>{state}</p></p>
<button onClick={() => setState(state+1)}>+1</button>
<button onClick={() => setState(state-1)}>-1</button>
</div>
);
}
export default Counter;
useState를 여러번 사용한 간단한 Info 컴포넌트
// Info.js
// useState를 여러번 사용하기(컴포넌트에서 관리할 상태가 여러 개라면...)
import React, { useState } from 'react';
const Info = () => {
const [name, setName] = useState('');
const [nickname, setNickname] = useState('');
const onChangeName = (e) => {
setName(e.target.value);
}
const onChangeNickname = (e) => {
setNickname(e.target.value);
}
return (
<div>
<input value={name} onChange={onChangeName} />
<input value={nickname} onChange={onChangeNickname} />
<p>{name}({nickname})</p>
</div>
);
}
export default Info;
useEffect는 컴포넌트가 렌더링 될 때마다 특정 작업을 수행하도록 설정할 수 있는 Hook이다.
클래스형 컴포넌트의 componentDidMount와 ComponentDidUpdate를 합친 형태로 보아도 무방하다.
컴포넌트가 렌더링 될때 실행.
useEffect(() => {
console.log('컴포넌트가 렌더링되면 출력됨.');
});
마운트될 때만 실행
// useEffect 함수의 두번째 파라미터로 비어있는 배열을 입력.
useEffect(() => {
console.log('마운트(처음 렌더링)될 때만 출력됨.');
}, []);
특정 값이 업데이트될 때만 실행
// useEffect 함수의 두번째 파라미터로 검사하고 싶은 값을 입력.
useEffect(() => {
console.log(`${name}이 업데이트될 때만 출력됨.`);
}, [name]);
컴포넌트가 언마운트될 때 실행
// useEffect 함수에서 뒷정리(cleanup) 함수를 return.
useEffect(() => {
return () => {
console.log('컴포넌트가 언마운트 되면 출력됨.');
};
}, []);
useReducer는 useState 보다 더 다양한 컴포넌트 상황에 따라 다양한 상태를 다른 값으로 업데이트 해주고 싶을 때 사용하는 Hook.
리듀서는 현재 상태, 액션값(업데이트를 위해 필요한 정보)을 전달받아 새로운 상태를 반환하는 함수이다.
리듀서 함수에서 새로운 상태를 만들 때는 반드시 불변성을 지켜야 한다.
function reducer(state, action) {
return { ... }; // 불변성을 지키면서 업데이트한 새로운 상태를 반환.
}
useReducer의 첫번째 파라미터에는 리듀서 함수를 넣고, 두번째 파라미터에는 해당 리듀서의 기본값을 넣어 줍니다.
const [state, dispatch] = useReducer(reducer, { 기본값 });
// state: 현재 가리키고 있는 상태.
// dispatch: 액션을 발생시키는 함수(함수 안에 파라미터로 액션값을 넣어 주면 리듀서 함수가 호출됨.)
useReducer를 이용한 간단한 Info 컴포넌트
// Info.js
import React, { useReducer } from 'react';
function reducer(state, action) {
return {
...state,
[action.name]: action.value
};
}
const Info = () => {
const [state, dispatch] = useReducer(reducer, {
name: '',
nickname: ''
});
const { name, nickname } = state;
const onChange = (e) => {
dispatch(e.target);
}
return (
<div>
<input name='name' value={name} onChange={onChange} />
<input name='nickname' value={nickname} onChange={onChange} />
<p>{name}({nickname})</p>
</div>
);
}
export default Info;
useMemo를 사용하면 함수 컴포넌트 내부에서 발생하는 연산을 최적화 할 수 있다.
렌더링 하는 과정에서 특정 값이 바뀌었을 때만 연산을 실행하고, 원하는 값이 바뀌지 않았다면 이전에 연산했던 결과를 다시 사용하는 방식.
useMemo를 이용한 간단한 Average 컴포넌트
// Average.js
import React, { useState, useMemo } from 'react';
const getAverage = (numbers) => {
console.log('평균값 계산중...');
if(numbers.length === 0) return 0;
const sum = numbers.reduce((arr, cur) => arr + cur);
return sum / numbers.length;
}
const Average = () => {
const [list, setList] = useState([]);
const [number, setNumber] = useState('');
const onChange = (e) => {
setNumber(e.target.value);
}
const onClick = () => {
setList([...list, parseInt(number)]);
setNumber('');
}
const avg = useMemo(() => getAverage(list), [list]);
// list 배열의 내용이 변경 될 때만 getAverage 함수가 호출된다.
return (
<div>
<input value={number} onChange={onChange} />
<button onClick={onClick}>등록</button>
<ul>
{list.map((val, idx) => (
<li key={idx}>{val}</li>
))}
</ul>
<div>
<b>평균값:</b> {avg}
</div>
</div>
);
}
export default Average;
useCallback은 useMemo와 유사한 함수이며, 주로 렌더링 성능을 최적화해야 하는 상황에서 사용한다.
이 Hook을 사용하면 만들어 놨던 함수를 재사용할 수 있다.
useCallback의 첫번째 파라미터에는 생성하고 싶은 함수를 넣고, 두번째 파라미터에는 배열을 넣는다. (배열에는 어떤 값이 바뀌었을 때 함수를 새로 생성해야 하는지 명시)
useMemo를 사용한 Average 컴포넌트를 useCallback을 이용하여 최적화
// Average.js
import React, { useState, useMemo, useCallback } from 'react';
const getAverage = (numbers) => {
console.log('평균값 계산중...');
if(numbers.length === 0) return 0;
const sum = numbers.reduce((arr, cur) => arr + cur);
return sum / numbers.length;
}
const Average = () => {
const [list, setList] = useState([]);
const [number, setNumber] = useState('');
const onChange = useCallback((e) => {
setNumber(e.target.value);
}, []); // 컴포넌트가 마운트 되었을 때만 함수 생성.
const onClick = useCallback(() => {
setList([...list, parseInt(number)]);
setNumber('');
}, [number, list]); // number 혹은 list가 변경 되었을때만 함수 생성.
const avg = useMemo(() => getAverage(list), [list]);
// list 배열의 내용이 변경 될 때만 getAverage 함수가 호출된다.
return (
<div>
<input value={number} onChange={onChange} />
<button onClick={onClick}>등록</button>
<ul>
{list.map((val, idx) => (
<li key={idx}>{val}</li>
))}
</ul>
<div>
<b>평균값: </b> {avg}
</div>
</div>
);
}
export default Average;
useRef Hook은 함수 컴포넌트에서 ref를 쉽게 사용할 수있도록 도와준다.
useRef를 사용하여 ref를 설정하면 useRef를 통해 만든 객체의 current 값이 실제 엘리먼트를 가리킨다.
useRef를 이용한 Average 컴포넌트(버튼 클릭시 input으로 focus이동)
import React, { useState, useMemo, useCallback, useRef } from 'react';
const getAverage = (numbers) => {
console.log('평균값 계산중...');
if(numbers.length === 0) return 0;
const sum = numbers.reduce((arr, cur) => arr + cur);
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);
}, []);
// 컴포넌트가 마운트 되었을 때만 함수 생성.
const onClick = useCallback(() => {
setList([...list, parseInt(number)]);
setNumber('');
inputEl.current.focus();
// useRef를 통해 만든 객체 안의 current 값이 실제 엘리먼트를 가리킨다.
}, [number, list]);
// number 혹은 list가 변경 되었을때만 함수 생성.
const avg = useMemo(() => getAverage(list), [list]);
// list 배열의 내용이 변경 될 때만 getAverage 함수가 호출된다.
return (
<div>
<input value={number} onChange={onChange} ref={inputEl}/>
<button onClick={onClick}>등록</button>
<ul>
{list.map((val, idx) => (
<li key={idx}>{val}</li>
))}
</ul>
<div>
<b>평균값: </b> {avg}
</div>
</div>
);
}
export default Average;
ref 안의 값이 바뀌어도 컴포넌트가 렌더링 되지 않는다. 렌더링과 관련되지 않은 값을 관리할 때만 이러한 방식의 코드를 작성).
import React, { useRef } from 'react';
const RefExample = () => {
const id = useRef(1);
const setId = (n) => {
id.current = n;
};
const printId = () => {
console.log(id.current);
};
return (
<div>
refExample
</div>
);
};
여러 컴포넌트에서 비슷한 기능을 공유할 경우, 이를 나만의 Hook으로 작성하여 로직을 재사용 할 수 있다.
커스텀 Hook을 사용한 간단한 Info 컴포넌트
// useInput.js
import { useReducer } from "react";
function reducer(state, action) {
return {
...state,
[action.name]: action.value
};
}
export default function useInputs(initialForm) {
const [state, dispatch] = useReducer(reducer, initialForm);
const onChange = (e) => {
dispatch(e.target);
}
return [state, onChange];
}
// Info.js
import useInputs from './useInputs.js';
const Info = () => {
const [state, onChange] = useInputs({
name: '',
nickname: ''
});
const { name, nickname } = state;
return (
<div>
<div>
<input name='name' value={name} onChange={onChange} />
<input name='nickname' value={nickname} onChange={onChange} />
</div>
<div>
<p>{name}({nickname})</p>
</div>
</div>
);
}
export default Info;
[참고] 김민준(Velopert)님의 '리액트를 다루는 기술'을 공부하며 정리한 내용임.