Elm with webcomponents - 这是一个错误还是我做错了什么?

问题描述

问题总结 我在 elm 0.19.1 中使用 webcomponents。 目标是让 webcomponent 在 webcomponent 属性“requeststate”发生变化时发出一个事件。 事件被发出(我可以在 webcomponent 构造函数的日志中看到),但 elm 没有正确触发“WebcomponentEvent”。 问题出在 windows 10 和 ubuntu 16.04、firefox 和 chrome 上。 没有测试旧的 elm 版本。

重现步骤:

  • 在这个最小的 repo 中重现了这个问题: https://github.com/Derryrover/elm_webcom_bug
  • 使用以下命令编译 elm: elm make src/Main.elm --output elm.js --debug
  • 使用网络服务器(例如“服务”)在本地托管
  • 点击“请求”按钮。这将更改 webcomponent 上的属性“requeststate”。这反过来会触发 webcomponent 上的自定义事件“created”。但是 elm 没有收到这个事件..

再次打开 elm-debugger 或单击“请求”按钮将神奇地在 elm 中触发事件。奇怪的。 我也做了分支'tom-experiment'。在这个分支中,我从 webcomponent-click 工作中获得了事件,但前提是我直接点击了 webcomponent 本身。

这个问题的重要性 为什么要通过更改属性来触发 webcomponent 上的事件? 这种方法的目标也是 JavaScript 互操作。 例如,我现在可以使用这个 webcomponent 来创建日期时间或 uuid 或做一些其他的 javascript 魔术。这样我就可以完全解决端口问题。 此问题的解决方案可能会解决整个 Javascript 互操作性讨论!

代码

这是我的 Main.elm:

module Main exposing (..)

import browser
import Html
import Html.Attributes
import Html.Events
import Json.Decode

main =
    browser.sandBox
        { init = init,view = view,update = update
        }

type alias Model =
    { request : Bool,received: Bool
    }

init : Model
init = { request = False,received = False 
       }


type Msg
    = Request
    | Reset
    | WebcomponentEvent


update : Msg -> Model -> Model
update msg model =
    case msg of
        WebcomponentEvent ->
            { model | received = True}
        Request ->
            { model | request = True }
        Reset -> 
            { model | request = False,received = False}
        


view : Model -> Html.Html Msg
view model =
    Html.div []
        [ Html.button [Html.Events.onClick Request] [Html.text "Request"],Html.div 
            [] 
            [ Html.text "requested:",Html.text (if model.request then "true" else "false")
            ],Html.div 
            [] 
            [ Html.text "received:",Html.text (if model.received then "true" else "false")
            ],Html.button [Html.Events.onClick Reset] [Html.text "Reset"],Html.node "webcomponent-test" 
            [ Html.Events.on "created" (Json.Decode.succeed WebcomponentEvent),Html.Attributes.attribute "requeststate" (if model.request == True then "requested" else "idle")
            ] []
        ]

这是 webcomponent.js

class Webcomponent extends HTMLElement {

  static get observedAttributes() { 
    return [
    "requeststate"
    ]; 
  }
  attributeChangedCallback(name,oldValue,newValue) {
    switch (name) {
      case  "requeststate":
        if (newValue === "requested") {
          console.log(`requested in webcomponent triggered`);
          const customEvent = new CustomEvent('created',{detail: ""});
          this.dispatchEvent(customEvent);
        }
    }
  }
  
  constructor() {
    super();
    
    this.addEventListener('created',function (e) {
      console.log("event triggered as sensed by javascript: ",e.detail,e);
    },false,true);
    
  }
}

customElements.define('webcomponent-test',Webcomponent);

这是我的 index.html

<!doctype html>
<html>
  <head>
    <script type="module" src="./webcomponent.js"></script>
    <script src="./elm.js"></script>
  </head>
  <body>
    <div id="elm_element"></div>
    <script>
    var app = Elm.Main.init({
      node: document.getElementById('elm_element')
    });
    </script>
  </body>
</html>

解决方法

这是在 Elm Slack 上讨论的。该问题最终成为时间问题。分辨率是从改变

this.dispatchEvent(customEvent)

requestAnimationFrame(() => this.dispatchEvent(customEvent))

在自定义元素中,可以在这个 ellie https://ellie-app.com/cqGkT6xgwqKa1 中看到。

感谢@antew 提供最终解决方案。

,

还有一个潜在的原因:

attributeChangedCallback 在元素连接到 DOM 之前运行

attributeChangedCallback(name,oldValue,newValue) {
    switch (name) {
      case  "requeststate":
        if (newValue === "requested") {
          console.log(`requested in webcomponent triggered`);
          const customEvent = new CustomEvent('created',{detail: ""});
          this.dispatchEvent(customEvent);
        }
    }
  }

添加

 connectedCallback(){
    console.log("Now I am ready to emit Events");
 }

验证您的 dispatchEvent 不会很快运行。

requestAnimationFrame(或setTimeout)是等待事件循环为空(因此已运行connectedCallback)的解决方法

(我不知道您的用例)您还可以在您的 oldValue === null

中测试 this.isConnectedattributeChangedCallback

另请注意,当涉及 shadowDOM 时,您可能需要在该 CustomEvent 上使用 bubbles:truecomposed:true