Wails API接口(译文)

只有 Web 前端没有任何意义,除非您能够与系统交互。Wails通过绑定实现此目,使Go代码可以从前端调用。

有两种类型的代码可以绑定到前端:

  1. 函数
  2. 结构体方法

绑定成功后,它们便可以在前端调用。

绑定函数

绑定函数只需要调用#.Bind方法,其参数即是你想绑定的函数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
  "github.com/wailsapp/wails"
  "fmt"
)

func Greet(name string) string {
  return fmt.Printf("Hello %s!", name)
}

func main() {

  app := wails.CreateApp(&wails.AppConfig{
    Width:  1024,
    Height: 768,
  })
  // 绑定函数
  app.Bind(Greet)
  app.Run()
}

当程序运行时,在全局backend对象下提供名为Greet的 Javascript 函数。函数可以通过.调用。动态生成的函数返回标准promise。因此,对于这个简单示例,您可以这样打印结果:

1
.backend.Greetbackend.Greet("World")backend.Greet("World").then(console.log)

类型转换

标量类型(Scalar type)将自动转换为相关的 Go 类型。而对于对象类型(Object type),Hashicorp的mapstructure包为你提供了将其解析为Go中具体类型的能力。

实例

使用默认 Vue 模板项目,对main.go稍加修改以包括我们的结构和回调功能。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
  type MyData struct {
    A string
    B float64
    C int64
  }

  // 我们期望以下形式的javascript对象:
  // { A: "", B: 0.0, C: 0 }
  func basic(data map[string]interface{}) string {
    var result MyData
    fmt.Printf("data: %#v\n", data)

    err := mapstructure.Decode(data, &result)
    if err != nil {
      // 错误处理
    }
    fmt.Printf("result: %#v\n", result)
    return "Hello World!"
  }

在前端,我们更新HelloWorld.vue组件中的方法getMessage以发送对象。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    getMessage: function() {
      var self = this;
      var mytestStruct = {
        A: "hello",
        B: 1.1,
        C: 99
      }
      window.backend.basic(mytestStruct).then(result => {
        self.message = result;
      });
    }

当程序正常运行时,您将获得以下输出:

1
2
data: map[string]interface {}{"A":"hello", "B":1.1, "C":99}
Result: main.MyData{A:"hello", B:1.1, C:99}

WARNING: 建议业务逻辑和数据结构主要位于应用程序的Go部分中,并使用事件将更新发送到前端。 在两个地方管理状态会导致很多不愉快的事情发生。

结构方法

可以通过与绑定函数类似的方式将struct方法绑定到前端。 这是通过绑定希望在前端使用的结构实例来完成的。 完成此操作后,该结构的所有公共方法将对前端可用。 wails没有尝试甚至不相信将数据绑定到前端是一件好事。 wails认为前端主要是一个视图层,其状态和业务逻辑通常由Go中的后端处理。 这样,您绑定到前端的结构应被视为业务逻辑上的“包装器(wrapper)”或“接口(interface)”。

绑定一个结构很容易:

robot.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import "fmt"

type Robot struct {
 Name string
}

func NewRobot() *Robot {
 result := &Robot{
  Name: "Robbie",
 }
 return result
}

func (t *Robot) Hello(name string) string {
 return fmt.Sprintf("Hello %s! My name is %s", name, t.Name)
}

func (t *Robot) Rename(name string) string {
 t.Name = name
 return fmt.Sprintf("My name is now '%s'", t.Name)
}

func (t *Robot) privateMethod(name string) string {
 t.Name = name
 return fmt.Sprintf("My name is now '%s'", t.Name)
}

main.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
package main

import "github.com/wailsapp/wails"

func main() {
 app := wails.CreateApp(&wails.AppConfig{
  Width:  1024,
  Height: 768,
  Title:  "Binding Structs",
 })

 app.Bind(NewRobot())
 app.Run()
}

如果绑定了Robot结构,则可以在前端使用它。 Robot结构具有两个已导出方法(Hello、Rename),因此可以在此处调用该方法。 需要注意的是,privateMethod方法未导出,因此不会被绑定到前端。

1
2
3
bound.backend.RobotHellobackend.Robot.Hello
bound.backend.RobotHellobackend.Robot.Rename
bound.backend.RobotHellobackend.Robot.privateMethod

这是通过以调试模式运行应用程序并使用检查器来演示其工作方式的示例:

演示图片

结构体初始化

如果您的结构具有特殊的初始化方法,那么Wails将在启动时调用它。 该方法的签名是:

1
WailsInit(runtime *wails.Runtime) error

这使您可以在启动主应用程序之前进行一些初始化。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
 type MyStruct struct {
    runtime *wails.Runtime
  }

  func (s *MyStruct) WailsInit(runtime *wails.Runtime) error {
    // 保存运行时
    s.runtime = runtime

    // 进行其他一些初始化

    return nil
  }

如果返回错误,则应用程序将记录错误并关闭。

传递给它的运行时对象是在运行时与应用程序进行交互的主要手段。 它由许多子系统组成,这些子系统提供对系统不同部分的访问。

构体关闭

如果您的结构具有特殊的关闭方法,那么Wails会在应用程序关闭期间调用它。 该方法的签名是:

1
WailsShutdown()

这使您可以在终止主应用程序时清理所有资源。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
  type MyStruct struct {
    runtime *wails.Runtime
  }

  func (s *MyStruct) WailsInit(runtime *wails.Runtime) error {
    // 保存运行时
    s.runtime = runtime

    // 分配一些资源

    return nil
  }


  func (s *MyStruct) WailsShutdown() {
    
    // 释放一些资源...

}

绑定规则

只要遵循以下规则,任何Go函数(或方法)都可以绑定:

  • 必须返回0-2个结果。
  • 如果有2个返回参数,则最后一个必须是错误类型。
  • 如果返回结构或结构指针,则您希望在前端访问的字段必须定义Go的标准json结构标签。

如果仅返回一个值,则取决于是否为错误类型,该值将在Promise的resolve或reject部分中可用。

实例 1

1
2
3
  func (m *MyStruct) MyBoundMethod(name string) string {
    return fmt.Sprintf("Hello %s!", name)
  }

在Javascript中,正确的调用将返回一个Promise,该Promise将使用字符串进行解析。

1
MyStruct.MyBoundMethod

实例 2

1
2
3
4
5
6
7
8
9
  ...
  func (m *MyStruct) AddUser(name string) error {
    if m.userExists(name) {
      return fmt.Errorf("user '%s' already exists");
    }
    m.saveUser(name)
    return nil
  }
  ...

在Javascript中,调用MyStruct.AddUser并传递一个不存在的用户名,将返回一个Promise,该Promise没有值。使用已存在的用户名调用MyStruct.AddUser时将返回一个Promise,该Promise将被拒绝(reject),错误设置为用户user ‘$name’ already exists(用户’$name’已经存在)。

1
.MyStruct.MyBoundMethodMyStruct.MyBoundMethoduser'$ name'

建议最好返回两个值,一个结果和一个错误,因为这直接映射到Javascript Promise。 如果不返回任何东西的话,或许事件可能是更好的选择。

重要细节

要考虑的一个非常重要的细节是,对绑定的Go代码的所有调用都在其自己的goroutine中运行。 在编写任何绑定函数时都应牢记这一点。 这样做的原因是确保绑定的代码不会阻止应用程序中的主事件循环,从而导致UI冻结。

Licensed under CC BY-NC-SA 4.0