Hooks
Hooks는 리액트 v16.8에 새로 도입된 기능으로 함수형 컴포넌트에서 할 수 없었던 작업들 할 수 있게 해줌
ㄴ useState, useEffect 등
useState
useState는 가장 기본적인 Hook이며 함수형 컴포넌트에서도 가변적 상태 지닐 수 있게 해줌
함수형 컴포넌트에서 상태(state) 관리해야 한다면 useState 사용하면 됨.
useState를 이용한 숫자 카운터 구현
import React, {useState} from 'react';
// Counter.js
const Counter = () => {
// useState함수의 파라미터에는 상태의 기본값
// 카운터의 기본값을 0으로 설정
// useState 호출되면 배열 반환하는데
// 베열을 첫번째 원소는 상태값, 두번째는 상태값을 설정하는 함수
const [value, setValue] = useState(0); // 그 배열
return (
<div>
<p>
현재 카운터 값은 <b>{value}</b>입니다.
</p>
<button onClick = {() => setValue(value+1)}>+1</button>
<button onClick = {() => setValue(value-1)}>-1</button>
</div>
)
}
export default Counter;
하나의 useState함수는 하나의 상태값만 관리
관리해야 할 상태 여러개면 여러번 사용
import React, {useState, useEffect} from 'react';
// Info.js
const Info = () => {
const [name, setName] = useState('');
const [nickname, setNickname] = useState('');
useEffect(() => {
console.log("렌더링 완료!");
console.log({
name,
nickname
});
})
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}</p>
<p>닉네임은 {nickname}</p>
</div>
)
}
export default Info;
마운트 될때만 실행하고 싶다면 두번째 파라미터로 빈 배열
useEffect(() => {
console.log('마운트될 때만 실행됩니다.');
}, []);
특정 값이 업데이트될 때만 실행하고 싶다면
클래스형 에서는
componentDidUpdate(prevProps, prevState) {
if (prevProps.value !== this.props.value) {
doSomething();
}
}
이 코드는 props안의 value가 바뀔때만 작업 수행
이 작업은 useEffect에서는 이전의 두번째 빈 배열에 검사하고 싶은 값 넣으면 됨.
useEffect(() => {
console.log('이름 변할때만 실행됨!! ' + name);
}, [name]);
useEffect는 기본적으로 렌더링 직후마다 실행
언마운트 전이나 업데이트 직전에 어떤 작업 수행하고 싶다면 useEffect에서 뒷정리(cleanup) 함수 반환해 줘야 함
App 컴포넌트에서 Info 컴포넌트 가시성 바꾸기
import React, {useState} from 'react';
import Info from './Info'
const App = () => {
const [visible, setVisible] = useState(false);
return (
<div>
<button
onClick = {() => {
setVisible(!visible);
}}
>
{visible ? '숨기기' : '보이기'}
</button>
<hr/>
{visible && <Info />}
</div>
)
};
export default App;
Counter를 useReducer 사용해 다시 구현
import React, {useReducer} from 'react';
function reducer(state, action) {
// action.tyle에 따라 다른 작업 수행
switch(action.type) {
case 'INCREMENT' :
return {value: state.value + 1};
case 'DECREMENT' :
return {value: state.value - 1};
default:
return state;
}
}
const Counter = () => {
// useReducer의 첫 파라미터에 리듀서 함수, 두번째에 해당 리듀서의 기본 값 넣기
// 이 Hook 사용하면 state값과 dispatch함수 받아 엄
// state는 현재 가리키는 상태이고
// dispatch는 액션 발생시키는 함수
// dispatch(action) 형태로 함수안에 파라미터로 액션 값 넣어주면 리듀서 함수가 호출됨
const [state, dispatch] = useReducer(reducer, {value : 0});
return (
<div>
<p>
현재 카운터 값은 <b>{state.value}</b>입니다!
</p>
<button onClick = {() => dispatch({type: 'INCREMENT'})}>+1</button>
<button onClick = {() => dispatch({type: 'DECREMENT'})}>-1</button>
</div>
)
}
export default Counter;
useReducer의 가장 큰 장점은 컴포넌트의 업데이트 로직을 컴포넌트 바깥으로 빼낼 수 있다는 것
// Info.js
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>
<div>
<input name = "name" value = {name} onChange={onChange} />
<input name = "nickname" value = {nickname} onChange={onChange} />
</div>
<div>
<b>이름 : </b> {name}
</div>
<div>
<b>닉네임: </b> {nickname}
</div>
</div>
)
}
useReducer에서의 액션은 어떤 값이든 사용 가능
위에서 이벤트 객체가 지니고 있는 e.target자체를 액션값으로 사용
인풋 개수 많아져도 코드 짧고 깔끔하게 유지 가능
import React, {useState, useMemo} from 'react';
// Average.js
const getAverage = numbers => {
console.log('평균 값 계산 중...');
if (numbers.length === 0) return 0;
const sum = numbers.reduce((acc, val) => acc + val);
return sum / numbers.length;
}; // 숫자 등록할때만 아니라 인풋 내용 수정될 때로 getAverage 함수 호출됨
// 인풋 내용 바뀔 때는 평균값 다시 계산할 필요 업음
// 렌더링 마다 계산하는 것은 낭비
// useMemo 사용하면 작업 최적화 가능
// 렌더링 과정에서 특정 값 바뀌었을때만 연산 실행하고
// 바뀌지 않았다면 잊너 연산 결과 다시 사용
const Average = () => {
const [list, setList] = useState([]);
const [number, setNumber] = useState('');
const onChange = e => {
setNumber(e.target.value);
};
const onInsert = e => {
const nextList = list.concat(parseInt(number));
setList(nextList);
setNumber('');
};
// list 배열 내용 바뀔 때만 getAverage 함수 호출 됨
const avg = useMemo(() => getAverage(list), [list]);
return (
<div>
<input value = {number} onChange = {onChange} />
<button onClick = {onInsert}>등록</button>
<ul>
{list.map((value, index) => {
<li key = {index}>{value}</li>
})}
</ul>
<div>
<b>평균값: </b>{avg}
</div>
</div>
)
}
export default Average;
렌더링 성능 최적화해야 하는 경우 사용
이 함수 사용 시 이벤트 핸들러 함수 필요할 때만 생성할 수 있음
컴포넌트의 렌더링이 자주 발생하거나 렌더링해야 할 컴포넌트가 많아지면 최적화 하는것이 좋음
import React, {useState, useMemo, useCallback} from 'react';
const getAverage = numbers => {
console.log('평균값 계산 중..');
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);
}, []); // 컴포넌트가 처음 렌더링 될 때만 함수 생성
const onInsert = useCallback(() => {
const nextList = list.concat(parseInt(number));
setList(nextList);
setNumber('');
}, [number, list]); // number 혹은 list 바뀌었을때만 함수 생성
const avg = useMemo(() => getAverage(list), [list]);
return (
<div>
<input value = {number} onChange={onChange} />
<button onClick={onInsert}>등록</button>
<ul>
{list.map((value, index) => {
<li key = {index}>{value}</li>
})}
</ul>
<div>
<b>평균값: {avg}</b>
</div>
</div>
)
}
export default Average;
useRef
커스텀 Hooks
정리
Hooks 사용하면 클래스형 컴포넌트 작성하지 않고도 대부분 기능 구현 가능
함수형 컴포넌트와 Hooks 사용을 권고
꼭 필요할 때만 클래스 컴포넌트 구현