JavaScript interop 시작하기
JavaScript interop 시작하기
브라우저 API와 번들된 JS 라이브러리를 사용하는 기본 예제예요.
본문
이 튜토리얼은 다양한 브라우저 및 JavaScript API를 사용해서 Dart 코드에서 JavaScript와 상호 작용하는 기본을 가르쳐 줘요.
JavaScript 객체에 접근하고 정의하기
브라우저의 document 객체 같은 전역 JavaScript 객체에 접근하려면, @JS 어노테이션이 붙은 external 최상위 getter를 선언해요. 이것은 값을 불투명한 JSObject로 반환해요.
import 'dart:js_interop';
// 모든 최상위 JS interop API는 @JS 어노테이션이 필요해요.
@JS()
external JSObject get document;
JSObject는 불투명해서 타입 안전성이나 자동 완성을 제공하지 않아요. 타입 안전한 멤버를 추가하려면 extension type을 사용해서 interop 타입을 정의해요. 이것은 인터페이스 역할을 하며 더 많은 interop API를 external 멤버로 선언할 수 있게 해 줘요. extension type을 사용해 interop 타입을 다르게 보이도록 정의할 수 있어요:
@JS()
external Document get document;
extension type Document._(JSObject _) implements JSObject {}
이제 Document 인터페이스에 external 메서드를 추가할 수 있어요. 예를 들어 createElement() 인스턴스 메서드를 추가해 보죠:
extension type Document._(JSObject _) implements JSObject {
external JSObject createElement(JSString tag);
}
createElement 메서드가 정의됐으니 document 객체에서 호출할 수 있어요. 'button'을 String에서 JSString으로 .toJS extension 메서드를 사용해 변환해야 한다는 점에 주목하세요.
var button = document.createElement('button'.toJS);
interop API로 주고받는 값은 interop 타입(JSObject나 JSString 같은)이거나 허용된 Dart 기본 타입이어야 해요.
자동 타입 변환 사용하기
컴파일러는 대부분의 Dart 기본 타입(String, num, bool, null 같은)을 자동으로 변환해 줘서, 코드를 단순화하기 위해 interop 시그니처에서 그것들을 직접 사용하는 경우가 많아요.
예를 들어 이전 섹션의 createElement 선언을 Dart String을 직접 받도록 다시 작성할 수 있어요:
external JSObject createElement(String tag);
이제 명시적인 .toJS 변환 없이 호출할 수 있어요:
var button = document.createElement('button');
새로 만든 버튼을 문서의 body에 추가하려면 먼저 body와 그 appendChild() 메서드에 대한 interop 타입을 정의해요:
extension type Document._(JSObject _) implements JSObject {
external JSObject createElement(String tag);
external Body get body;
}
extension type Body._(JSObject _) implements JSObject {
external JSObject appendChild(JSObject child);
}
이 정의들을 사용하면 버튼을 만들어 페이지에 추가할 수 있어요:
var button = document.createElement('button');
document.body.appendChild(button);
이벤트와 콜백 처리하기
버튼 클릭 같은 사용자 상호 작용을 처리하려면 addEventListener()를 사용해서 이벤트 리스너를 등록할 수 있어요.
먼저 버튼 요소에 대한 인터페이스를 만들어요. 그다음 이벤트 이름과 콜백 함수와 함께 addEventListener를 호출해요.
extension type ButtonElement(JSObject _) implements JSObject {
external void addEventListener(String event, JSFunction listener);
}
var button = ButtonElement(document.createElement('button'));
document.body.appendChild(button);
button.addEventListener('click', (JSObject event) {
print('Clicked!');
}.toJS);
.toJS로 JS로 변환된 콜백도 다른 interop API와 동일한 타입 제한을 가져요. 즉 파라미터와 반환 값이 interop 타입이거나 호환 가능한 기본 타입이어야 해요.
Promise와 Array 다루기
JavaScript interop은 다른 흔한 타입들에 대한 헬퍼를 제공해요. JavaScript Promise를 Dart Future로/로부터 변환하거나, Array를 List로/로부터 변환하는 것 같은 것들이요.
Promise와 Future
이 예제는 Promise를 반환하는 fetch API를 사용해요. .toDart extension은 Promise를 Future로 변환해서 Dart에서 그 결과를 await할 수 있게 해 줘요:
import 'dart:js_interop';
extension type Response._(JSObject _) implements JSObject {
external bool get ok;
}
@JS()
external Response fetch(String resource);
void main() async {
var response = await fetch('image.png').toDart;
print(response.ok);
}
Array와 List
이 예제는 정적 JavaScript Array.of 메서드를 호출해서 JSArray를 만들고, 그 배열을 Dart List로 변환한 다음 순회하면서 각 요소를 출력해요.
import 'dart:js_interop';
@JS('Array.of')
external JSArray arrayOf(String a, String b);
void main() {
var array = arrayOf('hello', 'world');
var list = array.toDart;
for (var element in list) {
print(element.toDart);
}
}
List 같은 제네릭 타입을 변환할 때는 그 요소들이 이미 JS interop 타입이어야 해요. 예를 들어 List<String>을 변환하려면 먼저 각 String을 JSString으로 변환해야 해요.
// 방법 1: 처음부터 JS 타입으로 리스트를 만들어요.
List list = ['hello'.toJS, 'world'.toJS];
JSArray jsArray1 = list.toJS;
// 방법 2: Dart 리스트를 JS 타입 리스트로 매핑해요.
List dartList = ['hello', 'world'];
JSArray jsArray2 = dartList.map((e) => e.toJS).toList().toJS;
더 알아보기
- 타입 변환에 대한 자세한 내용은 Conversions를 확인해요.
- interop API를 작성하는 방법에 대한 자세한 내용은 Usage 가이드를 참고해요.
- 흔한 유틸리티 함수에 접근하려면 다음을 참고해요:
dart:js_interop라이브러리, 그리고dart:js_interop_unsafe라이브러리.
package:web는 (위 예제에서 사용한 것들을 포함해) 많은 브라우저 API를 interop 선언으로 노출해요.