이전에는 컴포넌트의 상태관리나 생명주기에 따라 작업을 수행할 때에는 클래스형 컴포넌트만 사용해야 했다. 그러나 Hooks이 등장하며 함수형 컴포넌트에서도 상태를 관리할 수 있게 되었다.
useState를 호출하면 변수와 그 변수를 수정할 수 있는 새터 함수를 배열로 반환한다. 관리해야 하는 상태의 수만큼 여러번 사용할 수 있다. 상태 관리 변수는 반드시 새터 함수를 이용해 값을 변경해야 한다.
새터 함수를 사용하는 방법은 두가지가 있다.
새터 함수에 변경될 상태의 값을 전달하기
새터 함수의 파라미터에 함수를 전달하기
setState(prevState => {});
새터 함수는 비동기로 동작하기 때문에 상태 변경이 여러번 일어나면 상태가 변경되기 전에 또다시 상태에 대한 업데이트가 실행되는 상황이 발생한다. 이럴 땐 새터함수에 함수를 인자로 전달하여 이전 상태값을 이용한다.
<Button
title="+"
onPress={() => {
setCount(count + 1);
setCount(count + 1);
}}
/>
<Button
title="+"
onPress={() => {
setCount(prevCount => prevCount + 1);
setCount(prevCount => prevCount + 1);
}}
/>
컴포넌트가 렌더링될 때마다 원하는 작업이 실행되도록 설정할 수 있는 기능이다. useEffect의 첫번째 파라미터로 전달된 함수는 조건을 만족할 때마다 호출되고, 두번째 파라미터로 전달되는 배열로 호출되는 조건을 설정할 수 있다.
useEffect(() => {
console.log(`name: ${name}, email: ${email}\n`);
}, [email]);
//이메일이 변경될 때마다 실행된다
실행조건이 컴포넌트가 마운트 될 때로 설정한다면 아래와 같다
useEffect(() => {
console.log('\n===== Form Component Mount =====\n');
, []);
언마운트 될 때 실행하려면 실행조건, 첫번째 파라미터를을 빈 배열로 만든다.
useEffect(() => {
console.log('\n===== Form Component Mount =====\n');
refName.current.focus();
return () => console.log('\n===== Form Component Unmount =====\n');
}, []);
특정 컴포넌트를 선택해야 하는 경우 사용된다(ex. 포커스를 옮길 때)
useRef를 사용할 때 주의할 점이 있다.
return (
<>
<StyledText>Name: {name}</StyledText>
<StyledText>Email: {email}</StyledText>
<StyledTextInput
value={name}
onChangeText={text => setName(text)}
placeholder="name"
ref={refName}
returnKeyType="next"
onSubmitEditing={() => refEmail.current.focus()}
/>
<StyledTextInput
value={email}
onChangeText={text => setEmail(text)}
placeholder="email"
ref={refEmail}
returnKeyType="done"
/>
</>
);
};
export default Form;
동일한 반복 수행을 제거하여 성능을 최적화시킨다. 첫번째 파라미터에는 함수를 전달하고 두번째 파라미터에는 함수 실행 조건을 배열로 전달한다.
import React, { useState, useMemo } from 'react';
import styled from 'styled-components/native';
import Button from './Button';
const StyledText = styled.Text`
font-size: 24px;
`;
const getLength = text => {
console.log(`Target Text: ${text}`);
return text.length;
};
const list = ['JavaScript', 'Expo', 'Expo', 'React Native'];
let idx = 0;
const Length = () => {
const [text, setText] = useState(list[0]);
const _onPress = () => {
++idx;
if (idx < list.length) setText(list[idx]);
};
const length = useMemo(() => getLength(text), [text]);
/*사용하지 않으면 값의 변경이 없더라도 계속 같은 문자열의 길이를 구한다.*/
return (
<>
<StyledText>Text: {text}</StyledText>
<StyledText>Length: {length}</StyledText>
<Button title="Get Length" onPress={_onPress} />
</>
);
};
export default Length;
특정 API에 GET요청을 보내고 응답을 받는 함수를 만들어보자. 이번에는 Fetch를 이용하여 uesFetch라는 이름의 Hook을 만들 것이다.
hooks/useFetch.js
import { useState, useEffect } from 'react';
export const useFetch = url => {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [inProgress, setInProgress] = useState(false);
useEffect(() => {
const fetchData = async () => {
try {
setInProgress(true);
const res = await fetch(url);
const result = await res.json();
if (res.ok) {
setData(result);
setError(null);
} else {
throw result;
}
} catch (error) {
setError(error);
} finally {
setInProgress(false);
}
};
fetchData();
}, []);
return { data, error, inProgress };
};
📝 실행 흐름 요약
- 컴포넌트가 처음 렌더링되면 useEffect가 실행된다.
- fetchData 함수가 호출되고 inProgress가 true로 바뀐다.
- fetch(url)을 실행하고 응답을 JSON으로 변환한다.
- 응답이 성공(res.ok)이라면 data에 결과를 저장하고 error를 null로 만든다.
- 응답이 실패하면 throw되어 catch로 넘어가고 error 상태에 저장된다.
- try/catch가 끝나면 finally가 실행되어 inProgress가 false로 바뀐다.
- 최종적으로 훅은 data, error, inProgress 값을 반환한다.
components/Dogs.js
import React from 'react';
import styled from 'styled-components/native';
import { useFetch } from '../hooks/useFetch';
const StyledImage = styled.Image`
background-color: #7f8c8d;
width: 300px;
height: 300px;
`;
const ErrorMessage = styled.Text`
font-size: 18px;
color: #e74c3c;
`;
const LoadingMessage = styled.Text`
font-size: 18px;
color: #2ecc71;
`;
const URL = 'https://dog.ceo/api/breeds/image/random';
const Dog = () => {
const { data, error, inProgress } = useFetch(URL);
return (
<>
{inProgress && (
<LoadingMessage>The API request is in progress</LoadingMessage>
)}
<StyledImage source={data?.message ? { uri: data.message } : null} />
<ErrorMessage>{error?.message}</ErrorMessage>
</>
);
};
export default Dog;
📝 실행 흐름 요약
- Dog 컴포넌트가 렌더링되면 useFetch(URL)이 호출된다.
- useFetch 내부의 useEffect가 실행되어 API 요청이 시작되고 inProgress가 true가 된다.
- fetch로 강아지 랜덤 이미지 API를 호출하고 JSON 데이터를 받아온다.
- 요청이 성공하면 data에 응답이 저장되고 error는 null이 된다.
- 요청이 실패하면 error에 에러 정보가 저장된다.
- 요청이 끝나면 inProgress가 false로 바뀐다.
- Dog 컴포넌트는 상태에 따라 다음을 렌더링한다:
- inProgress가 true일 때 로딩 메시지를 표시한다.
- data.message가 있으면 해당 url의 이미지를 StyledImage에 표시한다.
- error가 있으면 ErrorMessage로 에러 메시지를 표시한다.