admin管理员组文章数量:1355532
I'm trying to integrate a vanilla js web component in Vue. The code is super contrived and just renders a name and surname as a proof of concept.
class HelloWorld extends HTMLElement {
static observedAttributes = ["name", "surname"];
constructor() {
super();
}
connectedCallback() {
const
shadow = this.attachShadow({ mode: 'closed' }),
hwMsg = `Hello ${ this.name } ${ this.surname }`;
shadow.append(hwMsg);
}
// attribute change
attributeChangedCallback(property, oldValue, newValue) {
console.log('property ', property, ' changed from ', oldValue, ' to ' newValue);
if (oldValue === newValue) return;
this[ property ] = newValue;
}
}
customElements.define('hello-world', HelloWorld);
I'm trying to integrate a vanilla js web component in Vue. The code is super contrived and just renders a name and surname as a proof of concept.
class HelloWorld extends HTMLElement {
static observedAttributes = ["name", "surname"];
constructor() {
super();
}
connectedCallback() {
const
shadow = this.attachShadow({ mode: 'closed' }),
hwMsg = `Hello ${ this.name } ${ this.surname }`;
shadow.append(hwMsg);
}
// attribute change
attributeChangedCallback(property, oldValue, newValue) {
console.log('property ', property, ' changed from ', oldValue, ' to ' newValue);
if (oldValue === newValue) return;
this[ property ] = newValue;
}
}
customElements.define('hello-world', HelloWorld);
I registered the hello-world tag as custom in my vite.config file:
export default defineConfig({
plugins: [
vue({
template: {
compilerOptions: {
isCustomElement: (tag) => tag.includes('hello-world')
}
}
})
...]
I am including the webcomponent in the component where I want to use it:
<script setup lang="ts">
import '@/web_components/prova_web';
import { ref } from 'vue';
const name = ref('John');
const surname = ref('Smith');
</script>
<template>
<main class="main-view">
<form>
<input id="model_name" v-model="name"/>
<input id="model_surname" v-model="surname">
</form>
Current values: {{ name }} {{ surname }} <br/>
<hello-world :name="name" :surname="surname"></hello-world>
</main>
</template>
<style scoped>
</style>
<script src="https://cdnjs.cloudflare/ajax/libs/vue/3.5.4/vue.global.min.js"></script>
The code renders just fine the first time, and the attributeChangedCallback
is correctly called for both properties:
console log
Unfortunately, when I change a property using the form inputs, the attributeChangedCallback
is not firing again, even though the property values have changed (and properly re-rendered above the custom tag).
Screenshot
It's weird that the first time the ref value is correctly unpacked but subsequent changes to the refs' values are not recognized as such. Can I achieve reactive property binding with plain javascript or am I forced to use a framework? (Lit seems very popular).
Share Improve this question asked Mar 28 at 9:47 Fiorenza OppiciFiorenza Oppici 133 bronze badges2 Answers
Reset to default 0There are two different issues in your code here.
The first seems the be with template literals:
const hwMsg = `Hello ${ this.name } ${ this.surname }`;
This line will evaluate this.name
and this.surname
and create the string with it - once.
I.e., hwMsg
is just a simple string after that and there is no reactivity involved here. So since this line is part of the connectedCallback
it is only executed once when attaching the custom element. Thus, updating this.name
or this.surname
won't have any effect.
For attribute changes to be reflected in the text content you need to update the content of the element in the attributeChangedCallback
.
Secondly, since you are setting this.name
and this.surname
in your attributeChangedCallback
your custom element will also have the object properties name
and surname
which are independent from the element attributes.
With the reactive binding
<hello-world :name="name" :surname="surname"></hello-world>
vue
first checks if there are properties with the same name (name
/surname
) - which there are in this case - and binds to them instead of the attributes of the same name.
See also What is the difference between properties and attributes in HTML?
So the reactive binding will only change the value of the properties and thus not trigger a call of the attributeChangedCallback
.
A working implementation of your example could thus be
class HelloWorld extends HTMLElement {
static observedAttributes = ['name', 'surname'];
#shadowRoot;
constructor() {
super();
this.#shadowRoot = this.attachShadow({
mode: 'closed',
});
}
attributeChangedCallback(attribute, oldValue, newValue) {
console.log( 'attribute ', attribute, ' changed from ', oldValue, ' to ', newValue );
if (oldValue === newValue) return;
const hwMsg = `Hello ${this.getAttribute('name')} ${this.getAttribute('surname')}`;
this.#shadowRoot.replaceChildren(hwMsg);
}
}
This will
- update the text every time an attribute is changed
- not create the
name
/surname
properties sovue
will reactively update the attributes so the callback is triggered
It worked the first time because your connectedCallback
set the whole string.
Native Web Components are not reactive like you are used to with Frameworks and Libraries;
you have to do the getter/setter part yourself.
That is why developers complain Native development is verbose.
But once you have these getters/setters it saves you from Framework bloat.
This Native Web Component will work in any setting, not just Vue
<script>
customElements.define("hello-world", class extends HTMLElement {
#name = "" // private properties point to <span>
#surname = ""
static observedAttributes = ["name", "surname"]
attributeChangedCallback(attrname, oldValue, newValue) {
this[attrname] = newValue; // matching methodname
}
constructor() {
// the most helpful helper function:
const createElement = (tag, props = {}) => Object.assign(document.createElement(tag), props)
super() // sets AND returns 'this' scope
.attachShadow({ mode: "closed" }) // returns this.shadowRoot (mode:"open" would also set this.shadowRoot)
.append( // fet appendChild! (unless you need the single! return value)
"Hello ",
(this.#name = createElement("span")),
" ",
(this.#surname = createElement("span")),
);
this.onclick = (evt) => (this.name="Danny", this.surname="Engelman");
}
get name() { return this.#name.innerText }
set name(str) { this.#name.innerText = str }
get surname() { return this.#surname.innerText }
set surname(str) { this.#surname.innerText = str }
},
)
</script>
<hello-world name="Fiorenza" surname="Oppici"></hello-world>
If you only want code for VUE, then this should be enough.
Since you are overwriting all of shadowRoot with a String, a closed shadowRoot has no meaning An "open" shadowRoot sets this.shadowRoot
for free
class HelloWorld extends HTMLElement {
static observedAttributes = ['name', 'surname'];
attributeChangedCallback(attribute, oldValue, newValue) {
(this.shadowRoot || this.attachShadow({mode:"open"})
.innerHTML = `Hello ${this.getAttribute('name')} ${this.getAttribute('surname')}`;
}
}
本文标签: javascriptintegrating native web components in Vue properties are not reactiveStack Overflow
版权声明:本文标题:javascript - integrating native web components in Vue: properties are not reactive - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1744045327a2581415.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论