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

时间:2016-08-15 09:41:04

标签: elm

我想将Elm与Javascript库集成,以便Elm能够动态创建" cell" (html divs),Javascript将提供他们的id-s并使用它们来执行自定义操作。我想要的序列是

  1. Elm创建一个单元格(并指定id)
  2. 带有id的消息被发送到端口
  3. Javascript接收消息并执行其操作
  4. 这就是我在开头(full source)实现此目的的方法:

    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)
    

    问题在于另一方面的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) }    
    });
    

    抱怨说无法找到这样的身份证明。显然,该视图尚未呈现。

    所以我在Elm应用程序中添加了另一个状态(OnCellAdded),希望流程是这样的:

    1. Elm创建一个单元格(在推送上)并请求(Task.perform)异步任务OnCellAdded
    2. 此处视图呈现
    3. OnCellAdded被调用,带有id的消息被发送到端口
    4. Javascript收到邮件并执行其操作
    5. 实现看起来像这样(diff)(full source):

      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)
      

      但仍然OnCellAddedPush之后立即处理,而不会在其间呈现模型。

      我的最后一次尝试是使用Update.andThendiff)(full source

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

      仍然没有用。我需要一些帮助。

3 个答案:

答案 0 :(得分: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);

See here for an SPA type setup with multiple pages and the need to re-render a JS interop'd graph。还可以更新图表中的数据值。 (控制台日志消息也可能具有指导意义。)

如果有人好奇如何在榆树方面而不是html / js方面实施,请参阅Elm Defer Command库。

如您所述,问题是:

  1. 加载Javascript并查找尚未创建的元素。
  2. Elm在此搜索后呈现DOM,并显示您需要的元素。
  3. 您通过端口发送的任何Elm命令也会在渲染时或之前发生,因此端口订阅调用的任何javascript都会遇到同样的问题。
  4. Elm使用requestAnimationFrame(rAF)本身作为排队DOM渲染的方法,并且有充分的理由。假设Elm在不到1/60秒内进行多次DOM操作,而不是单独渲染每个操作 - 这将是非常低效的 - Elm会将它们传递给浏览器的rAF,它将作为整体DOM渲染的缓冲区/队列。换句话说,在view之后动画帧上会调用update,因此view调用并非总是在每个update之后调用。

    过去人们会使用:

    setInterval(someAnimationFunc, 16.6) //16.6ms for 60fps
    

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

    • 浏览器可以优化渲染,因此动画会更顺畅
    • 非活动标签中的动画将停止,允许CPU冷却
    • 更加适合电池使用

    有关rAF herehere的更多信息,以及Google here的视频

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

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

答案 1 :(得分: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);
});

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

您可以获得进度here,很可能是即将发布的版本。

答案 2 :(得分:4)

我想昨天做类似的事情,将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']
    })
  })
})

使用.morris-chart类查看任何新元素的dom,一旦发现它使用该新元素创建图表。

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

也许这样的事情会满足你的需求。

相关问题