일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- virtual Dom
- deep
- 프로그래머스
- 상태
- 원리
- Hook
- 가상 DOM
- API
- 백준
- 유용한 사이트
- Dive
- 상태 끌어올리기
- BOJ
- memory
- State
- 프로젝트
- 꿀팁
- react
- axios
- 개발
- EventListener
- programmers
- javascript
- Java
- 요청
- DoM
- LeetCode
- Today
- Total
탄탄한 기본기!
[React] axios 불필요한 (반복) 요청 취소하기 본문
Axios 요청 횟수를 줄이자
React로 비동기 통신을 기능을 구현할 때 불필요한 요청을 줄이는 것은 서버 측과 클라이언트 측 모두 성능 개선에 큰 도움이 된다.
예를 들어서, 유저가 텍스트 타입의 Input box에 입력한 단어로 어떠한 결과를 입력할 때마다 바로바로 검색하는 API에 GET 요청을 한다고 가정하자. 그럼 Input tag의 onChange props로 매번 타이핑을 할 때마다 API요청을 보낼 수 있을 것이다.
하지만 Apple pie라는 단어를 검색할 때
- A
- Ap
- App
- Appl
- ...
- Apple pi
- Apple pie
모든 경우에 대해서 모두 API 요청을 날릴 것이다. 음... 일단 기능은 되니까 됐다(?)라는 생각이 든다면 몰라도 나의 성격 상 절대 그럴 수 없다.
여러 가지 방법이 존재할 것이다.
- 입력을 마친 후 엔터 등을 입력해 검색을 하도록 하기
(하지만 입력 시마다 동적으로 검색 결과를 보여주는 요구사항과 거리가 멀다) - 디 바운스(Debounce)를 활용해서 연속적으로 타이핑한 결과들을 그룹화하여 그룹당 한 번씩만 요청 보내기
하지만 이번에 Infinite Scroll에 대해 공부하며 다른 사람의 github 코드를 보는데, axios에서 cancelToken이라는 메서드를 제공해주는 것을 알게 되어 공부했다.
axios.cancelToken()
Cancellation | Axios Docs
Cancellation You can cancel a request using a cancel token. The axios cancel token API is based on the withdrawn cancelable promises proposal. You can create a cancel token using the CancelToken.source factory as shown below: const CancelToken = axios.Canc
axios-http.com
axios는 공식적으로 요청을 취소할 수 있는 방법을 제시하고 있는데, CancelToken의 source 프로퍼티를 활용해 source를 하나 만든 후, 요청을 보낼 때 option으로 cancelToken을 주면 된다.
// 공식 문서 예시 (https://axios-http.com/docs/cancellation)
const CancelToken = axios.CancelToken;
const source = CancelToken.source(); // source 생성
axios.get('/user/12345', {
cancelToken: source.token // option으로 cancelToken에 source.token을 줌
}).catch(function (thrown) {
if (axios.isCancel(thrown)) {
console.log('Request canceled', thrown.message);
} else {
// handle error
}
});
// 메세지 파라미터는 줘도되고 안줘도 된다 (the message parameter is optional)
source.cancel('Operation canceled by the user.'); // 요청을 취소할 때 source의 cancel 메서드를 호출
혹은, source를 만들고 하는 과정을 줄여 아래 예시에서 처럼 executor function을 cancelToken에 직접 줄 수 있다.
const CancelToken = axios.CancelToken;
let cancel;
axios.get('/user/12345', {
cancelToken: new CancelToken(function executor(c) {
cancel = c; // executor가 매개변수로 cancel 메서드를 받고, 그것을 cancel 변수에 저장
})
});
cancel(); // cancel 호출로 요청 취소
예시
위의 상황은 Next Comments 버튼을 눌러 랜덤으로 여러 가지 Comments를 단 사람의 이메일을 받아오는 api를 요청한다. 그런데 빠르게 클릭했을 때, 밀렸던 요청들이 취소되지 않고 하나하나 차례대로 요청을 받아 위와 같이 결과가 뒷북(?)을 치고 있는 셈이다.
아래는 CommentList라는 간단한 컴포넌트이고, 해당 컴포넌트가 api를 요청에 뿌려주고 있다.
const COUNT = 10;
export default function CommentList() {
const [loading, setLoading] = useState(false);
const [comments, setComments] = useState(null);
const clickHandler = () => {
setLoading(true); // 로딩 시작
(async () => {
const commentList = await getRandomComments(COUNT);
setComments(commentList);
setLoading(false); // 로딩 끝!
})();
};
return (
<div>
<h1>Get Emails</h1>
<button onClick={clickHandler}>Get Random Emails</button>
{loading ? (
<h2>로딩중 ...</h2>
) : (
<ul>
{comments &&
comments.map((comment) => (
<li key={comment.id}>{comment.email}</li>
))}
</ul>
)}
</div>
);
}
그리고 아래는 getRandomComments이다. 그리고 중복된 값을 허용하지 않기 위해서 중간에 rand와 Set으로 중복을 제거해주는 작업을 대략적으로 구현했다.
import axios from 'axios';
const URL = 'https://jsonplaceholder.typicode.com/comments';
export const getRandomComments = async (count) => {
const { data } = await axios.get(URL);
const res = [];
const usedRand = new Set();
while (res.length < count) {
const rand = Math.floor(Math.random() * 100);
if (usedRand.has(rand)) {
continue;
}
res.push(data[rand]);
usedRand.add(rand);
}
return res;
};
이때, 위에서 알게 된 axios의 cancelToken을 사용해 빠르게 버튼을 눌렀을 때(빠르게 요청을 연속적으로 보냈을 때), 이전 요청을 취소하고 마지막 요청만을 보내고 싶다면 아래처럼 코드를 바꿀 수 있다.
// 컴포넌트
const COUNT = 10;
export default function CommentList() {
const [loading, setLoading] = useState(false);
const [comments, setComments] = useState(null);
const clickHandler = () => {
setLoading(true);
(async () => {
const commentList = await getRandomComments(COUNT);
setComments(commentList);
setLoading(!commentList);
})();
};
return (
<div>
<h1>Get Emails</h1>
<button onClick={clickHandler}>Get Random Emails</button>
{loading ? (
<h2>로딩중 ...</h2>
) : (
<ul>
{comments &&
comments.map((comment) => (
<li key={comment.id}>{comment.email}</li>
))}
</ul>
)}
</div>
);
}
// getRandomComments
import axios from 'axios';
const URL = 'https://jsonplaceholder.typicode.com/comments';
let cancel = null; // 추가!
export const getRandomComments = async (count) => {
const res = [];
const usedRand = new Set();
cancel && cancel(); // 추가!
try {
const { data } = await axios.get(URL, {
cancelToken: new axios.CancelToken((c) => {
cancel = c; // 추가!
}),
});
while (res.length < count) {
const rand = Math.floor(Math.random() * 100);
if (usedRand.has(rand)) {
continue;
}
res.push(data[rand]);
usedRand.add(rand);
}
} catch (e) {
if (axios.isCancel(e)) { // 추가!
return null;
}
}
return res;
};
getRandomComments에서 axios를 호출할 때, cancel 변수가 있다면 이전에 한 번 호출한 적이 있다는 뜻이므로 cancel을 호출해 이전 요청을 취소해주는 방식으로 코드를 작성했다.
이렇게 간단한 코드를 작성해주게 되면 더 이상 밀린 요청을 처리하지 않고 무시해 마지막 요청만 제대로 하게 된다.
아래는 그 결과이다!
이때까지 중복된 요청을 그룹화할 때는 무조건 Debounce밖에 생각하지 않았는데, 우연치 않은 기회로 남의 코드를 보다가 새로운 지식을 하나 얻게 되어 너무 행복하고 뿌듯하다.
항상 기존의 방식대로, 알고 있던 대로만 코딩을 하다 보면 실력이 늘지 않고 지식의 한계가 올 것이라고 생각했었다. 특히 프론트엔드는 생태계 자체가 비교적 빠르고 급격하게 바뀌기 때문에 더더욱 그렇게 생각했다.
앞으로도 문제 상황들을 마주쳤을 때, 여러 가지 방법들 중 그중에 알맞은 방법을 선택해 적용하기 위해서 새롭고 다양한 기술들, 방법들을 익히는 것에 더욱 열려있어야겠다!