回调
回调是一种成员,它将一次调用从一个地方传递到另一个地方。
它使用 callback 关键字声明,通过调用它来触发,
并由通过 => 箭头设置的处理程序作出反应。 {#sls.callback.def}
export component Example inherits Window { callback hello; TouchArea { clicked => { root.hello(); } }}回调可以在组件中声明、在组件内的任何元素上声明, 或在全局对象中声明。
回调声明 会向其所在元素体内添加一个回调。
它由关键字 callback、一个名字以及 ; 组成。 {#sls.callback.decl.form}
回调名字不得与同一元素的其他成员冲突: 名字与现有属性、函数或回调匹配的声明是一个错误。 {#sls.callback.decl.unique}
在名字与 ; 之间,声明还可以带有回调的参数类型及其返回类型:
export component Example { callback plain; // no parameters, no return value callback with-args(int, string); // two parameters callback with-return(int, int) -> int;}允许以下形式:
- 不带括号表示声明一个无参数的回调,例如
callback plain;。 - 括号列出参数类型,例如
callback with-args(int, string);。 允许在最后一个参数后出现尾随逗号。 - 参数可以带名字,写作
name: type,例如callback c(foo: int, bar: string)。 名字是可选的,并且可以与无名字的参数混合使用。 名字不携带任何语义值;处理程序会独立地命名其参数。 - 返回类型写在
->之后,并且要求括号存在, 即使没有参数也是如此:callback c() -> int。 书写不带括号的-> type会构成编译错误。
pure 可以放在 callback 之前,以将该回调标记为没有副作用(见纯性)。
pure 回调可以从纯上下文中调用,并且其处理程序本身必须是纯的。
从纯上下文中调用非纯回调会构成编译错误。
重复的回调声明会构成编译错误。
声明前可以带有 @deprecated 注解,
它会将该回调标记为已废弃,并指引用户使用其替代者。
回调使用调用语法来触发: 回调,后跟包含参数的括号。 {#sls.callback.call.form}
触发回调会运行其处理程序。 没有处理程序的回调在被触发时不执行任何操作。 {#sls.callback.call.handler}
export component Example inherits Window { callback acknowledged; TouchArea { clicked => { root.acknowledged(); } }}参数的数量必须与声明匹配, 并且带返回类型的回调在没有处理程序时会返回该类型的默认值:
export component Example { pure callback hello(int, int) -> int; property <int> result: hello(1, 2);}处理程序 对回调的触发作出反应。
它写作回调的名字、=>,以及单个表达式或代码块。 {#sls.callback.handler.form}
一个回调至多有一个处理程序; 为同一回调设置第二个处理程序是一个错误。 {#sls.callback.handler.one}
处理程序可以设置在子元素的回调上,
例如 TouchArea 的 clicked。 {#sls.callback.handler.target}
export component Example inherits Window { callback first; callback second; TouchArea { clicked => { root.first(); root.second(); } }}- 处理程序在括号中命名其参数。 这些名字是处理程序体的局部名字,与声明中的任何名字无关。 当回调没有参数时,可以省略括号。
- 主体的最后一个表达式是返回值;其类型必须与回调的返回类型匹配。
export component Example { callback sum(int, int) -> int; sum(a, b) => { a + b }}处理程序也可以设置在同一组件中声明的回调上。
init 回调
Section titled “init 回调”init 是存在于每个元素上的内置回调。
其处理程序在元素实例化时运行一次。
init 处理程序从最外层组件向内运行,因此组件自身的 init 会在
它所包含元素的 init 之后运行。
显式调用 init 不执行任何操作,并且已废弃。
全局对象既不能声明也不能处理 init 回调;两者都会构成编译错误。
回调别名将一个回调转发到另一个回调,使用与双向绑定相同的 <=> 语法:
export component Example { callback clicked <=> area.clicked; area := TouchArea {}}- 别名必须省略参数列表;在化名的声明中书写括号会构成编译错误。
- 两个回调必须具有相同的签名。
- 触发任一回调都会触发另一个,设置在任一方上的处理程序都会响应两者。
别名可以指向全局对象的回调,写作 Global.callback。
全局对象可以为另一个全局对象的回调设置别名;这是受支持的方式,用于让一个全局对象实现另一个的回调。
非全局元素不能为指向全局对象回调的别名设置处理程序,
因为该元素可能被实例化多次,而全局对象是单例;这会构成编译错误。
changed <property> => { ... } 声明一个在属性值变化时运行的处理程序:
export component Example inherits Window { in-out property <string> value; changed value => { debug(self.value); }}- 目标必须是该元素上存在的属性;命名回调、函数 或不存在的成员会构成编译错误。
- 变更回调不能设置在属于另一个组件的私有属性上;这会构成编译错误。
- 一个属性在每个元素上至多有一个变更回调;重复会构成编译错误。
- 处理程序体可以有副作用;不要求它是纯的。
变更回调不会立即被调用。 它会被排队,并在事件循环的后续迭代中运行。 仅当值确实与上一次运行时的值不同时才会运行: 一个周期内的多次变化会合并为一次调用, 而在回调运行之前先变化又恢复的值不会产生调用。
在变更回调内部以反馈到同一属性的方式修改该属性是未定义行为:
export component Example { in-out property <int> foo; property <int> bar: foo + 1; changed bar => { foo += 1; }}这种一个变更回调触发另一个变更回调的链式反应不会无界运行。 运行时会在一个循环中运行排队的变更处理程序,该循环在十轮之后停止, 因此自我延续的序列会在不调用剩余处理程序的情况下终止。
变更回调强制该属性急切求值,而非惰性求值, 并且其副作用会使周围的绑定变为非纯。 声明式绑定会自动跟踪其依赖关系并按需求值, 因此可以用绑定表达的关系应表达为绑定,而不是变更回调。
© 2026 SixtyFPS GmbH