dom - (PureScript) 如何在 Eff 以外的单子(monad)上下文中运行 DOM 事件监听器回调?

标签 dom functional-programming dom-events purescript

我正在使用 PureScript 制作 Canvas 游戏,我想知道处理事件监听器的最佳方法是什么,特别是在自定义 monad 堆栈中运行回调。这是我的游戏堆栈...

type BaseEffect e = Eff (canvas :: CANVAS, dom :: DOM, console :: CONSOLE | e)
type GameState = { canvas :: CanvasElement, context :: Context2D, angle :: Number }
type GameEffect e a = StateT GameState (BaseEffect e) a

我想做的是在按下任意键时更改 GameState 中的“角度”属性(仅出于开发目的,以便我可以调整图形)。这是我的回调函数...

changeState :: forall e. Event -> GameEffect e Unit
changeState a = do
  modify \s -> s { angle = s.angle + 1.0 }
  liftEff $ log "keypress"
  pure unit

但是 addEventListenereventListener 看起来它们只能与 Eff 一起使用,因此以下内容不会键入检查...

addEventListener
  (EventType "keypress")
  (eventListener changeState)
  false
  ((elementToEventTarget <<< htmlElementToElement) bodyHtmlElement)

我想我可以自己定义 addEventListener 和 eventListener 使用外部函数接口(interface)导入它们(将 Eff 更改为 GameEffect)。该类型已检查,但当我尝试在浏览器中运行时导致控制台错误。

foreign import addEventListener :: forall e. EventType -> EventListener e -> Boolean -> EventTarget -> GameEffect e Unit
foreign import eventListener :: forall e. (Event -> GameEffect e Unit) -> EventListener e

处理 monad 堆栈中正在运行的回调的最佳方法是什么?

最佳答案

我会使用purescript-aff-coroutines为了这。这意味着将 BaseEffect 更改为 Aff,但任何 Eff 能做的事情 Aff 也能做:

import Prelude

import Control.Coroutine as CR
import Control.Coroutine.Aff as CRA
import Control.Monad.Aff (Aff)
import Control.Monad.Aff.AVar (AVAR)
import Control.Monad.Eff.Class (liftEff)
import Control.Monad.Eff.Console (CONSOLE, log)
import Control.Monad.Rec.Class (forever)
import Control.Monad.State (StateT, lift, modify)
import Data.Either (Either(..))

import DOM (DOM)
import DOM.Event.EventTarget (addEventListener, eventListener)
import DOM.Event.Types (Event, EventTarget, EventType(..))
import DOM.HTML.Types (HTMLElement, htmlElementToElement)
import DOM.Node.Types (elementToEventTarget)

import Graphics.Canvas (CANVAS, CanvasElement, Context2D)

type BaseEffect e = Aff (canvas :: CANVAS, dom :: DOM, console :: CONSOLE, avar :: AVAR | e)
type GameState = { canvas :: CanvasElement, context :: Context2D, angle :: Number }
type GameEffect e = StateT GameState (BaseEffect e)

changeState :: forall e. Event -> GameEffect e Unit
changeState a = do
  modify \s -> s { angle = s.angle + 1.0 }
  liftEff $ log "keypress"
  pure unit

eventProducer :: forall e. EventType -> EventTarget -> CR.Producer Event (GameEffect e) Unit
eventProducer eventType target =
  CRA.produce' \emit ->
    addEventListener eventType (eventListener (emit <<< Left)) false target

setupListener :: forall e. HTMLElement -> GameEffect e Unit
setupListener bodyHtmlElement = CR.runProcess $ consumer `CR.pullFrom` producer
  where
  producer :: CR.Producer Event (GameEffect e) Unit
  producer =
    eventProducer
      (EventType "keypress")
      ((elementToEventTarget <<< htmlElementToElement) bodyHtmlElement)
  consumer :: CR.Consumer Event (GameEffect e) Unit
  consumer = forever $ lift <<< changeState =<< CR.await

因此,这里的 eventProducer 函数为事件监听器创建一个协程生成器,然后 setupListener 执行与理论上的 addEventListener 等效的操作你上面的用法。

其工作原理是为监听器创建一个生产者,然后将其连接到接收到 Event 时调用 changeState 的消费者。协程进程在单子(monad)上下文中运行,这里是您的 GameEffect 单子(monad),这就是一切顺利的原因。

关于dom - (PureScript) 如何在 Eff 以外的单子(monad)上下文中运行 DOM 事件监听器回调?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42080807/

相关文章:

javascript - innerhtml提取dom中相邻元素的方法

java - 无效的 Lambda 表达式

在不使用匿名函数的情况下创建函数适配器

javascript - 如何让UI线程持续运行

PHP Dom 文档 html 更快还是 preg_match_all 函数更快?

javascript - 在 dom 中移动 div 的最快方法是什么?

javascript - HTML5 下载属性不适用于 Mozilla

haskell - 功能知识的具体示例,让您编写更好的命令式/OO代码

javascript - D3js : mouseover of one element change opacity of several others elements

javascript - Django JavaScript 下拉菜单