问题描述
问题总结 我在 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.isConnected
或 attributeChangedCallback
另请注意,当涉及 shadowDOM 时,您可能需要在该 CustomEvent 上使用 bubbles:true
和 composed:true
。