React and Vue components execute within a controlled environment using their respective runtime. Web components cannot have same level of sanitized environment. The render cycle is perfectly defined for React/Vue/Angular/etc. Web component can be initialized in variety of ways:
// Using custom registered tag
const myComp = document.createElement('my-custom');
// Using constructor
const myComp = new MyCustom();
// Initialization using plain html
<div>
<my-comp>
</div>
So, lifecycle is not easily not controllable. Let us imagine you have a web component with a prop - myRequiredProp
. And, it is being initialized as:
// Pass prop at the time of initialization
const myComp1 = new MyCustom({ myRequiredProp: 10 });
// Pass prop at a later time.
const myComp2 = new MyCustom();
// ... after some time
myComp2.myRequiredProp = 10;
// And how to pass props if component is being used directly in HTML.
// Is this right way? What if `my-required-prop` should be object and not a primitive like number or string?
<div>
<my-comp my-required-prop="10" />
</div>
Plus, each framework which is using your component can have its own way of initializing props. At what point should the validation of prop be triggered. What if component is just initialized and never rendered. These are not easy decisions to make to address every scenario in case of web components. Thus, it is better to leave such concern outside of the core. The lit-element
intentionally avoids such open-ended decisions.
Having said that you can build your own validation system for props. You can govern when the prop validation should happen. For example, you can validate the prop at render
time.
export class MyElement extends LitElement {
@property({})
count = 0;
@property()
name = 'World';
validateAllProps() {
// Your validation logic.
// Use joi or zod to validation `this`.
return true;
}
render() {
// Trigger validation here
if (!this.validateAllProps) {
console.warn('Invalid props');
}
return html`
<h1>Hello, ${this.name}!</h1>
<button @click=${this._onClick}>
Click Count: ${this.count}
</button>
`;
}
}
Or you can also validate the prop using custom setter or using converter function.
export class MyElement extends LitElement {
#name = 'World';
set name(name) {
if (typeof name !== 'string') {
console.warn('Invalid prop name for MyElement');
}
this.#name = name;
}
render() {
return html`
<h1>Hello, ${this.name}!</h1>
`;
}
}
You should be able to generalize the validation behavior using a common base class that extends LitElement or employ custom decorator to manage the property validation.
I agree that LitElement
can feel awkward compared to React and Vue but in general it has far too many concerns to deal with which React simply doesn't have to. It gets complicated easily.