语言参考

gotsx 的方言是一门借 TSX 语法的静态语言, 不是 TypeScript 的子集实现——它没有 React 运行时, 类型系统限定在 Go 能表示的集合里。子集由类型系统定义: 每个表达式都能推出一个静态类型, 且落在允许集合里, 就能编译; 否则是带位置的编译错误。

几个语义约定
number 是 float64 · 可选的原始类型用零值表示缺席("" / 0 / false) · 字符串在 Go 侧按 rune 处理 · ||?? 对字符串以空为假 · 跑在服务端的代码里不能出现 DOM / async / Node, 只跑在浏览器的可以。

语法表

73 项支持, 13 项明确不支持(会报错)。这张表本身是一个岛: 过滤在浏览器里完成。

86 / 86
类别语法说明状态
模块import X from "./a"默认导入; 路径可省 .tsx / .server.tsx / .client.tsx支持
模块import { a, b as c } from "./a"命名导入支持
模块import type { T } from "./a"类型导入; 客户端对 host:* 只允许 import type(action 除外)支持
模块import { useState, useEffect, useMemo } from "gotsx"hooks支持
模块import { createStore, seed } from "gotsx"store: 多个岛共享的状态, 由页面每请求注入初值(见 Hooks 行)支持
模块import type { Node, PageProps, LayoutProps, Meta, Flash } from "gotsx"框架类型支持
模块import { x } from "host:name"服务端组件里的宿主模块: 编译成直接 Go 调用支持
模块import { toggle } from "host:name" (island) → await toggle(id)类型化 action: Registry[...].Actions 里的 Go 方法; 调用是同源 POST, 类型 Promise<T> 来自 Go 签名; 失败抛出带 .status / .fields 的 Error支持
模块export function meta(props?: PageProps): Meta页面元数据(title / description / canonical / image / noIndex), 每请求算一次, 每层布局通过 props.meta 拿到支持
模块props.session / props.flash / props.csrfPageProps: 签名会话的键值(只读)、一次性 flash 消息、经典表单用的 CSRF token支持
模块export default function / export function组件(大写)或普通函数支持
模块export const data: T[] = [...]模块级 const → Go 包级变量; 不允许 let支持
模块export interface / export type类型导出(所有类型自动可被 import type)支持
模块pages/a/[id].server.tsx / pages/docs/[...slug].server.tsx文件路由: params.id; catch-all 的 params.slug = "x/y/z"; 更具体的路由优先支持
模块pages/**/_layout.server.tsx / _404 / _error嵌套布局(LayoutProps = PageProps + meta + children, 外层包内层); _404 → gen.NotFound, _error(ErrorProps)→ gen.ErrorPage支持
模块import { Suspense } from "gotsx"流式边界(仅服务端): <Suspense fallback={…}> 随外壳先发 fallback, children 在自己的 goroutine 里渲染后流式填入支持
模块import * as ns from命名空间导入不支持
语句const x = ... / let x = ...单声明; 类型可标注可推断支持
语句const { a, b = 1 } = obj / const [x, setX] = ...解构 + 默认值(原始类型, 零值语义)支持
语句if / else if / else条件按 JS 真值语义(空串、0 为假)支持
语句for (const x of xs)for-of 遍历数组支持
语句for (let i = 0; i < n; i++) / while (cond) / break / continue经典循环; Go 侧是真正的 for 语句(continue 会执行更新)支持
语句switch (x) { case a: case b: … break; default: … }保留 JS 的贯穿语义(翻译成 Go fallthrough); switch (true) 也可以支持
语句return / throwthrow 在 Go 侧是 panic, 请求层 recover支持
语句try / catch / finally客户端完整; Go 侧只执行 try 和 finally 体支持
语句function f() {} / async function f() {}嵌套函数; async 只进 JS 后端支持
语句do … while / for … in / labeled break改写成 while, 或 for-of 遍历 Object.keys(obj)不支持
语句const a = 1, b = 2一条语句多个声明不支持
表达式number / string / template string / true / null / undefinednumber 是 float64支持
表达式[a, ...b] / { a, b: 1, ...c }数组和对象字面量(对象展开只在客户端)支持
表达式a.b / a?.b / a[i] / a?.[i]成员、可选链、下标支持
表达式f(x) / f?.(x) / useState<T>(x)调用、可选调用、显式类型参数支持
表达式! - + typeof一元支持
表达式+ - * / % === !== < > <= >=算术、严格相等、比较支持
表达式&& || ??逻辑; 原始类型的 ?? 与 || 等价(零值 = 缺席); 缺席的对象(find 没找到、可选字段)为假且 === undefined支持
表达式a ? b : c三元(节点或值)支持
表达式(a, b) => expr / x => { ... } / async () => ...箭头函数; 参数类型可从上下文推断支持
表达式x = v / += -= *= /= %= / x++ x-- ++x --x给变量、字段、Record 键、数组下标赋值; ++/-- 只对 number支持
表达式x as T / x!类型断言、非空断言支持
表达式await x只在客户端代码里支持
表达式== !=只允许 === / !==不支持
表达式new / class / this / function expression没有类和原型不支持
表达式/pattern/gimsu正则字面量, RE2 子集编译期校验(无 lookaround / 反向引用); re.test, s.match/replace/replaceAll/split/search支持
表达式delete m.key / delete m[key] / Object.hasOwn(m, key)Record 键: 缺席的键两端都读出零值; hasOwn 判断存在支持
表达式in / instanceof / void不支持
JSX<div class="x" id={v} disabled>…</div> / <br />元素、属性字符串/表达式/布尔简写、自闭合支持
JSX<></>fragment支持
JSXonClick={fn} / onInput={(e) => ...}事件; 处理器参数类型 any; 服务端不生成支持
JSXaria-* / data-* / role布尔值渲染成 "true" / "false"支持
JSX{cond && <x/>} / {a ? <x/> : <y/>} / {list.map(...)}条件与列表; 读到 signal 的才是响应式的支持
JSX{list.map((x) => <li key={x.id}>…</li>)}keyed 列表: 客户端按 key 复用 / 移动 / 销毁 DOM, 输入框、焦点、行内 effect 在重排后保留; 不写 key 就整块重建支持
JSX<Comp prop={v}>children</Comp>组件调用; children 是 Node支持
JSX{/* comment */}支持
JSX<div {...props} />属性展开不支持
JSXpassing children to an island岛的 props 走 JSON 进 HTML 属性, 放不下 Node; 共享组件和服务端组件的 children 正常不支持
JSXdangerouslySetInnerHTML没有注入 HTML 的口子; 用宿主返回 token 自己渲染不支持
类型string number boolean void any undefined null支持
类型T[] / Array<T> / Record<string, T>数组、映射支持
类型{ a: string; b?: number; f(x: T): R }对象类型; 可选原始类型 = 零值语义支持
类型interface A extends B, C { … } / type aliasextends 复制基类型的字段(同名字段覆盖)支持
类型"a" | "b" / T | undefined字面量联合 → string; 可选支持
类型(x: T) => R / Promise<T>函数类型; Promise<T> 视为 T支持
类型custom generics / tuple / intersection / keyof / typeof / enum / class不支持
Hooksconst [x, setX] = useState(init)服务端 = 初值; 客户端 = signal支持
HookssetX(v) / setX(prev => ...)支持
Hooksconst y = x * 2依赖 signal 的 const 自动是 memo, 不需要 useMemo支持
HooksuseMemo(() => ...) / useEffect(() => ...)useEffect 只进客户端, 依赖自动追踪支持
Hooksexport const cart = createStore<T>(init)client 模块的模块级 const(app/stores/cart.client.tsx): 一个 store —— 浏览器里每个字段一个 signal, 服务端是本请求注入的值; init 里缺的字段是零值; 状态必须能 JSON 化(没有函数和 Node)支持
Hookscart.count / const { count, items } = cart读取就是 signal(细粒度绑定、memo、effect); 只在 client 模块; set 之外只读支持
Hookscart.set((s) => { s.items.push(x); s.count += 1; }) / cart.set(value)在事件处理、effect 和普通函数里调用, 不能在渲染中: s 是写时复制的 draft —— 只有改过的字段通知订阅者, keyed 列表里没动的行保持身份; 传整个值则整体替换支持
Hooksseed(cart, value)在页面或布局默认组件的函数体里、JSX 之前: 本请求的所有岛用这个值渲染, 浏览器里的 store 也从它开始(首屏不闪, 不用再请求)支持
HooksuseRef / useContext / useReducer不支持
内建console.log / JSON.stringify / Math.max min floor ceil round abs sqrt random两端都有支持
内建String() Number() Boolean() parseInt parseFloat isNaN encodeURIComponent支持
内建fetch setTimeout document window location history navigator localStorage只在客户端, 类型 any支持
内建Object.keys / Object.values键按字典序(与 Go map 一致, 保证 hydrate 稳定)支持
内建Math.pow sign trunc支持
内建redirect(url, status?) / notFound()只在服务端页面: 中断渲染, 回 3xx / 404 页(也可以写 return redirect(…))支持
内建Date.now() / Date.parse(iso) / isoDate(ms)两端都是毫秒; 用 fmtDate / isoDate 格式化支持
内建Array.from / new Date(...) / Object.assign没有构造函数; 日期用数字 + isoDate不支持
数组length map filter find findIndex some every includes indexOf lastIndexOf join slice concat forEachfind 没找到给零值, 为假且 === undefined支持
数组sort reduce reverse flat atsort/reduce 与 Go 后端同语义(拷贝, 不改原数组)支持
数组push pop shift unshift splice原地修改, 作用于变量 / 字段 / 下标(Go 侧取地址); 不能用在 useState 的数组上——用 setXs([...xs, x])支持
字符串length toUpperCase toLowerCase trim includes startsWith endsWith split slice replace replaceAll repeat indexOf charAtGo 侧按 rune 处理支持
字符串padStart padEnd trimStart trimEnd lastIndexOf at localeCompare toString / number toFixed toString按 rune 处理; localeCompare 两端都按码点比较(不做本地化排序)支持
字符串matchAll / named groups / sticky regex不在 RE2 子集里不支持

响应性规则

客户端的更新模型由编译器决定, 没有 hooks 调用顺序这回事

  • const [n, setN] = useState(0) — n 是 signal; 读 n 的地方编译成 n()
  • const double = n * 2 — 初始化表达式读到 signal 的 const 自动是 memo。
  • JSX 里读到 signal/memo 的文本、属性、条件、列表各自绑一个 effect; 没读到的就是静态的, 服务端也不留标记。
  • list.map(cb) 的响应性只看 list, 不看回调内部; 条件和三元只看条件。
  • 组件的 props 不是响应式的(和 Solid 一样, 传 signal 的值只在创建时读一次); 需要响应式的内容放进 children 或条件块。

服务端 / 客户端 / 共享

  • *.server.tsx: 只编成 Go, 能 import host:*, 永不进浏览器。
  • *.client.tsx: 编成 Go(SSR 单趟)和 JS; 只能 import type 宿主类型; default 导出是岛。
  • 无后缀: 共享组件, 两端都编, 不能碰宿主也不能碰 DOM——纯渲染函数。
  • 边界由编译器强制: 客户端 import 服务端组件、服务端出现 await, 都是编译错误。