Una clase base para crear componentes web utilizando lit-html
lit-element se puede instalar a través del paquete NPM de elementos Lit-HTML.
lit-element le permite crear componentes web con plantillas HTML expresadas con literales de plantilla JavaScript, y de manera eficiente representa y vuelve a renderizar esas plantillas a DOM.
lit-element logra esto integrando Lit-HTML y tiene las siguientes características:
render()renderCallbackinvalidate()this o destrucirid en la raíz de la sombra usando this.$(...)Las demostraciones se pueden encontrar aquí.
Simple Escriba su código HTML usando lit-html creando un método render() .
import { LitElement , html } from '/src/lit-element.js' ;
class HelloWorld extends LitElement {
render ( ) {
return html `
< div style =" font-weight: bold " > Hello World </ div >
` ;
}
}
customElements . define ( 'hello-world' , HelloWorld ) < hello-world > </ hello-world >id Después de que el contenido se haya presentado la primera vez (es decir, después de los incendios connectedCallback() ), luego puede acceder a elementos en la raíz de la sombra por id usando this.$(...) .
En el siguiente ejemplo, this.changeColor() Cada vez que se presiona el botón, que en el resultado accede al DIV usando this.$("wrapper") y modifica su color de fondo.
class ColorMarker extends LitElement {
changeColor ( ) {
const color = Math . random ( ) . toString ( 16 ) . substr ( 2 , 6 ) ;
// Easily query the element by id:
this . $ ( "wrapper" ) . style . backgroundColor = `# ${ color } ` ;
}
render ( ) {
return html `
< style >
div {
background-color: yellow;
}
</ style >
< button on-click = ${ ( ) => this . changeColor ( ) } >
Change background color
</ button >
< div id =" wrapper " > < slot > </ slot > </ div >
` ;
}
}
customElements . define ( 'color-marker' , ColorMarker ) ; < color-marker > Horse </ color-marker > En este ejemplo usaremos propiedades. Cada propiedad definida en las properties() se asegurará de que el contenido se vuelva a renderizar en el momento adecuado cuando se modifique.
Las propiedades pueden tener valores predeterminados e incluso pueden reflejarse a través de atributos (los cambios van en ambos sentidos). En lugar de hacer magia y convertir casos después de reglas especiales como upper-case frente a upperCase , en su lugar, define un ejemplo a qué atributo debe reflejar la propiedad y, por lo tanto, evitar cualquier ambigüedad.
Tenga en cuenta que al usar propiedades, debe llamar this.withProperties antes de usar los elementos. A medida que el método devuelve la clase en sí, esto se puede hacer como parte de customElements.define(...)
Nota, los valores predeterminados de los atributos se establecen desde los atributos del elemento mismos (presentes o faltantes) y, por lo tanto, se ignoran los valores predeterminados establecidos a través de 'valor'.
import { LitElement , html } from '/src/lit-element.js' ;
class HelloWorld extends LitElement {
static get properties ( ) {
return {
uppercase : {
type : Boolean ,
attrName : "uppercase"
}
}
}
render ( ) {
return html `
< style >
.uppercase {
text-transform: uppercase;
}
</ style >
< div id =" box " class$ =" ${ this . uppercase ? 'uppercase' : '' } " >
< slot > Hello World </ slot >
</ div >
` ;
}
}
customElements . define ( 'hello-world' , HelloWorld . withProperties ( ) ) ; < hello-world > </ hello-world >
< hello-world uppercase > ¡Hola, mundo! </ hello-world > Al crear elementos personalizados, un buen patrón es usar atributos en lugar de métodos o propiedades. Esto permite usar el elemento declarativamente como <my-dialog opened> .
Para elementos personalizados solo consumidos internamente en otros elementos personalizados, a menudo es más rápido que depende de las propiedades. Este es también el caso si necesita pasar datos complejos como matrices u objetos.
Para facilitar el trabajo con atributos, lit-html-element admite el mapeo entre atributos y propiedades automáticamente, solo definiendo el nombre del atributo con el que debe asignar la propiedad a través de attrName: .
La presencia de atributos o no (en elementos) da como resultado valores reales , es decir. Un atributo faltante para una propiedad booleana significa que la propiedad será false y para todos los demás tipos de propiedades, undefined . Esto significa que al asignar propiedades a los atributos, no existe un valor predeterminado, ya que los valores siempre se definen dependiendo de la presencia o no de los atributos. Esto significa que value: se ignora cuando attrName: está presente.
Los valores se convierten usando sus constructores de tipo, es decir, String(attributeValue) para String , Number(attributeValue) para Number , etc.
Boolean tiene un manejo especial para seguir los patrones de la plataforma web.
Del estándar HTML:
La presencia de un atributo booleano en un elemento representa el valor verdadero, y la ausencia del atributo representa el valor falso.
Si el atributo está presente, su valor debe ser la cadena vacía o un valor que es una coincidencia insensible al caso ASCII para el nombre canónico del atributo, sin espacio en blanco o finding o find.
Array y Object están desencadenados para los atributos y no tienen un manejo especial, por lo que los valores se convierten utilizando sus constructores como cualquier otro tipo de valor, excepto booleano.
this se pasa a Render () para usted, que es más limpio. particularmente cuando destruye. Sin embargo, aún puede hacer referencia a ellos manualmente.
class RenderShorthand extends LitElement {
static get properties ( ) {
return {
greeting : {
type : String ,
value : "Hello"
}
}
}
render ( { greeting } ) {
return html ` ${ greeting } World!` ;
}
}
customElements . define ( 'render-shorthand' , RenderShorthand . withProperties ( ) ) ; Cuando cualquiera de las propiedades en properties() cambia, lit-element volverá a renderizar automáticamente. Lo mismo ocurre con los atributos que se asignan a las propiedades a través de attrName .
Si necesita volver a renderizar manualmente, puede activar un re-renderizar a través de una llamada para invalidate() . Esto programará una microtask que representará el contenido justo antes de la próxima requestAnimationFrame .
Los elementos personalizados deben actualizarse antes de trabajar. Esto ocurre automáticamente por el navegador cuando tiene todos los recursos que necesita.
Esto significa que si realiza un elemento personalizado que depende de otros elementos personalizados y usa propiedades para el flujo de datos, entonces establecer esas propiedades antes de que se actualice el elemento, significa que terminará siguiendo las propiedades lit-html-element , lo que significa que las actualizaciones de la propiedad y la reflexión de los atributos no funcionarán como se espera.
Hay una API whenAllDefined(result, container) para trabajar en este problema, al permitir esperar hasta que se hayan actualizado todas las dependencias. Una forma de usarlo es sobrescribir el renderCallback() ::
renderCallback ( ) {
if ( "resolved" in this ) {
super . renderCallback ( ) ;
} else {
whenAllDefined ( this . render ( this ) ) . then ( ( ) => {
this . resolved = true ;
this . renderCallback ( ) ;
} ) ;
}
}Pero aún puede lograr las propiedades de sombra si manual establece valores antes de actualizarse como
document . getElementById ( 'ninja' ) . firstName = "Ninja" ;Así que guarda estos de la siguiente manera:
customElements . whenDefined ( 'computed-world' ) . then ( ( ) => {
document . getElementById ( 'ninja' ) . firstName = "Ninja" ;
} ) ;Si necesita algunas propiedades calculadas y actualizaciones dependiendo de otras propiedades, eso es posible utilizando el valor 'calculado', que definió un método de objeto con argumentos como una cadena.
Las propiedades calculadas solo se actualizan cuando se definen todas las propiedades dependientes . El valor predeterminado se puede establecer usando value:
Nota, las propiedades calculadas no pueden reflejarse en los atributos.
P.ej.
import { LitElement , html } from '/node_modules/lit-html-element/lit-element.js' ;
class ComputedWorld extends LitElement {
static get properties ( ) {
return {
firstName : {
type : String ,
attrName : "first-name"
} ,
doubleMessage : {
type : String ,
computed : 'computeDoubleMessage(message)'
} ,
message : {
type : String ,
computed : 'computeMessage(firstName)' ,
value : 'Hej Verden'
}
}
}
computeDoubleMessage ( message ) {
return message + " " + message ;
}
computeMessage ( firstName ) {
return `Konichiwa ${ firstName } ` ;
}
render ( ) {
return html `
< div style =" font-weight: bold " > ${ this . doubleMessage } </ div >
` ;
}
}
customElements . define ( 'computed-world' , ComputedWorld . withProperties ( ) ) < computed-world > </ computed-world >
< computed-world first-name =" Kenneth " > </ computed-world > Es posible usar lit-html-element de TypeScript en lugar de JavaScript. Al usar TypeScript, puede optar por usar decoradores en lugar de definir el accesor de propiedades estáticas static get properties() .
Cuando use decoradores de propiedades, se ignorará cualquier accesor de propiedad estática, y tampoco necesita llamar a .withProperties() .
import {
LitElement ,
html ,
TemplateResult ,
customElement ,
property ,
attribute ,
computed
} from '../../src/lit-element.js' ;
@ customElement ( 'test-element' )
export class TestElement extends LitElement {
@ computed ( 'firstName' , 'lastName' )
get fullName ( ) : string {
return ` ${ this . firstName } ${ this . lastName } ` ;
}
@ property ( ) firstName : string = 'John' ;
@ property ( ) lastName : string = 'Doe' ;
@ property ( ) human : boolean = true ;
@ property ( ) favorite : any = { fruit : 'pineapple' } ;
@ property ( ) kids : Array < string > = [ 'Peter' , 'Anna' ] ;
@ attribute ( 'mother' ) mother : string ;
@ attribute ( 'super-star' ) superStar : boolean ;
render ( ) : TemplateResult {
return html `
< h2 > Name: ${ this . fullName } </ h2 >
< h2 > Is human?: ${ human ? "yup" : "nope" } </ h2 >
< h2 > Favorites: ${ JSON . stringify ( this . favorite ) } </ h2 >
< h2 > Kids: ${ JSON . stringify ( this . kids ) } </ h2 >
< h2 > Mother: ' ${ this . mother } ' </ h2 >
< h2 > Superstar?: ' ${ this . superStar } ' </ h2 >
` ;
}
} < test-element super-star mother =" Jennifer " > </ test-element > Para usar los decoradores de TypeScript, debe habilitar la configuración del compilador experimentalDecorators en su tsconfig.json o usar el indicador --experimentalDecorators .
{
"compilerOptions" : {
"experimentalDecorators" : true
}
}Con el anterior habilitado, puede comenzar a usar decoradores pero debe especificar la información de tipo manualmente:
@ property ( { type : String } )
myProperty: string ;Como el tipo a menudo puede derivarse de la propiedad, especialmente en TypeScript donde define el tipo, esto se siente como un poco de trabajo doble. Afortunadamente, hay una nueva propuesta de especificación llamada reflexión de metadatos que tiene como objetivo resolver este problema. Esta propuesta aún no se ha propuesto formalmente al grupo de trabajo TC39 (define el estándar de JavaScript) pero ya hay un soporte de polyfill disponible y experimental en TypeScript.
Con la reflexión de metadatos habilitados, es posible definir los tipos de propiedades de manera más concisa:
@ property ( ) myProperty: string ;Para usar los decoradores de TypeScript, siga los siguientes pasos.
emitDecoratorMetadata en su tsconfig.json o usar el indicador --emitDecoratorMetadata . {
"compilerOptions" : {
"emitDecoratorMetadata" : true
}
}$ npm install --save-dev rbuckton/reflect-metadata < script src =" /node_modules/reflect-metadata/Reflect.js " > </ script >La siguiente documentación de la API utiliza IDL web.
PropertyOptions PropertyOptions se utilizan para configurar las propiedades para el elemento personalizado. En JavaScript, debe implementar un accesor de propiedad estática llamado properties , que devuelve un objeto donde cada propiedad de ese objeto tiene una PropertyOptions asociada:
class {
static get properties ( ) {
return { selfDefinedObjectProperty : ... }
}
} El Diccionario PropertyOptions tiene 4 propiedades opcionales, que se muestran a continuación en formato IDL web.
typedef (BooleanConstructor or DateConstructor or NumberConstructor or StringConstructor or ArrayConstructor or ObjectConstructor) PropertyType ;
dictionary PropertyOptions {
attribute PropertyType type ;
attribute any value ;
attribute USVString attrName ;
attribute USVString computed ;
}type La propiedad type solo es opcional cuando se usa decoradores y reflexión de metadatos.
value La propiedad value define un valor predeterminado para la propiedad. En el caso de la asignación de atributos / propiedades a través de attrName (ver más abajo), se ignora value . Al usar los decoradores, el valor está tomando de la definición de la propiedad en sí:
@ property ( ) myProperty: string = "Hello World" ; attrName El attrName define el nombre del atributo que debe reflejarse con la propiedad y al revés. Con attrName , los valores predeterminados se ignoran y determinan desde el elemento personalizado en su lugar, es decir. dependiendo de la presencia o no de los atributos.
El nombre del atributo, mucho en letras latinas (AZ) que incluye '-' (guión). Todos los atributos en los elementos HTML en los documentos HTML obtienen ASCII-libercase automáticamente, y se ignora el guión inicial ('-').
Tenga en cuenta que los atributos de datos, es decir. Los atributos que comienzan con data- son accesibles como propiedades automáticamente a través de element.dataset .
Cuando las propiedades asignadas se establecen en el elemento, el atributo se actualiza con la representación de cadena del nuevo valor, a menos que el nuevo valor esté undefined en el que se elimina el atributo.
Hay una excepción a esto, como las propiedades booleanas como se reflejan de manera diferente. Configuración de la propiedad en true y el atributo (digamos attr ) se establece en la cadena vacía '' (el atributo de significado está presente, es decir, <div attr> ). Establecer la propiedad en false y el atributo se elimina, es decir. <div> .
Cuando se establecen los atributos, los valores se convierten utilizando sus constructores de tipo, es decir, String(attributeValue) para String , Number(attributeValue) para Number , etc.
Boolean tiene un manejo especial para seguir los patrones de la plataforma web.
Del estándar HTML:
La presencia de un atributo booleano en un elemento representa el valor verdadero, y la ausencia del atributo representa el valor falso.
Si el atributo está presente, su valor debe ser la cadena vacía o un valor que es una coincidencia insensible al caso ASCII para el nombre canónico del atributo, sin espacio en blanco o finding o find.
Lea más en la sección de reflexión de atributos arriba.
computed Las propiedades se pueden calcular a partir de otras propiedades utilizando computed , se requiere una cadena como 'methodName(property1, property2)' , donde se definen un methodName en el elemento y property1 y property2 .
Las propiedades calculadas solo se actualizan cuando se definen todas las propiedades dependientes . El valor predeterminado se puede establecer usando value:
Nota, las propiedades calculadas no pueden reflejarse en los atributos.
renderCallback renderCallback permite ganchos personalizados antes y después de la representación.
Si necesita hacer un trabajo adicional antes de renderizar, al igual que establecer una propiedad basada en otra propiedad, una subclase puede anular renderCallback() para hacer el trabajo antes o después de la clase base llama render() , incluida la configuración de la propiedad dependiente antes de render() .
withProperties()HACER:
render(HTMLElement this)TODO: Mover documentos aquí
async invalidate()TODO: Mover documentos aquí
$(DOMString id)TODO: Mover documentos aquí
whenAllDefined(TemplateResult result)TODO: Mover documentos aquí
@customElement(USVString tagname)Un decorador de clase para registrar el elemento personalizado
@ customElement ( 'my-element' )
class extends HTMLElement {
...
}@property(optional PropertyOptions options) Un decorador de propiedades para engancharse en el sistema de propiedades lit-html-element .
Cuando usa el decorador de propiedades, no necesita definir el accesor de propiedades estáticas static get properties() .
Cuando use decoradores de propiedades, se ignorará cualquier accesor de propiedad estática, y tampoco necesita llamar a .withProperties() .
@ property ( { type : String } )
myProperty: string ;Consulte las extensiones para obtener más información para obtener más información.
@attribute(USVString attrName) Un decorador de propiedades para engancharse en el sistema de propiedades lit-html-element y asociar una propiedad con un atributo de elemento personalizado.
Consulte la propiedad attrName para obtener más información.
@computed(any dependency1, any dependency2, ...) Un decorador de propiedades para engancharse en el sistema de propiedades lit-html-element y crear una propiedad computada automáticamente de otras propiedades.
Consulte la propiedad computed para obtener más información.
@listen(USVString eventName, (USVString or EventTarget) target) Un decorador de métodos para agregar un oyente de eventos. Puede usar una cadena para Target y buscará un elemento en el shadowroot con esa id .
Los oyentes de eventos se agregan después de la primera representación, lo que crea el DOM de Shadow.