首页 / Wails 入门教程 / 从前端调用 Go

Wails 入门教程

从前端调用 Go

本教程共 42 篇 · 第 15 篇 · 更新于 2026-08-03

Wails桌面开发TypeScriptPromiseReact

15. 从前端调用 Go

本节目标

  • 看懂 window.go 全局对象的结构,会用它做快速调试
  • 理解绑定方法返回的 Promise 什么时候 resolve、什么时候 reject
  • 记住 Go 与 TypeScript 之间的类型映射规则
  • 在 React 里用 try/catch 或 .catch() 稳妥地处理 Go 返回的错误
  • 知道 ipc.jsruntime.js 是怎么被注入到页面里的

15-1 两种调用写法

第 12 章提过一句,这里展开。前端调用 Go 方法有两个入口,底层是同一套东西。

写法一:import 生成的模块(推荐)

import { Greet } from "../wailsjs/go/main/App";

const text = await Greet("码上学");

好处是有完整的 TypeScript 类型提示,参数写错、返回值用错,编辑器当场标红。

写法二:走全局对象

const text = await window.go.main.App.Greet("码上学");

路径规律是 window.go.<Go 包名>.<结构体名>.<方法名>。没有类型提示,但胜在不用 import,在 devtools 控制台里随手就能敲。

Tip

排查问题时,先在控制台敲一句 window.go,展开看看有没有你的结构体。绑定成功的话结构一目了然。这一招比翻半天代码快多了。

日常写业务用第一种,调试用第二种。两者调用的是同一个底层通道,行为完全一致。

15-2 Promise 的两种结局

所有绑定方法在前端都返回 Promise。跨进程通信本质是异步的,Go 那边跑完了才能把结果送回来。

Promise 的 resolve 和 reject 分别对应什么,取决于 Go 方法的签名。

只有一个返回值:永远 resolve

func (a *App) Greet(name string) string {
	return "你好," + name
}
export function Greet(arg1: string): Promise<string>;

这个 Promise 不会 reject(除非你传了非法数据导致序列化失败)。写 .catch() 也没什么意义。

返回值 + error:error 非 nil 时 reject

func (a *App) ReadConfig(path string) (string, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return "", fmt.Errorf("读取配置失败: %w", err)
	}
	return string(data), nil
}

Go 返回的 error 不为 nil 时,Promise 走 reject 分支,错误信息作为 rejection 的值传过去。

只有 error 没有值

func (a *App) SaveConfig(path, content string) error {
	return os.WriteFile(path, []byte(content), 0644)
}
export function SaveConfig(arg1: string, arg2: string): Promise<void>;

成功时 resolve 一个 void,失败时 reject。

什么都不返回

func (a *App) Refresh() {}

依然返回 Promise<void>,只是永远 resolve。

Note

Go 的惯例是「结果在前,error 在后」,Wails 严格遵循这个约定。error 必须是第二个返回值,写在第一位或者返回三个值,绑定都会失败。

15-3 类型映射表

Go 和 JavaScript 中间隔着一层 JSON 序列化,类型对应关系如下:

Go 类型TypeScript 类型说明
stringstring直接对应
int / int8int64number注意 JS number 的精度上限
uint / uint8uint64number同上
float32 / float64number直接对应
boolboolean直接对应
[]TArray<T>切片变数组
map[string]T{ [key: string]: T }key 必须是 string
struct生成的 classmodels.ts
*struct可选的 class可能为 null
interface{} / anyany失去类型信息
time.Timestring序列化成 RFC3339 字符串
Warning

Go 的 int64 能表示的范围超过 JavaScript 的安全整数范围(约 9 × 10^15)。传大整数 ID 或时间戳纳秒值时会丢精度。稳妥做法是在 Go 侧把它转成 string 再返回。

map 的 key 只能是 string。map[int]string 序列化成 JSON 后 key 会变成字符串,前端拿到的是 {"1": "a"},和你预期的类型对不上。

chanfunc、含有互斥锁的结构体这类无法 JSON 化的类型,不能出现在绑定方法的签名里。

time.Time 这一项也值得单独说一句。它序列化后是一个 RFC3339 格式的字符串,比如 2026-08-03T10:30:00Z。前端拿到后要用 new Date(str) 转换才能做日期运算。如果你只关心「显示」,直接在 Go 侧格式化成中文习惯的字符串返回会更省事,前端一行代码都不用写。

还有一个容易忽略的点:Go 里值为 nil 的切片和 map,序列化后是 null 而不是 []{}。前端拿到 null 直接 .map() 会报错。要么在 Go 侧初始化成空切片再返回,要么前端做一次空值兜底。

15-4 错误处理的三种写法

async/await + try/catch(最常用)

import { ReadConfig } from "../wailsjs/go/main/App";

async function load() {
  try {
    const content = await ReadConfig("./config.json");
    console.log(content);
  } catch (err) {
    // err 是 Go 侧 error 的字符串描述
    console.error("加载失败:", err);
  }
}

Promise 链

ReadConfig("./config.json")
  .then((content) => console.log(content))
  .catch((err) => console.error("加载失败:", err));

