wails/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/howdoesitwork.mdx
2021-09-27 20:05:25 +10:00

282 lines
10 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: 它如何工作
sidebar_position: 3
---
# 它如何工作?
Wails 应用程序是一个标准的 Go 应用程序,带有一个 webkit 前端。Wails 应用程序的架构是:
<div className="text--center">
<img src="/img/architecture.svg" width="75%" />
</div>
## 后端
### 概述
主应用程序,通常称为“后端”,由对`wails.Run()`的调用组成. 它接收应用程序配置,该配置说明应用程序应该有多大尺寸,标题栏应该显示什么,使用什么资源等。一个基本的应用程序可能看起来像这样:
```go title="main.go"
package main
import (
"embed"
"log"
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
)
//go:embed frontend/dist
var assets embed.FS
func main() {
app := &App{}
err := wails.Run(&options.App{
Title: "Basic Demo",
Width: 1024,
Height: 768,
Assets: &assets,
OnStartup: app.startup,
OnShutdown: app.shutdown,
Bind: []interface{}{
app,
},
})
if err != nil {
log.Fatal(err)
}
}
type App struct {
ctx context.Context
}
func (b *App) startup(ctx context.Context) {
b.ctx = ctx
}
func (b *App) shutdown(ctx context.Context) {}
func (b *App) Greet(name string) string {
return fmt.Sprintf("Hello %s!", name)
}
```
### 选项概要
此示例设置了以下选项:
- `Title` - 应该出现在窗口标题栏中的文本
- `Width&Height`- 窗口的尺寸
- `Assets` - 应用程序的前端资源
- `OnStartup` - 创建窗口并即将开始加载前端资源时的回调
- `OnShutdown` - 应用程序即将退出时的回调
- `Bind` - 我们希望向前端暴露的一部分结构体实例
完整的应用程序参数选项列表可以在[参数选项](/docs/reference/options)中找到。
#### 资源
`Assets` 选项是必须的,因为你不能拥有没有前端资源的 Wails 应用程序。这些资源可以是你希望在 Web 应用程序中找到的任何文件 - html、js、css、svg、png 等。
不需要生成资源包- 纯文件即可。当应用程序启动时,它将尝试从你的资源中加载`index.html`,并且那时起前端基本上将作为浏览器工作。值得注意的是 embed.FS
文件所在的位置没有要求。嵌入路径很可能使用了相对于你的主应用程序代码的嵌套目录,例如 `frontend/dist`
```go title="main.go"
// go:embed frontend/dist
var assets embed.FS
```
启动时Wails 将遍历嵌入的文件,寻找包含的`index.html`. 所有其他资源将相对于该目录加载。
由于生产二进制文件使用中包含在`embed.FS`的文件,因此应用程序不需要附带任何外部文件。
在`dev`模式下,资源从磁盘加载,任何更改都会导致“实时重新加载”。资源的位置需要传递给`dev`命令,并且很可能与嵌入路径相同。更多细节可以在[应用程序开发指南](/docs/guides/application-development)中找到。
#### 应用程序生命周期回调
在即将加载前端`index.html`之前,对 [应用启动回调](/docs/reference/options#应用启动回调) 中提供的函数进行回调。一个标准的 Go 上下文被传递给这个方法。
调用运行时需要此上下文,因此标准模式是保存此时对它的引用。在应用程序关闭之前,以同样的方式调用 [应用退出回调](/docs/reference/options#应用退出回调) 回调,
再次使用上下文。当前端加载所有资源时,还有一个 [前端 Dom 加载完成回调](/docs/reference/options#前端-dom-加载完成回调) 回调。
#### 方法绑定
`Bind`选项是 Wails 应用程序中最重要的参数选项之一。它指定向前端暴露哪些结构方法。当应用程序启动时,它会检查 `Bind` 中列出的结构实例,确定哪些方法是公开的(以大写字母开头),并将生成前端可以调用的那些方法的 Javascript 版本。
这些方法位于前端 `window.go.<packagename>.<struct>.<method>`。在上面的例子中,我们绑定 `app`,它有一个公开方法 `Greet`。这可以通过在 `Javascript` 中用 `window.go.main.App.Greet`调用。这些方法返回一个 Promise。成功的调用将导致 Go 调用的第一个返回值被传递给 resolve 处理程序。一个不成功的调用是将一个 Go 方法的第二个错误类型返回值传递回调用者。这是通过`reject`传回的。在上面的例子中Greet 只返回一个字符串,所以 `Javascript` 调用永远不会`reject` - 除非将无效数据传递给它。
所有数据类型都在 Go 和 Javascript 之间正确转换。包括结构体。如果你从 Go 调用返回一个结构体,它将作为 `Javascript` Map 返回到你的前端。注意:如果你想使用结构体,你必须为你的结构体字段定义`json` 标签!也可以将结构体发送回 Go。任何作为期望结构的参数传递的 Javascript Map 都将转换为该结构类型。为了使这个过程更容易,在 `dev`模式下,会生成一个 TypeScript 模块,定义绑定方法中使用的所有结构类型。使用此模块,可以构建原生 Javascript 对象并将其发送到 Go 代码。
有关绑定的更多信息可以在[绑定示例](/docs/guides/application-development#binding-methods)页面中找到。
## 前端
### 概述
前端是由 webkit 渲染的文件集合。这就像浏览器和网络服务器合二为一。您可以使用的框架或库[^1]几乎没有限制。前端和 Go 代码之间的主要交互点是:
- 调用绑定的 Go 方法
- 调用运行时方法
[^1]: 有一小部分库使用了 WebView 中不支持的功能。对于这种情况,通常有替代方案和解决方法。
### 调用绑定的 Go 方法
所有绑定的 Go 方法都可以在`window.go.<package>.<struct>.<method>`调用. 如上一节所述,这些方法返回一个 Promise其中成功调用将值返回给 resolve 处理程序,错误将值返回给 reject 处理程序。
```go title="mycode.js"
window.go.main.App.Greet("Bill").then((result) => {
console.log("The greeting is: " + result);
})
```
在`dev`模式下运行应用程序时,会生成一个 javascript 模块,用 JSDoc 注释包装这些方法。这确实有助于开发,尤其是因为大多数 IDE 将处理 JSDoc 以提供代码完成和类型提示。该模块名为`go` 并在`wailsjsdir`标志指定的目录中生成。在这个模块中有一个`bindings.js`的文件,其中包含这些包装器。对于上面的示例,该文件包含以下代码:
```js title="bindings.js"
const go = {
main: {
App: {
/**
* Greet
* @param {Person} arg1 - Go Type: string
* @returns {Promise<string>} - Go Type: string
*/
Greet: (arg1) => {
return window.go.main.App.Greet(arg1);
},
},
},
};
export default go;
```
#### 支持结构体
还额外支持在其签名中使用结构的 Go 方法。所有由绑定方法指定的 Go 结构体(作为参数或返回类型)都将自动生成 Typescript 版本作为 Go 代码包装器模块的一部分。
使用这些,可以在 Go 和 Javascript 之间共享相同的数据模型。这些模型与 JSDoc 注释一致,支持 IDE 代码自动完成。
示例:我们更新我们的`Gree`方法以接受一个`Person`而不是字符串:
```go title="main.go"
type Person struct {
Name string `json:"name"`
Age uint8 `json:"age"`
Address *Address `json:"address"`
}
type Address struct {
Street string `json:"street"`
Postcode string `json:"postcode"`
}
func (a *App) Greet(p Person) string {
return fmt.Sprintf("Hello %s (Age: %d)!", p.Name, p.Age)
}
```
我们的`bindings.js`文件现已更新以反映更改:
```js title="bindings.js"
const go = {
main: {
App: {
/**
* Greet
* @param {Person} arg1 - Go Type: main.Person
* @returns {Promise<string>} - Go Type: string
*/
Greet: (arg1) => {
return window.go.main.App.Greet(arg1);
},
},
},
};
export default go;
```
此外`bindings.js`,还有一个名为`models.ts`的文件. 这包含我们的 TypeScript 形式的 Go 结构:
```ts title="models.ts"
export class Address {
street: string;
postcode: string;
static createFrom(source: any = {}) {
return new Address(source);
}
constructor(source: any = {}) {
if ("string" === typeof source) source = JSON.parse(source);
this.street = source["street"];
this.postcode = source["postcode"];
}
}
export class Person {
name: string;
age: number;
address?: Address;
static createFrom(source: any = {}) {
return new Person(source);
}
constructor(source: any = {}) {
if ("string" === typeof source) source = JSON.parse(source);
this.name = source["name"];
this.age = source["age"];
this.address = this.convertValues(source["address"], Address);
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice) {
return (a as any[]).map((elem) => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
```
只要你将 TypeScript 作为前端构建配置的一部分,你就可以通过以下方式使用这些模型:
```js title="mycode.js"
import go from "./wailsjs/go/bindings";
import { Person } from "./wailsjs/go/models";
let name = "";
function greet(name) {
let p = new Person();
p.name = name;
p.age = 42;
go.main.App.Greet(p).then((result) => {
console.log(result);
});
}
```
JSDoc 和 TypeScript 生成模型的组合构成了一个强大的开发环境。
### 调用运行时方法
Javascript 运行时位于`window.runtime`并包含许多方法来执行各种任务,例如发出事件或执行日志记录操作:
```js title="mycode.js"
window.runtime.EventsEmit("my-event", 1);
```
可以在[运行时参考](/docs/reference/runtime/intro)中找到有关 JS 运行时的更多详细信息。