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

Elm with webcomponents - Is this a bug or am I doing something wrong?

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

重现步骤:

打开 elm 调试器或再次单击 'request' 按钮将神奇地使事件在 elm 中触发。奇怪的。 我还创建了分支 'tom-experiment'。在这个分支中,我从 webcomponent-click 工作中获得了事件,但前提是我直接单击 webcomponent 本身。

这个问题的重要性 为什么要通过更改属性来触发 Web 组件上的事件? 这种方法的目标也是 JavaScript 互操作。 例如,我现在可以使用此 Web 组件来创建日期时间或 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 运行s before元素连接到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 运行)

(我不知道您的用例)您还可以在 attributeChangedCallback

中测试 oldValue === nullthis.isConnected

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