在 React 组件里的完整形态

import { useState } from "react";
import { ReadConfig } from "../wailsjs/go/main/App";

export default function ConfigLoader() {
  const [content, setContent] = useState("");
  const [error, setError] = useState("");
  const [loading, setLoading] = useState(false);

  const handleLoad = async () => {
    setLoading(true);
    setError("");
    try {
      const data = await ReadConfig("./config.json");
      setContent(data);
    } catch (err) {
      setError(String(err));
    } finally {
      setLoading(false);
    }
  };

  return (
    <div>
      <button onClick={handleLoad} disabled={loading}>
        {loading ? "加载中…" : "加载配置"}
      </button>
      {error && <p style={{ color: "red" }}>{error}</p>}
      {content && <pre>{content}</pre>}
    </div>
  );
}

loading 状态别省。桌面应用的用户对「点了没反应」特别敏感,一个磁盘 IO 慢一点就会让人怀疑程序卡死了。

Tip

catch 到的 err 是 Go 侧 error.Error() 的字符串,不是一个结构化对象。想传错误码、错误详情这些信息,就在 Go 里定义一个结构体作为正常返回值,把成功失败的判断放进业务字段里。

比如这样:

type Result struct {
	OK      bool   `json:"ok"`
	Code    string `json:"code"`
	Message string `json:"message"`
	Data    string `json:"data"`
}

func (a *App) LoadSafe(path string) Result {
	data, err := os.ReadFile(path)
	if err != nil {
		if os.IsNotExist(err) {
			return Result{OK: false, Code: "NOT_FOUND", Message: "文件不存在"}
		}
		return Result{OK: false, Code: "IO_ERROR", Message: err.Error()}
	}
	return Result{OK: true, Data: string(data)}
}

前端就能按 code 分支处理,而不是靠字符串匹配去猜错误类型。

15-5 window.go 是怎么来的

这套调用机制不是魔法。Wails 在给浏览器返回 index.html 时,会往 <body> 里注入两个脚本:

<script src="/wails/ipc.js"></script>
<script src="/wails/runtime.js"></script>

ipc.js 负责建立前端到 Go 的调用通道,也就是 window.go 的来源。runtime.js 提供 window.runtime,下一章会详细讲。

这两个脚本由 Wails 的资产服务器动态提供,不在你的项目目录里,所以别去 frontend/ 里找。

大多数情况下自动注入就够了。但如果你用了自己的开发服务器(比如 Create React App 的 yarn start),前端重建时会覆盖掉 Wails 注入的内容,导致 window.go 不存在。

这时候需要关掉自动注入,手动写进 index.html

<head>
  <meta name="wails-options" content="noautoinject" />
  <script src="/wails/ipc.js"></script>
  <script src="/wails/runtime.js"></script>
</head>

wails-options 这个 meta 标签支持三个值:

作用
noautoinjectruntime只禁用 runtime.js 的自动注入
noautoinjectipc只禁用 ipc.js 的自动注入
noautoinject全部禁用

多个值用逗号分隔。

Warning

用 Vite 的 react-ts 官方模板不需要做这些配置,Wails 的 dev 流程已经处理好了。只有接入外部开发服务器时才会遇到这个问题。手动注入了却忘记加 noautoinject,脚本会被加载两遍。

常见误区

忘了 awaitconst r = Greet("x") 拿到的是 Promise 对象,打印出来是 [object Promise]。TypeScript 会报类型错误,纯 JS 项目里就只能靠自己发现。

import 路径写错wailsjsfrontend/ 根目录下,不在 src/ 里。组件层级越深,../ 越多。可以在 tsconfig.json 里配 path alias 简化。

在 useEffect 里直接写 async 函数useEffect(async () => {...}) 是错的,effect 的返回值会被当成清理函数。正确写法是在里面定义一个 async 函数再调用,或者用 .then() 链。

指望前端能捕获 Go 的 panic。Go 方法里 panic 会让整个应用崩溃,不会变成一个 Promise rejection。可能 panic 的地方自己加 defer recover()

在渲染期间调用绑定方法。组件函数体里直接 Greet(...),每次渲染都会触发一次 IPC 调用。放进 useEffect 或事件处理函数里。

用 int64 传大数。前面说过的精度问题,症状是前端拿到的数字末尾几位对不上。转成字符串传。

小结

前端调 Go 这条链路,本质就三步:Wails 注入 ipc.js 建立通道 → 生成的 wailsjs/go/ 模块包装成普通函数 → 调用返回 Promise。

Promise 的行为由 Go 方法签名决定:单返回值永远 resolve,带 error 的在 error 非 nil 时 reject。

类型映射基本符合直觉,两个例外要记牢:int64 有精度风险,map 的 key 只能是 string。

错误处理上,简单场景用 try/catch 接住 error 字符串;需要错误码和结构化信息时,改成返回一个带业务状态的结构体。

下一章跳出绑定,看看运行时(Runtime)这套 API 集合——窗口、对话框、剪贴板、日志,都在里面。