2019년 2월 6일, 페이스북은 리액트 v16.8을 발표합니다. React v16.8: The One With Hooks!
기존 함수 컴포넌트에서는 상태 관리
, 렌더링 후 작업 설정
을 할 수 없었는데, Hooks가 이 문제를 해결했습니다. Hooks 덕분에 페이스북은 클래스형 컴포넌트 대신 함수형 컴포넌트를 권장하게 됐습니다.
Hooks의 사용은 Function Programming의 첫 걸음입니다. Object Oriented Programming에 익숙한 제게 FP은 낯설었습니다. OOP도 완벽하게 사용하지 못하는데 또 무언가 배우는 것이 부담되기도 했습니다. 그러나 진화하기 위해선 변화해야 한다는 점을 되새기며 공부했습니다. 오늘보다 내일 더 발전된 사람이 되길 바랍니다 :)
가장 기본적인 Hook입니다. 함수 컴포넌트에서도 가변적인 상태를 지닐 수 있게 해 줍니다.
① useState는 배열을 반환합니다.
② useState의 첫 번째 원소는 상태 값, 두 번째 원소는 상태를 설정합니다.
③ useState(0)으로 상태의 기본값을 초기화합니다. (value == 0)
import { useState } from 'react';
const Counter = () => {
const [value, setValue] = useState(0);
return (
<>
<p>
현재 값은 {value}입니다.
</p>
<button onClick={() => setValue(value + 1)}>+1</button>
<button onClick={() => setValue(value - 1)}>-1</button>
</>
);
};
export default Counter;
컴포넌트가 렌더링될 때마다 특정 작업을 수행합니다. 클래스형 컴포넌트의 componentDidMount
와 componentDidUpdate
를 합친 형태입니다. 콜백 지옥을 벗어나게 해주는 중요한 Hook입니다.
<React.StrickMode>
를 사용하면 useEffect
가 두 번 호출됩니다. 코드에 문제 여부를 확인하기 위함입니다. 추후 컴포넌트가 사라졌다가 다시 나타나도 컴포넌트의 상태를 유지하는 기능이 도입될 거라고 합니다.
useEffect는 두 번째 파라미터가 중요합니다.
두 번째 파라미터에 어떤 배열을 넣느냐에 따라 실행 시점이 결정됩니다.
import { useState, useEffect } from 'raect;
const Info = () => {
const [name, setName] = useState('');
const [nickname, setNickname] = useState('');
useEffect(() => {
name,
nickname
});
});
const onChangeName = e => {
setName(e.target.value);
};
const onChangeNickname = e => {
setNickname(e.target.value);
};
return (
(...)
);
};
export default Info;
useEffect
에서 설정한 함수가 초기 렌더링 때만 실행되고, 업데이트될 때는 실행되지 않게하려면 함수의 두 번째 파라미터로 빈 배열을 넣어주면 됩니다. 이는 콜백 지옥을 벗어나게 해줍니다.
useEffect(() => {
}, []);
마운트될 때만 실행하고 싶을 때와는 다르게 업데이트될 때만 실행하고 싶을 때는 두 번째 파라미터로 업데이트 되는 값을 넣어줍니다.
useEffect(() => {
}, [name]);
뒷정리 함수(cleanup)를 반환해주어야 합니다.
useEffect(() => {
return () => {
};
}, [name]);
useState
보다 더 다양한 상황에서 상태를 다른 값으로 업데이트할 때 사용합니다. useReducer
는 현재 상태, 액션 값(업데이트를 위해 필요한 정보를 담은 것)을 전달받아 새로운 상태를 반환합니다.
useReducer
에서 새로운 상태를 만들 때 반드시 불변성을 지켜야 합니다.
첫 번째 파라미터에는 reducer 함수를, 두 번째 파라미터에는 해당 reducer 함수의 기본값을 넣습니다.
reducer는 state와 dispatch를 반환합니다.
state는 현재 가리키고 있는 상태이고, dispatch는 액션을 발생시키는 함수입니다.
import { useReducer } from 'react';
fuction 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 = () => {
const [state, dispatch] = useReducer(reducer, {value: 0});
return (
<>
<p>
{state.value}
</p>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>+1</button>
<button onClick={() => dispatch({ type: 'DECREMENT' })}>-1</button>
</>
);
};
export default Counter;
함수 컴포넌트 내부에서 발생하는 연산을 최적화합니다. 렌더링 과정에서 특정 값이 바뀌었을 때만 연산합니다. 값이 바뀌지 않았다면 이전에 연산한 결과를 다시 사용합니다.
import { useState, useMemo } from 'react;
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 onInsert = () => {
const nextList = list.concat(parseInt(number));
setList(nextList);
setNumber('');
};
const avg = useMemo(() => getAverage(list), [list]);
return (
<>
<input value={number} onChange={onChange} />
<button onClick={onInsert}>등록</button>
<ul>
{list.map((value, index) => (
<li key={index}>{value}</li>
)))};
</ul>
<>
{avg}
</>
</>
);
};
export default Average;
useMemo
와 상당히 비슷합니다. 렌더링 성능을 최적화할 때 사용합니다. useCallback
은 만들어 둔 함수를 재사용할 수 있게 해줍니다.
첫 번째 파미터에는 생성하고 싶은 함수를
두 번째 파라미터에는 배열을 넣으면 됩니다.
이 배열에는 어떤 값이 바뀌었을 때 함수를 새로 생성해야 하는지 명시합니다.
import { useState, useMemo, useCallback } from 'react';
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);
}, []);
const onInsert = useCallback(() => {
const nextList = list.concat(parseInt(number));
setList(nextList);
setNumber('');
}, [number, list]);
const avg = useMemo(() => getAverage(list), [list]);
return (
<>
<input value={number} onChange={onChange} />
<button onClick={onInsert}>등록</button>
<ul>
{list.map((value, index) => (
<li key={index}>{value}</li>
))}
</ul>
<>
{avg}
</>
</>
);
};
export default Average;
함수 컴포넌트에서 ref를 쉽게 사용할 수 있게 해줍니다. useRef
는 ref의 값이 바뀌어도 컴포넌트가 렌더링되지 않습니다. 렌더링에 관련되지 않은 값을 관리할 때 사용하면 유용합니다.
import { useRef } from 'react';
const RefSample = () => {
const id = useRef(1);
const setId = (n) => {
id.current = n;
}
const printId = () => {
conssole.log(id.current);
}
return (
<>
refSample
</>
);
};
export default RefSample;
이 외에도 사용자 직접 Hook을 작성하여 사용할 수도 있습니다. Hooks 패턴을 사용하면 클래스형 컴포넌트를 사용하지 않고도 대부분의 기능을 구현할 수 있습니다.
리액트를 다루는 기술
김민준 지음ㅣ길벗ㅣ2019ㅣ도서 정보