Flutter JavaScript交互插件js的使用

发布于 1周前 作者 vueper 来自 Flutter

Flutter JavaScript交互插件js的使用

简介

pub package package publisher

重要提示: 推荐使用 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

1 回复

更多关于Flutter JavaScript交互插件js的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


在处理Flutter与JavaScript的交互时,可以使用flutter_webview_pluginwebview_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进行交互。根据实际需求,你可以进一步扩展和定制这个示例。

回到顶部