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