StateT
在这种情况下不起作用。问题是您需要计数器的状态在按钮回调的调用之间保持不变。由于回调(startGUI
以及)产生UI
动作,StateT
使用它们运行的任何计算都必须是独立的,以便您可以调用runStateT
和使用结果UI
动作。
使用 Threepenny 保持持久状态的主要方法有两种。第一个也是最直接的方法是使用一个IORef
(它只是一个存在于 中的可变变量IO
)来保持计数器状态。这导致代码与使用传统事件回调 GUI 库编写的代码非常相似。
import Data.IORef
import Control.Monad.Trans (liftIO)
-- etc.
mkButtonAndList :: UI [Element]
mkButtonAndList = do
myButton <- UI.button # set text "Click me!"
myList <- UI.ul
counter <- liftIO $ newIORef (0 :: Int) -- Mutable cell initialization.
on UI.click myButton $ \_ -> do
count <- liftIO $ readIORef counter -- Reads the current value.
element myList #+ [UI.li # set text (show count)]
lift IO $ modifyIORef counter (+1) -- Increments the counter.
return [myButton, myList]
第二种方式是从命令式回调接口切换到由Reactive.Threepenny
.
mkButtonAndList :: UI [Element]
mkButtonAndList = do
myButton <- UI.button # set text "Click me!"
myList <- UI.ul
let eClick = UI.click myButton -- Event fired by button clicks.
eIncrement = (+1) <$ eClick -- The (+1) function is carried as event data.
bCounter <- accumB 0 eIncrement -- Accumulates the increments into a counter.
-- A separate event will carry the current value of the counter.
let eCount = bCounter <@ eClick
-- Registers a callback.
onEvent eCount $ \count ->
element myList #+ [UI.li # set text (show count)]
return [myButton, myList]
go的典型用法Reactive.Threepenny
是这样的:
- 首先,您通过(或者,如果您选择的事件未包含在该模块中)获取
Event
来自用户的输入。在这里,“原始”输入事件是.Graphics.UI.Threepenny.Events
domEvent
eClick
Control.Applicative
然后,您使用和Reactive.Threepenny
组合器按摩事件数据。在我们的示例中,我们转发eClick
为eIncrement
和eCount
,在每种情况下设置不同的事件数据。
- 最后,您可以通过使用事件数据构建
Behavior
(like bCounter
)或回调(通过 using onEvent
)。一个行为有点像一个可变变量,除了对它的更改是由您的事件网络以原则方式指定的,而不是通过散布在您的代码库中的任意更新。处理此处未显示的行为的有用函数是sink
函数,它允许您将 DOM 中的属性绑定到行为的值。
这个问题和 Apfelmus 的回答中提供了一个额外的示例,以及对这两种方法的更多评论。
细节:在 FRP 版本中您可能关心的一件事是是否eCount
会bCounter
在eIncrement
. 答案是该值肯定会是旧值,正如预期的那样,因为正如Reactive.Threepenny
文档所述,Behavior
更新和回调触发具有名义上的延迟,而其他操作不会发生这种延迟Event
。