JsInterop
JsInterop 是 GWT 2.8 的核心功能之一。顾名思义,JsInterop 是一种在 Java 和 JavaScript 之间进行交互的方法。它提供了一种更好的通信方式,使用注释而不是在类中编写 JavaScript(使用 JSNI)。有关注释的更多详细信息,请参阅 GWT javadoc:https://gwt.java.net.cn/javadoc/latest/jsinterop/annotations/package-summary.html
将 Java 类型导出到 JavaScript
JsInterop 可用于公开 Java 类型,以便从 JavaScript 脚本(也称为非原生类型)外部使用。这可以通过使用 @JsType
注释类型来实现。此注释公开所有公共非静态字段和方法,并告诉 GWT 编译器将类型导出到 JavaScript 类型。使用 @JsType
注释类等同于使用 @JsMethod
注释其所有公共非静态方法,使用 @JsConstructor
注释其构造函数(仅允许存在一个 @JsConstructor
,有关详细信息,请参阅 javadoc),以及使用 @JsProperty
注释其所有公共非静态字段,因此无需显式添加它们。
此外,可以使用以下属性微调 @JsType
- name:自定义类型的名称。默认情况下,保留 Java 类型名称。
- namespace:指定类型的 JavaScript 命名空间。默认情况下,为类型的当前包。要导出顶级类型,可以使用
JsPackage.GLOBAL
常量。
以下示例说明了如何使用 @JsType
导出 Java 类型。
package com.gwt.example;
@JsType
public class MyClass {
public String name;
public MyClass(String name) {
this.name = name;
}
public void sayHello() {
return "Hello" + this.name;
}
}
从 JS 脚本中,该对象可用作 JS 对象
//the package name serves as a JS namespace
var aClass = new com.gwt.example.MyClass('World');
console.log(aClass.sayHello());
// result: 'Hello World'
从外部 JavaScript 脚本导入类型
JsInterop 注释的另一种用法是导入来自外部脚本的类型(也称为原生类型)。要实现这一点,需要将 isNative
属性标记为 true:@JsType(isNative=true)
。名称和命名空间也必须匹配;命名空间 JsPackage.GLOBAL
和名称 "?"
可用于映射没有明确定义类型的 JS API(鸭子类型或私有/隐藏构造函数)。例如,以下代码片段说明了全局 JSON 对象的导入
@JsType(isNative = true, namespace = JsPackage.GLOBAL)
public class JSON {
public static native String stringify(Object obj);
public static native Object parse(String obj);
}
重要注意事项
- 原生类型的构造函数必须为空,并且不能包含任何语句,除了对超类的调用:
super()
(在编译时检查)。 - 原生类型不能具有非原生方法,除非使用
@JsOverlay
注释(在编译时检查)。 @JsType
不是可传递的。如果要将子对象公开给 JavaScript,则也需要对其进行注释。
使用回调参数使用 JavaScript 函数
JsInterop 还可用于使用 @JsFunction
将 JavaScript 函数映射到 Java 接口。与 Java 不同,方法可用作 JavaScript 中其他方法的参数(称为回调参数)。JavaScript 回调可以映射到使用 @JsFunction
注释的 Java 函数式接口(只有一个方法的接口)。以下示例的灵感来自 Elemental 2 源代码
@JsFunction
public interface EventListenerCallback {
void callEvent(Object event);
}
@JsType(isNative = true)
public class Element {
// other methods
public native void addEventListener(String eventType, EventListenerCallback fn);
}
Element element = DomGlobal.document.createElement("button");
// using Java 8 syntax
element.addEventListener("click", (event) -> {
GWT.log("clicked!");
});
等同于以下 JavaScript 代码(或多或少)
var element = document.createElement("button");
element.addEventListener("click", (event) => {
console.log("clicked!");
});
将函数/方法公开给 JS,并使用回调参数
以同样的方式,如果要将 Java 类型作为回调传递,则需要使用 @JsFunction
。回调的实现可以直接从 JavaScript 完成。例如
package com.example;
@JsType
public class Bar {
@JsFunction
public interface Foo {
int exec(int x);
}
public static int action1(Foo foo) {
return foo.exec(40);
}
public static Foo action2() {
return (x) -> x + 2;
}
}
可在 JavaScript 中使用,如下所示
com.example.Bar.action1((x) => x + 2); // will return 42!
var fn = com.example.Bar.action2();
fn(40); // will return 42!
向原生类型添加额外的实用程序方法
JsInterop 契约规定,原生类型只能包含原生方法,除了使用 @JsOverlay
注释的方法。@JsOverlay
允许向原生类型(使用 @JsType(isNative=true)
注释)或 @JsFunction
注释接口的默认方法添加方法。@JsOverlay
契约规定,注释的方法应该是 final 的,并且不应该覆盖任何现有的原生方法。注释的方法将无法从 JavaScript 访问,并且只能从 Java 访问。@JsOverlay
可用于添加原生类型可能不提供的实用程序方法。例如
@JsType(isNative = true)
public class FancyWidget {
public boolean visible;
public native boolean isVisible();
public native void setVisible(boolean visible);
@JsOverlay
public final void toggle() {
visible = !visible;
}
}
示例:从 Java 使用 Leaflet
假设我们想要从我们的 GWT 项目中使用 Leaflet。 Leaflet 是一个用于操作地图的 JS 库。我们所要做的就是使用 JsInterop 注释包装我们需要的方法
@JsType(isNative = true, namespace = JsPackage.GLOBAL)
public class L {
public static native Map map(String id);
}
@JsType(isNative = true, namespace = "L")
public class Map {
public native L setView(double[] center, int zoom);
}
请注意,我们使用了与 Leaflet 库源代码中相同的变量名称。在使用 JsInterop 包装原生类型时,类名(在本例中为 L 和 Map)和方法名非常重要。
现在,我们可以在 GWT 应用程序中初始化 Leaflet 地图,而无需编写任何 JavaScript 代码
public class Leafletwrapper implements EntryPoint {
double[] positions = { 51.505, -0.09 };
public void onModuleLoad() {
// it works
L.map("map").setView(positions, 13);
}
}
完整的示例可在 https://github.com/zak905/jsinterop-leaflet 找到
链接
JsInterop 规范:https://docs.google.com/document/d/10fmlEYIHcyead_4R1S5wKGs1t2I7Fnp_PaNaa7XTEk0/edit# GWT 2.8.0 和 JsInterop:http://www.luigibifulco.it/blog/en/blog/gwt-2-8-0-jsinterop