haunted
haunted copied to clipboard
Type coercion and other property goodies
LitElement has a lot of flexibility in how properties are handled: https://lit-element.polymer-project.org/guide/properties
The following options are available:
- converter: Convert between properties and attributes.
- type: Use LitElement’s default attribute converter.
- attribute: Configure observed attributes.
- reflect: Configure reflected attributes.
- noAccessor: Whether to set up a default property accessor.
- hasChanged: Specify what constitutes a property change.
The only 2 features that I'm missing when using haunted are
- "type" to coerce attributes (String) to String, Number, Boolean, Array, and Object properties
- "reflect" to specify whether to reflect property updates back to the attribute
Of those 2, "type" is the most common need for me. I also think handling of default values for properties/attributes might be a bit awkward currently.
What might a good approach be for accomplishing some of the above in haunted?
For reference, here's a simple use case, maintaining state in a property instead of in useState(): https://codesandbox.io/s/z275vomrqp
Pinning this one so others see it. I have some thoughts on this but will be on vacation for the rest of the week. Plan to come back to it after that.
A little preview: I've been thinking something like React's propTypes might be appropriate for us. In fact, might prop-types even work? I have never tried using it in a non-React setting.
Reflecting I agree is a need, we can look into how to do that.
Using Reacts prop-types might be hard as they only offer validation. They are functions that doesn't reflect the information that you enter into them. You can't even call them directly:
Invariant Violation: Calling PropTypes validators directly is not supported by the `prop-types` package. Use `PropTypes.checkPropTypes()` to call them. Read more at http://fb.me/use-check-prop-types
Another issue is that I don't think you want the same composability and and flexibility for what will essentially have to be serializable to a string.
So I think it makes sense to provide a separate implementation.
Maybe something like this:
import { html } from 'https://unpkg.com/lit-html/lit-html.js'
import {
component,
stringProperty,
integerProperty,
enumProperty,
objectProperty
} from "https://unpkg.com/[email protected]/haunted.js";
const User = ({ name, age, type, meta }) => html`
<div data-version=${meta.version} class="user ${type.toLowerCase()}">${name} ${age}</div>
`;
User.attributes = {
name: stringProperty.isRequired,
age: integerProperty(0, 120),
type: enumProperty('ADMIN', 'END_USER')
}
User.properties = {
meta: objectProperty({
version: stringProperty.isRequired
}).isRequired
}
customElements.define("x-user", component(User));
Here the attributes only allow property descriptors that is string serializable.
It would be really great if you can iterate the properties and actually extract the information, that would allow creating property-based tests generating valid attributes and properties.
That not bad. Fwiw I'd like to make this a separate module to prevent extra bundle size for people not using it.
The thing that gives me pause is that it's unfortunate for every view library to have its own separate typed property support. Couldn't this be done as a class mixin that any WC library could use?
Just to revisit this brief, is the assumption that a defined property will be coerced to a type? For example if you define a number property and "11" is provided, this will be coerced to 11? If so, you could argue that it's better to throw. I'm not sure if this is how React behaves or not.
@matthewp React would issue a console.error if you violate the property type. I think it is a bit dangerous to throw, in React that would break the entire rendering tree in the current error boundary. But web-components might behave differently.
Throwing in development would definitely be a good idea though.
I thought type coercion was just for attributes -> properties. Not for properties themselves...
It could be for both, that's up to us to decide.
I kind of agree with @jpray on
I thought type coercion was just for attributes -> properties. Not for properties themselves...
I don't like the idea of type coercion, but it is a necessary evil for attributes as they can only be entered as strings.
It might be good if haunted has built-in type converter!
In my case, I always do some type convert for props like Number() / JSON.parse() .
If there is a interface like below without breaking original usage:
customElements.define('hello-app', component(App, {observedAttributes: [{
name: 'count',
type: Number
}]}));
and In the App:
function App(count){
console.log(typeof count); // number
}
Here is the lit-element's approach, they just do some type convertion before update the component.
I thought we can also do some interface preprocessing before observedAttributes and pre-convert attributeChangedCallback.