英文:
The setters and getters are not called in a custom web component
问题
I have the following web component:
我有以下的网页组件:
I originally tested this locally, (in a create-react-app) didn't work. I then put it into codesandbox (react template) where it doesn't work either.
我最初在本地测试过这个(在create-react-app中),但不起作用。然后我把它放到codesandbox(react模板)中,它也不起作用。
constructor() {
console.log("ctor");
super();
}
_count: number = 0;
set count(value: number) {
console.log("CounterWebComponent set", value);
this._count = value;
}
get count() {
console.log("CounterWebComponent get", this._count);
return this._count;
}
connectedCallback() {
console.log("CounterWebComponent connected");
this.innerHTML = "<h1>fff</h1>";
}
disconnectedCallback() {
console.log("CounterWebComponent disconnected");
}
}
if (!customElements.get("counter-wc-custom"))
customElements.define("counter-wc-custom", CounterWebComponent);
In index.html I'm trying to set the count:
在index.html中,我正在尝试设置计数:
<script>
const counter2 = document.getElementById("counter1");
counter2.count = 23;
alert(counter2.count);
alert(counter2._count); //using alert because console.log inside index.html is not working for some reason in codesandbox
console.log("count", counter2.count);
console.log("_count", counter2._count);
counter2.addEventListener("count", (e) => console.log(e));
</script>
If I use it like this it works:
如果我像这样使用它,它可以工作:
a.count = 2;
console.log("count", a.count);
console.log("_count", a._count);
EDIT: https://codesandbox.io/s/react-web-component-custom-wrapper-xh2n9s (I forgot the link like an idiot)
我假设我在某处配置错误了,但我不知道在哪里。任何帮助都将不胜感激。
英文:
I have the following web component:
I originally tested this locally, (in a create-react-app) didn't work. I then put it into codesandbox (react template) where it doesn't work either.
class CounterWebComponent extends HTMLElement {
constructor() {
console.log("ctor");
super();
}
_count: number = 0;
set count(value: number) {
console.log("CounterWebComponent set", value);
this._count = value;
}
get count() {
console.log("CounterWebComponent get", this._count);
return this._count;
}
connectedCallback() {
console.log("CounterWebComponent connected");
this.innerHTML = "<h1>fff</h1>";
}
disconnectedCallback() {
console.log("CounterWebComponent disconnected");
}
}
if (!customElements.get("counter-wc-custom"))
customElements.define("counter-wc-custom", CounterWebComponent);
In index.html I'm trying to set the count:
<counter-wc-custom id="counter1"></counter-wc-custom>
<script>
const counter2 = document.getElementById("counter1");
counter2.count = 23;
alert(counter2.count);
alert(counter2._count); //using alert because console.log inside index.html is not working for some reason in codesandbox
console.log("count", counter2.count);
console.log("_count", counter2._count);
counter2.addEventListener("count", (e) => console.log(e));
</script>
If I use it like this it works:
const a = document.createElement("counter-wc-custom");
a.count = 2;
console.log("count", a.count);
console.log("_count", a._count);
EDIT: https://codesandbox.io/s/react-web-component-custom-wrapper-xh2n9s (I forgot the link like an idiot)
I assume I configured something somewhere incorrectly but I have no idea what. Any help is appreciated.
答案1
得分: 2
这涉及到时机; 具体来说,涉及到设置属性与定义自定义元素的时机。假设我们这样做:
<hello-world></hello-world>
<script>
const helloWorld = document.querySelector('hello-world');
helloWorld.message = 'Foo!'
customElements.define('hello-world', class extends HTMLElement {
#message = 'Default message.'
get message(){ return this.#message; }
set message(value){
this.#message = value;
console.log('Message: ', this.#message);
}
});
helloWorld.message = 'Bar?'
</script>
在这个示例中,我们在定义实例之前在helloWorld
实例上设置了message
属性。然后当我们定义自定义元素时,getter和setter被定义在元素的原型上;但是,helloWorld
实例现在已经有了一个"自己的"属性message
。这意味着即使在定义之后将message
设置为Bar?
也不会触发setter,因为它仍然只是使用最初在实例上定义的属性,完全忽略了getter和setter。
如果现在我们delete helloWorld.message
,我们会删除最初定义的属性,然后执行helloWorld.message = 'Baz!'
将最终触发setter。
您实际上可以通过获取与问题相关的自定义元素实例的引用,查看Object.getOwnPropertyDescriptors(elementInstance)
的结果来检查这一点。在该调用的结果中包含的任何属性都是"覆盖"您可能在原型上定义的getter和setter的属性,因此可能是不需要的。
在我上面给出的示例中,事情运行的顺序(有意)是明显的。在codesandbox中,这可能不太明显;查看它实际输出的页面似乎确实在定义组件之前运行了.count = 23
(上面的检查确实显示count
是ownPropertyDescriptors
中的一部分)。
在我看来,解决这个问题有两种主要方法:
- 确保在实例上设置属性之前,先运行自定义元素定义,可以通过重新排列包含的脚本或使用
customElements.whenDefined(...)
来实现。 - 如果您的自定义元素需要支持此操作,请调整您的组件以读取它可以找到的实例上的任何自有属性描述符,并在自定义元素构造函数中执行我上面提到的删除-设置操作。
英文:
This has to do with timing; specifically, when you set the property versus when you define the custom element. Let's say we do:
<hello-world></hello-world>
<script>
const helloWorld = document.querySelector('hello-world');
helloWorld.message = 'Foo!'
customElements.define('hello-world', class extends HTMLElement {
#message = 'Default message.'
get message(){ return this.#message; }
set message(value){
this.#message = value;
console.log('Message: ', this.#message);
}
});
helloWorld.message = 'Bar?'
</script>
In this example, we set the message
property on the helloWorld
instance before the instance is defined. Then when we define the custom element, the getters and setters are defined on the element's prototype; but, the helloWorld
instance now already has an "own" property message
. This means even setting the message
to Bar?
after definition doesn't trigger the setter, because it's still just using the property initially defined on the instance, completely ignoring the getters and setters.
If we now delete helloWorld.message
, we remove that initially defined property, and then doing helloWorld.message = 'Baz!'
will finally trigger the setter.
You can actually check that this is the case by getting a reference to the custom element instance in question and seeing what the result is of Object.getOwnPropertyDescriptors(elementInstance)
. Any properties included in the result of that call are "overwriting" getters and setters you may have defined on the prototype, and so are likely unwanted.
In the example I gave above, the order things run in is (intentionally) obvious. In codesandbox, it might be a little less obvious; looking at the actual page it outputs it seems like it is indeed running your .count = 23
before it defines the component (and the above check indeed reveals count
to be among the ownPropertyDescriptors
).
The way I see it, there are a two main ways to solve this issue:
- Make sure your custom element definitions run before doing setting properties on instances, either by rearranging your included scripts or through
customElements.whenDefined(...)
. - In case your custom element needs to support this, adjust your component to read any own property descriptors it can find on the instance and do the delete-set thing I mentioned above in the custom element constructor.
答案2
得分: 1
我认为你应该像这样更新 index.html 文件。
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
document.addEventListener("DOMContentLoaded", function() {
const counter2 = document.getElementById("counter1");
counter2.count = 23;
console.log("count", counter2.count);
console.log("_count", counter2._count);
counter2.addEventListener("count", (e) => console.log(e));
});
<!-- language: lang-html -->
<counter-wc-custom id="counter1"></counter-wc-custom>
<!-- end snippet -->
请注意,这是您提供的代码的翻译部分。
英文:
I think you should update the index.html like this.
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
document.addEventListener("DOMContentLoaded", function() {
const counter2 = document.getElementById("counter1");
counter2.count = 23;
console.log("count", counter2.count);
console.log("_count", counter2._count);
counter2.addEventListener("count", (e) => console.log(e));
});
<!-- language: lang-html -->
<counter-wc-custom id="counter1"></counter-wc-custom>
<!-- end snippet -->
答案3
得分: 1
问题与脚本执行的时间与自定义元素定义并连接到DOM的时间有关。
当您直接访问HTML文件中的元素时,脚本试图在其连接并准备好之前操纵自定义元素。
将您的脚本包装在DOMContentLoaded
事件侦听器中,以确保在尝试访问其属性之前自定义元素已被定义并连接到DOM。
document.addEventListener('DOMContentLoaded', () => {
const counter2 = document.getElementById("counter1");
counter2.count = 23;
alert(counter2.count);
alert(counter2._count);
console.log("count", counter2.count);
console.log("_count", counter2._count);
counter2.addEventListener("count", (e) => console.log(e));
});
英文:
The issue is related to the timing of when the script is being executed compared to when the custom element is defined and connected to the DOM.
When you directly access the element in the HTML file the script is trying to manipulate the custom element before it's connected and ready.
Wrap your script inside a DOMContentLoaded
event listener to make sure that the custom element has been defined and connected to the DOM before you try to access its properties.
document.addEventListener('DOMContentLoaded', () => {
const counter2 = document.getElementById("counter1");
counter2.count = 23;
alert(counter2.count);
alert(counter2._count);
console.log("count", counter2.count);
console.log("_count", counter2._count);
counter2.addEventListener("count", (e) => console.log(e));
});
答案4
得分: 0
Codesandbox 不太可靠以显示大部分日志(不确定原因),
我删除了一些样板代码(React 部分)并在 Stack Overflow 中创建了一个 snippet
,你可以看到你的代码正常运行 - 我删除了 _count
,因为它不是必要的(如果你不关心变量的隐私)
然而,你可以检查你的方法是否有效(set
和 get
的日志都在其中):
class CounterWebComponent extends HTMLElement {
static get observedAttributes() {
return ["count"];
}
constructor() {
console.log("CounterWebComponent");
super();
}
set count(value) {
console.log("CounterWebComponent set", value);
}
get count() {
console.log("CounterWebComponent get");
return this.getAttribute("count");
}
connectedCallback() {
console.log("CounterWebComponent connected");
this.innerHTML = `<h1>fff: ${this.count}</h1>`;
}
disconnectedCallback() {
console.log("CounterWebComponent disconnected");
}
}
if (!customElements.get("counter-wc-custom"))
customElements.define("counter-wc-custom", CounterWebComponent);
const counter = document.getElementById("counter1");
console.log(counter.count);
counter.count = 35;
<div id="app"></div>
<counter-wc-custom id="counter1" count="50"></counter-wc-custom>
英文:
Codesandbox isn't reliable for displaying most of logs (not sure why),
i removed some of the boilerplate(react part) and created a snippet
in SO, you can see your code works - i removed _count
as it is not needed (if you don't care about privacy of the variable)
however you can check, that your approach works (both set
and get
logs are there):
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
class CounterWebComponent extends HTMLElement {
static get observedAttributes() {
return ["count"];
}
constructor() {
console.log("CounterWebComponent");
super();
}
set count(value) {
console.log("CounterWebComponent set", value);
}
get count() {
console.log("CounterWebComponent get");
return this.getAttribute("count");
}
connectedCallback() {
console.log("CounterWebComponent connected");
this.innerHTML = `<h1>fff: ${this.count}</h1>`;
}
disconnectedCallback() {
console.log("CounterWebComponent disconnected");
}
}
if (!customElements.get("counter-wc-custom"))
customElements.define("counter-wc-custom", CounterWebComponent);
const counter = document.getElementById("counter1");
console.log(counter.count);
counter.count = 35;
<!-- language: lang-html -->
<div id="app"></div>
<counter-wc-custom id="counter1" count="50"></counter-wc-custom>
<!-- end snippet -->
答案5
得分: -4
以下是已翻译的内容:
看起来你正在尝试创建一个名为CounterWebComponent的Web组件并设置其count属性。但是,你的代码中存在一些需要解决的问题。
属性初始化:类的属性应该在构造函数内使用this进行初始化,而不是使用类字段语法。
事件分发:你尝试为“count”事件添加事件监听器,但实际上你的代码中没有分发任何这样的事件。
以下是你的代码的更新版本,应该按预期工作:
class CounterWebComponent extends HTMLElement {
constructor() {
super();
console.log("ctor");
this._count = 0; // 初始化count属性
// 监听count属性的变化
Object.defineProperty(this, 'count', {
get: () => {
console.log("CounterWebComponent get", this._count);
return this._count;
},
set: (value) => {
console.log("CounterWebComponent set", value);
this._count = value;
// 在count更新时分发自定义事件
const event = new CustomEvent('countUpdated', { detail: value });
this.dispatchEvent(event);
}
});
}
connectedCallback() {
console.log("CounterWebComponent connected");
this.innerHTML = "<h1>fff</h1>";
}
disconnectedCallback() {
console.log("CounterWebComponent disconnected");
}
}
if (!customElements.get("counter-wc-custom"))
customElements.define("counter-wc-custom", CounterWebComponent);
在你的HTML和脚本中:
<counter-wc-custom id="counter1"></counter-wc-custom>
<script>
const counter2 = document.getElementById("counter1");
counter2.count = 23;
// 监听自定义的“countUpdated”事件
counter2.addEventListener("countUpdated", (e) => {
console.log("countUpdated事件", e.detail);
});
</script>
通过在设置count属性时分发自定义的“countUpdated”事件,现在你可以监听此事件并相应处理。这个更新后的代码应该会给你想要的行为。
英文:
It looks like you're trying to create a web component named CounterWebComponent and set its count property. However, there are a couple of issues in your code that need to be addressed.
Property Initialization: The properties of the class should be initialized inside the constructor using this, and not using class field syntax.
Event Dispatching: You are trying to add an event listener for a "count" event, but you're not actually dispatching any such event in your code.
Here's an updated version of your code that should work as expected:
class CounterWebComponent extends HTMLElement {
constructor() {
super();
console.log("ctor");
this._count = 0; // Initialize the count property
// Listen to changes in the count property
Object.defineProperty(this, 'count', {
get: () => {
console.log("CounterWebComponent get", this._count);
return this._count;
},
set: (value) => {
console.log("CounterWebComponent set", value);
this._count = value;
// Dispatch a custom event when the count is updated
const event = new CustomEvent('countUpdated', { detail: value });
this.dispatchEvent(event);
}
});
}
connectedCallback() {
console.log("CounterWebComponent connected");
this.innerHTML = "<h1>fff</h1>";
}
disconnectedCallback() {
console.log("CounterWebComponent disconnected");
}
}
if (!customElements.get("counter-wc-custom"))
customElements.define("counter-wc-custom", CounterWebComponent);
In your HTML and script:
<counter-wc-custom id="counter1"></counter-wc-custom>
<script>
const counter2 = document.getElementById("counter1");
counter2.count = 23;
// Listen to the custom "countUpdated" event
counter2.addEventListener("countUpdated", (e) => {
console.log("countUpdated event", e.detail);
});
</script>
By dispatching the custom "countUpdated" event when the count property is set, you can now listen to this event and handle it accordingly. This updated code should give you the desired behavior.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论