Flutter JavaScript交互插件js的使用
Flutter JavaScript交互插件js的使用
简介
重要提示: 推荐使用 dart:js_interop
来替代此包进行JS互操作。详情请参阅Dart JS互操作文档。
package:js
主要用于在Dart代码中调用JavaScript API,或反之亦然。该包提供了注解和函数,使您能够指定Dart代码如何与JavaScript代码互操作。Dart到JavaScript的编译器(如dartdevc和dart2js)会识别这些注解,并使用它们来连接您的Dart代码与JavaScript。
此外,js_util
库提供了低级别的工具,当无法用静态、带注解的API包装JavaScript时可以使用。
静态互操作 (Static Interop)
自 Dart 3.3 版本开始,推荐使用带有扩展类型的 dart:js_interop
进行静态互操作,而不是使用 [@staticInterop](/user/staticInterop)
注解。有关更多详细信息,请参阅 JS interop documentation。
使用示例
调用JavaScript函数
@JS()
library stringify;
import 'package:js/js.dart';
// 调用 JavaScript 的 `JSON.stringify(obj)` 方法
@JS('JSON.stringify')
external String stringify(Object obj);
使用JavaScript命名空间和类
@JS('google.maps')
library maps;
import 'package:js/js.dart';
// 调用 JavaScript 的 `google.maps.map` getter
external Map get map;
// 构造函数调用 JavaScript 的 `new google.maps.Map(location)`
@JS()
class Map {
external Map(Location location);
external Location getLocation();
}
// 构造函数调用 JavaScript 的 `new google.maps.LatLng(...)`
@JS('LatLng')
class Location {
external Location(num lat, num lng);
}
向JavaScript传递对象字面量
@JS()
library print_options;
import 'package:js/js.dart';
void main() {
printOptions(Options(responsive: true));
}
@JS()
external printOptions(Options options);
@JS()
@anonymous
class Options {
external bool get responsive;
// 必须有一个无名工厂构造函数,且具有命名参数。
external factory Options({bool responsive});
}
让Dart函数可被JavaScript调用
@JS()
library callable_function;
import 'package:js/js.dart';
/// 允许将函数赋值给 `window.functionName()`
@JS('functionName')
external set _functionName(void Function() f);
/// 允许从Dart调用分配的函数。
@JS()
external void functionName();
void _someDartFunction() {
print('Hello from Dart!');
}
void main() {
_functionName = allowInterop(_someDartFunction);
// JavaScript代码现在可以调用 `functionName()` 或 `window.functionName()`.
}
使用 @staticInterop
@JS()
library static_interop;
import 'package:js/js.dart';
// 假设有一个顶层的 `StaticInterop` 类存在于JS模块中。
@JS()
[@staticInterop](/user/staticInterop)
class StaticInterop {
external factory StaticInterop();
}
extension on StaticInterop {
external int field;
external int get getSet;
external set getSet(int val);
external int method();
}
void main() {
var jsObj = StaticInterop();
jsObj.field = 1;
jsObj.method();
}
使用 @JSExport 和 js_util.createDartExport
import 'dart:js_util';
import 'package:js/js.dart';
[@JSExport](/user/JSExport)()
class Counter {
int value = 0;
[@JSExport](/user/JSExport)('increment')
void renamedIncrement() {
value++;
}
}
@JS()
[@staticInterop](/user/staticInterop)
class JSCounter {}
extension on JSCounter {
external int value;
external void increment();
}
void main() {
var dartCounter = Counter();
var counter = createDartExport<Counter>(dartCounter) as JSCounter;
expect(counter.value, 0);
counter.increment();
expect(counter.value, 1);
expect(dartCounter.value, 1); // Dart对象被修改了
dartCounter.value = 0;
expect(counter.value, 0); // Dart对象中的更改影响导出的对象
}
使用 js_util.createStaticInteropMock
@JS()
[@staticInterop](/user/staticInterop)
class StaticInterop {}
extension A on StaticInterop {
external Function member;
}
extension B on StaticInterop {
external void member();
}
void main() {
// 创建一个mock对象
var mock = createStaticInteropMock<StaticInterop, MockClass>(MockClass());
}
报告问题
请在 SDK issue tracker 上提交bug和功能请求。
已知限制和错误
Dart2js 和 Dartdevc 之间的差异
Dart 的生产环境和开发环境的JavaScript编译器使用不同的调用约定和类型表示,因此在JavaScript互操作方面有不同的挑战。目前存在一些已知的行为差异和编译器错误。
Dartdevc 和 dart2js 对 Map 的不同表示
传递 Map<String, String>
作为参数给JavaScript函数,在不同编译器下会有不同的行为。例如调用 JSON.stringify()
会得到不同的结果。
解决方法: 只传递对象字面量而不是 Map 作为参数。对于json格式的数据,使用Dart的 jsonEncode
而不是JS的替代方案。
Dartdevc 缺少对匿名工厂构造函数的验证
使用 @anonymous
类创建JavaScript对象字面量时,dart2js会强制使用命名参数,而dartdevc允许位置参数,但可能会生成错误代码。
解决方法: 尝试在开发和发布模式下构建以获取完整的静态验证范围。
常见问题
由于Dart和JavaScript有不同的语义和常见模式,这使得容易犯一些错误并且难以让工具提供安全保证。这些问题也被称为“尖锐边缘”。
缺乏运行时类型检查
标注有 @JS()
的方法返回类型在运行时不会被验证,因此不正确的类型可能会泄露到其他Dart代码中并违反类型系统保证。这不是 [@staticInterop](/user/staticInterop)
类的情况,除非使用了 @trustTypes
注解。
解决方法: 对于任何调用JavaScript代码的调用,如果其返回值未知是否安全,则手动使用 is
检查验证结果。
来自JavaScript的列表实例总是 List<dynamic>
JavaScript数组没有具体化的元素类型,因此来自JavaScript函数的数组不能对其元素做出任何保证,除非检查每个元素。在运行时,result is List
可能成功,但 result is List<String>
总是失败。
解决方法: 使用 .cast()
或构造一个新的 List
来获取具有预期具体化类型的实例。例如,如果您想要 List<String>
,则使用 .cast<String>()
或 List<String>.from
。
JsObject
类型不能与 @JS()
注释一起使用
JsObject
和相关代码使用不同的方法,可能无法作为参数传递给标注有 @JS()
的方法。
解决方法: 避免导入 dart:js
并仅使用 package:js
提供的方法。为了处理对象字面量,可以在 @JS()
注解的类上使用 @anonymous
。
is
检查和 as
强制转换在JS互操作类型之间总是成功
对于任何两个 @JS()
类型,无论是否带有 @anonymous
,检查一个类型是否为另一个类型总将返回true,即使这两个类型不在同一个原型链上。同样,显式使用 as
的强制转换也会成功。
希望以上内容可以帮助你更好地理解和使用Flutter中的JavaScript交互插件 package:js
。如果你有任何疑问或需要进一步的帮助,请随时提问!
更多关于Flutter JavaScript交互插件js的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter JavaScript交互插件js的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
在处理Flutter与JavaScript的交互时,可以使用flutter_webview_plugin
或webview_flutter
等插件来实现。下面是一个使用webview_flutter
插件的示例代码,展示了如何在Flutter应用中嵌入WebView并与其中的JavaScript进行交互。
首先,确保在pubspec.yaml
文件中添加webview_flutter
依赖:
dependencies:
flutter:
sdk: flutter
webview_flutter: ^3.0.4 # 请检查最新版本号
然后,运行flutter pub get
来安装依赖。
接下来,创建一个Flutter应用,并在其中使用WebView
组件。以下是一个完整的示例代码:
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter WebView Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: WebViewExample(),
);
}
}
class WebViewExample extends StatefulWidget {
@override
_WebViewExampleState createState() => _WebViewExampleState();
}
class _WebViewExampleState extends State<WebViewExample> {
late WebViewController _controller;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter WebView with JavaScript'),
),
body: WebView(
initialUrl: 'about:blank',
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webViewController) {
_controller = webViewController;
_loadHtmlFromAssets();
},
onPageFinished: (String url) async {
// 在页面加载完成后,可以调用JavaScript函数
_controller.evaluateJavascript('alert("Page loaded!");');
},
navigationDelegate: (NavigationRequest request) {
if (request.url.startsWith('http')) {
// 可以在这里处理外部链接的跳转,比如使用_controller.loadUrl来加载新页面
return NavigationDecision.prevent;
}
return NavigationDecision.navigate;
},
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
// 调用JavaScript函数并处理返回值
String result = await _controller.evaluateJavascript('yourJavaScriptFunction()');
print('JavaScript function result: $result');
},
tooltip: 'Call JS Function',
child: Icon(Icons.add),
),
);
}
_loadHtmlFromAssets() async {
// 从assets加载HTML内容
String htmlContent = await rootBundle.loadString('assets/index.html');
_controller.loadUrl(Uri.dataFromString(
htmlContent,
mimeType: 'text/html',
encoding: Encoding.getByName('utf-8')
).toString());
}
}
在这个示例中,我们创建了一个简单的Flutter应用,其中包含一个WebView
组件。我们在WebView
加载完成后,使用evaluateJavascript
方法调用了一个简单的JavaScript alert
函数。此外,我们还展示了如何从assets中加载HTML内容,并在FloatingActionButton的点击事件中调用了一个名为yourJavaScriptFunction
的JavaScript函数,并打印其返回值。
确保在assets
文件夹中有一个名为index.html
的文件,内容可以像这样:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebView Example</title>
<script>
function yourJavaScriptFunction() {
return 'Hello from JavaScript!';
}
</script>
</head>
<body>
<h1>Hello, WebView!</h1>
</body>
</html>
这个HTML文件定义了一个简单的网页,并包含一个名为yourJavaScriptFunction
的JavaScript函数,该函数返回一个字符串。
这个示例展示了如何在Flutter应用中使用webview_flutter
插件与嵌入的WebView中的JavaScript进行交互。根据实际需求,你可以进一步扩展和定制这个示例。