如何使用端口交互协调渲染(榆木0.17)

Luk*_*ski 8 elm

我想将Elm与Javascript库集成,以便Elm动态创建"cells"(html div),Javascript将提供其id-s并使用它们来执行自定义操作.我想要的顺序是

  1. Elm创建一个单元格(并指定id)
  2. 带有id的消息被发送到端口
  3. Javascript接收消息并执行其操作

这就是我在开始时实现这一点的方法(完整源代码):

port onCellAdded : CellID -> Cmd msg

update : Msg -> Model -> (Model, Cmd Msg)
update message ({cells} as model) =
  case message of

    Push ->
      let
        uid = List.length cells
      in
      ({ model
        | cells = [uid] ++ cells
      }, onCellAdded uid)
Run Code Online (Sandbox Code Playgroud)

问题是Javascript在另一边

var container = document.getElementById('app');
var demoApp = Elm.RenderDemo.embed(container);

demoApp.ports.onCellAdded.subscribe(function(cellID) {
   if(document.getElementById('cell:' + cellID) === null) { window.alert("Cannot find cell " + cellID) }    
});
Run Code Online (Sandbox Code Playgroud)

抱怨说找不到这样的身份证.显然,该视图尚未呈现.

所以我OnCellAdded在Elm应用程序中添加了另一个state(),希望流程如下:

  1. Elm创建一个单元(在Push上)并request(Task.perform)一个异步任务OnCellAdded
  2. 这里视图被渲染
  3. OnCellAdded 被调用,带有id的消息被发送到端口
  4. Javascript接收消息并执行其操作

实现看起来像这样(差异)(完整源代码):

update message ({cells} as model) =
  case message of

    Push ->
      let
        uid = List.length cells
      in
      ({ model
        | cells = [uid] ++ cells
      }, msgToCmd (OnCellAdded uid))

    OnCellAdded counter ->
      (model, onCellAdded counter)

msgToCmd : msg -> Cmd msg
msgToCmd msg =
      Task.perform identity identity (Task.succeed msg)
Run Code Online (Sandbox Code Playgroud)

但是OnCellAdded,Push如果没有在中间渲染模型,仍会立即处理.

我的最后一次尝试是使用Update.andThen(差异)(完整来源)

Push ->
  let
    uid = List.length cells
  in
  ({ model
    | cells = [uid] ++ cells
  }, Cmd.none)
  |> Update.andThen update (OnCellAdded uid)
Run Code Online (Sandbox Code Playgroud)

它仍然无效.我需要一些帮助.

cDi*_*tch 8

使用requestAnimationFrame()的实现

这似乎是我经验中最干净的解决方案.

var container = document.getElementById('app');
var demoApp = Elm.RenderDemo.embed(container);
var requestAnimationFrame = 
       window.requestAnimationFrame ||
       window.mozRequestAnimationFrame || 
       window.webkitRequestAnimationFrame || 
       window.msRequestAnimationFrame;   //Cross browser support

var myPerfectlyTimedFunc = function(cellID) {
   requestAnimationFrame(function() { 
       if(document.getElementById('cell:' + cellID) === null) { 
          window.alert("Cannot find cell " + cellID) 
       }
   })
}

demoApp.ports.onCellAdded.subscribe(myPerfectlyTimedFunc);
Run Code Online (Sandbox Code Playgroud)

请参阅此处了解具有多个页面的SPA类型设置以及重新呈现JS互操作图的需要.还可以更新图表中的数据值.(控制台日志消息也可能具有指导意义.)

如果有人对如何在Elm侧而不是html/js侧实现这一点感到好奇,请参阅Elm Defer Command库.

如您所述,问题是:

  1. 加载Javascript并查找尚未创建的元素.
  2. Elm在此搜索后呈现DOM,并显示您需要的元素.
  3. 您通过端口发送的任何Elm命令也将在渲染时或之前发生,因此端口订阅调用的任何javascript都会遇到同样的问题.

Elm使用requestAnimationFrame(rAF)本身作为排队DOM渲染的方法,并且有充分的理由.假设Elm在不到1/60秒内进行多次DOM操作,而不是单独渲染每个操作 - 这将是非常低效的 - Elm会将它们传递给浏览器的rAF,它将作为整体DOM渲染的缓冲区/队列.换句话说,之后view会在动画帧上调用update,因此view每次调用后都不会发生调用update.

在过去,人们会使用:

setInterval(someAnimationFunc, 16.6) //16.6ms for 60fps
Run Code Online (Sandbox Code Playgroud)

requestAnimationFrame是浏览器保留队列的一种方式,它以60fps的速度管理和循环.这提供了许多改进:

  • 浏览器可以优化渲染,因此动画将更加流畅
  • 非活动选项卡中的动画将停止,允许CPU冷却
  • 电池更友好

对RAF更多信息这里,并在这里,以及由谷歌的视频在这里

当我尝试将Chartist.js图形渲染到最初在Elm中创建的div时,我的个人故事开始了.我还想拥有多个页面(SPA样式),并且在各种页面更改时重新创建div元素时,图表需要重新呈现.

我在索引中将div写为直接HTML,但这阻止了我想要的SPA功能.我还使用了JQuery ala的端口和订阅$(window).load(tellElmToReRender),以及给Arrive.js一个 - 但是每个都导致了各种错误和缺乏所需的功能.我有点乱搞rAF但是在错误的地方和错误的方式使用它.这是在听完ElmTown - 第4集 - JS Interop之后,在那里我顿悟了一下,并意识到应该如何使用它.


hal*_*bra 5

截至0.17.1目前,没有好办法实现这一目标.

我建议最简单的方法是使用setTimeout等待至少60ms或等到下一个requestAnimationFrame

考虑这个例子:

demoApp.ports.onCellAdded.subscribe(function(cellID) {
   setTimeout(function() {
      if(document.getElementById('cell:' + cellID) === null) {
         window.alert("Cannot find cell " + cellID)
      }
   }, 60);
});
Run Code Online (Sandbox Code Playgroud)

有一个功能请求#19来添加一个钩子,因此可以知道HTML节点何时在DOM中.

你可以在这里取得进展,很可能是在即将发布的版本中.


ant*_*tfx 5

我昨天想做类似的事情,将MorrisJS集成到Elm生成的 div 中

最终我遇到了Arrive JS,它使用大多数现代浏览器中可用的新MutationObserver来观察 DOM 的变化。

所以在我的情况下,代码看起来像这样(简化):

$(document).ready(() => {
  $(document).arrive('.morris-chart', function () {
    Morris.Bar({
      element: this,
      data: [
        { y: '2006', a: 100, b: 90 },
        { y: '2007', a: 75, b: 65 },
        { y: '2008', a: 50, b: 40 },
        { y: '2009', a: 75, b: 65 },
        { y: '2010', a: 50, b: 40 },
        { y: '2011', a: 75, b: 65 },
        { y: '2012', a: 100, b: 90 }
      ],
      xkey: 'y',
      ykeys: ['a', 'b'],
      labels: ['Series A', 'Series B']
    })
  })
})
Run Code Online (Sandbox Code Playgroud)

这会使用.morris-chart该类监视 dom 中的任何新元素,一旦发现它就会使用该新元素创建图表。

所以只有在Elm运行该view函数然后重新生成 DOM后才会调用它。

也许这样的东西会满足您的需求。