1. 요청 보내고 응답 처리 하기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
import React, { useState, useEffect, useCallback } from 'react';
import MoviesList from './components/MoviesList';
import './App.css';
function App() {
const [movies, setMovies] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
async function fetchMoviesHandler() => {
// 초기화
setIsLoading(true);
setError(null);
try {
// 데이터 fetch
const response = await fetch('https://swapi.dev/api/films/');
if (!response.ok) {
throw new Error('Something went wrong!');
}
const data = await response.json();
// 받아온 정보를 저장한다.
const transformedMovies = data.results.map((movieData) => {
return {
id: movieData.episode_id,
title: movieData.title,
openingText: movieData.opening_crawl,
releaseDate: movieData.release_date,
};
});
// 데이터를 가져오면 transformedMovies에 저장하고 loading은 끝난다.
setMovies(transformedMovies);
setIsLoading(false);
}
// 데이터를 못가져오면 에러 메시지를 저장한다.
catch (error) {
setError(error.message);
}
});
let content = <p>Found no movies.</p>;
// 정상적으로 데이터를 fetch 해왔을 때
if (movies.length > 0) {
content = <MoviesList movies={movies} />;
}
// error가 발생했을 때
if (error) {
content = <p>{error}</p>;
}
// 로딩중 일 때
if (isLoading) {
content = <p>Loading...</p>;
}
return (
<React.Fragment>
<section>
<!--버튼을 누르면 데이터를 fetch 한다. -->
<button onClick={fetchMoviesHandler}>Fetch Movies</button>
</section>
<section>{content}</section>
</React.Fragment>
);
}
export default App;
2. useEffect() 활용하기
특정 컴포넌트가 로딩되자마자 데이터를 가져오기도 한다.
이 때 사용할 수 있는게 useEffect()이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import React, { useState, useEffect, useCallback } from 'react';
import MoviesList from './components/MoviesList';
import './App.css';
function App() {
const [movies, setMovies] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
// useCallback을 사용하는 이유는 참조 동일성 때문이다.
// 함수가 변경되면 useEffect가 재실행되기 때문에 useCallback을 사용하지 않으면 무한루프의 위험이 있다.
const fetchMoviesHandler = useCallback(async () => {
setIsLoading(true);
setError(null);
try {
const response = await fetch('https://swapi.dev/api/films/');
if (!response.ok) {
throw new Error('Something went wrong!');
}
const data = await response.json();
const transformedMovies = data.results.map((movieData) => {
return {
id: movieData.episode_id,
title: movieData.title,
openingText: movieData.opening_crawl,
releaseDate: movieData.release_date,
};
});
setMovies(transformedMovies);
} catch (error) {
setError(error.message);
}
setIsLoading(false);
}, []);
// 함수가 의존성 배열에 들어가있으므로, 함수는 useCallback을 사용해야 한다.
useEffect(() => {
fetchMoviesHandler();
}, [fetchMoviesHandler]);
let content = <p>Found no movies.</p>;
if (movies.length > 0) {
content = <MoviesList movies={movies} />;
}
if (error) {
content = <p>{error}</p>;
}
if (isLoading) {
content = <p>Loading...</p>;
}
return (
<React.Fragment>
<section>
<button onClick={fetchMoviesHandler}>Fetch Movies</button>
</section>
<section>{content}</section>
</React.Fragment>
);
}
export default App;
- HTTP 요청 전송은 일종의 사이드 이펙트로 컴포넌트의 상태를 바꿔버리기 때문에 useEffect()를 사용할 수 있다.
3. 커스텀 훅 제작하기
1) 커스텀 훅이란
커스텀 훅이란 상태를 설정할 수 있는 로직을 포함한 함수이다.
커스텀 훅을 만들어서, 재 사용 가능한 함수에 상태를 설정하는 로직을 아웃소싱할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// src/hooks/use-counter.js
// 위의 예시는 props로 넘기는 값에 따라 +를 하는 카운터를 만들거나 -를 하는 카운터를 만든다.
import { useState, useEffect } from 'react';
// 함수 인자를 통해 재활용성을 높일 수 있다.
const useCounter = (forwards = true) => {
const [counter, setCounter] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
if (forwards) {
setCounter((prevCounter) => prevCounter + 1);
} else {
setCounter((prevCounter) => prevCounter - 1);
}
}, 1000);
return () => clearInterval(interval);
}, [forwards]);
return counter;
};
export default useCounter;
- 커스텀 훅을 통해, 다른 컴포넌트에서 사용할 수 있는 로직을 커스텀 훅으로 아웃 소싱 할 수 있다.
- 함수 이름은
use
로 시작해야 한다.- 리액트에게 이 함수가 커스텀 훅임을 알려주며 이는 리액트가 해당 함수를 훅의 규칙에 따라 사용하겠다고 보장하는 것이다.
2) useHttp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// src/hooks/use-http.js
import { useState, useCallback } from 'react';
const useHttp = () => {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
// useCallback을 사용하는 이유는 다음과 같다.
// 훅을 사용할 때, useEffect로 해당 함수를 의존성 배열에 추가하게 되는데 참조 동일성을 지켜 무한 루프에 빠지지 않도록 한다.
const sendRequest = useCallback(async (requestConfig, applyData) => {
setIsLoading(true);
setError(null);
try {
const response = await fetch(requestConfig.url, {
method: requestConfig.method ? requestConfig.method : 'GET',
headers: requestConfig.headers ? requestConfig.headers : {},
body: requestConfig.body ? JSON.stringify(requestConfig.body) : null,
});
if (!response.ok) {
throw new Error('Request failed!');
}
const data = await response.json();
applyData(data);
} catch (err) {
setError(err.message || 'Something went wrong!');
}
setIsLoading(false);
}, []);
return {
isLoading,
error,
sendRequest,
};
};
export default useHttp;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// src/App.js
import React, { useEffect, useState } from 'react';
import Tasks from './components/Tasks/Tasks';
import NewTask from './components/NewTask/NewTask';
import useHttp from './hooks/use-http';
function App() {
const [tasks, setTasks] = useState([]);
const { isLoading, error, sendRequest: fetchTasks } = useHttp();
useEffect(() => {
const transformTasks = (tasksObj) => {
const loadedTasks = [];
for (const taskKey in tasksObj) {
loadedTasks.push({ id: taskKey, text: tasksObj[taskKey].text });
}
setTasks(loadedTasks);
};
// 훅에서 정의된 함수들은 훅이 사용되는 곳에서 호출할 수 있다. ``
// 즉, 훅을 사용하는 컴포넌트에서 호출할 수 있다.
fetchTasks(
{ url: 'https://react-http-6b4a6.firebaseio.com/tasks.json' },
transformTasks
);
}, [fetchTasks]);
const taskAddHandler = (task) => {
setTasks((prevTasks) => prevTasks.concat(task));
};
return (
<React.Fragment>
<NewTask onAddTask={taskAddHandler} />
<Tasks
items={tasks}
loading={isLoading}
error={error}
onFetch={fetchTasks}
/>
</React.Fragment>
);
}
export default App;
- 컴포넌트가 생성되면서 useHttp함수를 호출되고 useEffect가 실행된다.
- 그 결과, isLoading, error, sendRequest(fetchTask)가 반환된다.
- useEffect가 실행되면서 sendRequest(fetchTask)함수가 실행된다.
- 데이터를 fetch하고 데이터를 state에 저장하는 함수
- 데이터가 state에 저장되면 state가 변경되어 컴포넌트가 리랜더링된다.