네트워킹
많은 모바일 앱은 원격 URL에서 리소스를 불러와야 한다. REST API에 POST 요청을 보내야 할 수도 있고, 다른 서버에서 정적 콘텐츠를 가져와야 할 수도 있다.
Fetch API 사용하기
React Native는 네트워크 요청을 위해 Fetch API를 제공한다. Fetch API는 XMLHttpRequest
나 다른 네트워크 API를 사용해 본 경험이 있다면 익숙하게 느껴질 것이다. 추가 정보가 필요하다면 MDN의 Fetch API 사용 가이드를 참고할 수 있다.
요청 보내기
임의의 URL에서 콘텐츠를 가져오기 위해 URL을 fetch에 전달한다:
fetch('https://mywebsite.com/mydata.json');
fetch는 HTTP 요청을 커스터마이징할 수 있는 두 번째 인자를 선택적으로 받는다. 추가 헤더를 지정하거나 POST 요청을 보내는 경우에 유용하다:
fetch('https://mywebsite.com/endpoint/', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
firstParam: 'yourValue',
secondParam: 'yourOtherValue',
}),
});
모든 속성 목록은 Fetch Request 문서를 참고한다.
위 예제들은 요청을 어떻게 보내는지 보여준다. 대부분의 경우, 응답을 어떻게 처리할지 고민하게 된다.
네트워크 작업은 본질적으로 비동기적으로 동작한다. fetch
메서드는 비동기 코드를 쉽게 작성할 수 있도록 Promise를 반환한다:
const getMoviesFromApi = () => {
return fetch('https://reactnative.dev/movies.json')
.then(response => response.json())
.then(json => {
return json.movies;
})
.catch(error => {
console.error(error);
});
};
React Native 앱에서 async
/ await
문법을 사용할 수도 있다:
const getMoviesFromApiAsync = async () => {
try {
const response = await fetch(
'https://reactnative.dev/movies.json',
);
const json = await response.json();
return json.movies;
} catch (error) {
console.error(error);
}
};
fetch
에서 발생할 수 있는 오류를 잡는 것을 잊지 말아야 한다. 그렇지 않으면 오류가 조용히 무시될 수 있다.
- TypeScript
- JavaScript
기본적으로 iOS 9.0 이상은 App Transport Security(ATS)를 적용한다. ATS는 모든 HTTP 연결이 HTTPS를 사용하도록 요구한다.
http
로 시작하는 URL에서 데이터를 가져와야 한다면, 먼저 ATS 예외를 추가해야 한다. 미리 접근해야 할 도메인을 알고 있다면, 해당 도메인에만 예외를 추가하는 것이 더 안전하다. 도메인을 런타임에 알게 된다면 ATS를 완전히 비활성화할 수 있다. 하지만 2017년 1월부터 Apple의 App Store 심사는 ATS 비활성화에 대한 합리적인 근거를 요구한다. 자세한 내용은 Apple의 문서를 참고한다.
Android에서는 API Level 28부터 기본적으로 클리어 텍스트 트래픽이 차단된다. 이 동작은 앱 매니페스트 파일에서
android:usesCleartextTraffic
를 설정하여 재정의할 수 있다.
다른 네트워킹 라이브러리 사용하기
React Native는 XMLHttpRequest API를 기본적으로 지원한다. 따라서 frisbee나 axios와 같은 XMLHttpRequest에 의존하는 서드파티 라이브러리를 사용할 수 있다. 필요하다면 직접 XMLHttpRequest API를 사용할 수도 있다.
const request = new XMLHttpRequest();
request.onreadystatechange = e => {
if (request.readyState !== 4) {
return;
}
if (request.status === 200) {
console.log('success', request.responseText);
} else {
console.warn('error');
}
};
request.open('GET', 'https://mywebsite.com/endpoint/');
request.send();
XMLHttpRequest의 보안 모델은 웹과 다르다. 네이티브 앱에서는 CORS 개념이 존재하지 않는다.
WebSocket 지원
React Native는 WebSockets도 지원한다. WebSocket은 단일 TCP 연결을 통해 양방향 통신 채널을 제공하는 프로토콜이다.
const ws = new WebSocket('ws://host.com/path');
ws.onopen = () => {
// 연결이 열림
ws.send('something'); // 메시지 전송
};
ws.onmessage = e => {
// 메시지 수신
console.log(e.data);
};
ws.onerror = e => {
// 오류 발생
console.log(e.message);
};
ws.onclose = e => {
// 연결 종료
console.log(e.code, e.reason);
};
fetch
와 쿠키 기반 인증의 알려진 문제점
현재 fetch
에서 다음 옵션들이 정상적으로 작동하지 않는다:
redirect:manual
credentials:omit
- 안드로이드에서 동일한 이름의 헤더를 사용할 경우, 마지막 헤더만 남는 문제가 있다. 이에 대한 임시 해결책은 여기에서 확인할 수 있다.
- 쿠키 기반 인증은 현재 불안정하다. 관련 이슈는 이 링크에서 확인할 수 있다.
- iOS에서 최소한의 문제로,
302
리다이렉트를 통해 이동할 때Set-Cookie
헤더가 있더라도 쿠키가 제대로 설정되지 않는다. 리다이렉트를 수동으로 처리할 수 없기 때문에, 세션이 만료된 경우 무한 요청이 발생할 수 있다.
iOS에서 NSURLSession 설정하기
일부 애플리케이션에서는 iOS에서 실행 중인 React Native 앱의 네트워크 요청에 사용되는 NSURLSession
에 커스텀 NSURLSessionConfiguration
을 제공하는 것이 적절할 수 있다. 예를 들어, 앱에서 발생하는 모든 네트워크 요청에 대해 커스텀 사용자 에이전트 문자열을 설정하거나, NSURLSession
에 임시 NSURLSessionConfiguration
을 제공해야 할 수 있다. 이때 RCTSetCustomNSURLSessionConfigurationProvider
함수를 사용하면 이러한 커스터마이징이 가능하다. RCTSetCustomNSURLSessionConfigurationProvider
를 호출할 파일에 다음 import 문을 추가해야 한다:
#import <React/RCTHTTPRequestHandler.h>
RCTSetCustomNSURLSessionConfigurationProvider
는 애플리케이션 생명주기 초기에 호출되어야 한다. 이렇게 하면 React가 필요로 할 때 즉시 사용할 수 있다. 예를 들어 다음과 같이 설정할 수 있다:
-(void)application:(__unused UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// RCTSetCustomNSURLSessionConfigurationProvider 설정
RCTSetCustomNSURLSessionConfigurationProvider(^NSURLSessionConfiguration *{
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
// 세션 설정
return configuration;
});
// React 설정
_bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
}