Uma classe base para criar componentes da web usando lit-html
lit-element pode ser instalado no pacote npm de elementos lit-html.
lit-element permite criar componentes da Web com modelos HTML expressos com literais de modelo JavaScript e renderizar e renderizar com eficiência esses modelos para o DOM.
lit-element realiza isso integrando o lit-html e possui os seguintes recursos:
render()renderCallback pré/pósinvalidate()this ou destruiçãoid na raiz das sombras usando this.$(...)As demos podem ser encontradas aqui.
Escreva seu código HTML simples usando lit-html criando um 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 Depois que o conteúdo foi renderizado pela primeira vez (ou seja, depois do connectedCallback() incêndios), você pode acessar elementos na raiz das sombras por id usando this.$(...) .
No exemplo abaixo, chamamos this.changeColor() sempre que o botão é pressionado, que em resultado acessa o div usando this.$("wrapper") e modifica sua cor de fundo.
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 > Neste exemplo, usaremos propriedades. Todas as propriedades definidas no estático getter properties() garantirão que o conteúdo seja renderizado novamente no momento certo quando modificado.
As propriedades podem ter valores padrão e podem até ser refletidos por meio de atributos (as alterações vão nos dois sentidos). Em vez de fazer magia e converter casos após regras especiais como upper-case versus upperCase , você define o exemplo de qual atributo o nome da propriedade deve refletir e, assim, evitar qualquer ambiguidade.
Observe que, ao usar propriedades, você deve chamar this.withProperties antes de usar os elementos. À medida que o método retorna a classe em si, isso pode ser feito como parte dos customElements.define(...)
Observe que os valores padrão dos atributos são definidos a partir dos próprios atributos do elemento (presente ou ausente) e, portanto, os valores padrão definidos via 'valor' são ignorados.
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 > Ao criar elementos personalizados, um bom padrão é usar atributos em vez de métodos ou propriedades. Isso permite usar o elemento declarativamente como <my-dialog opened> .
Para elementos personalizados consumidos apenas internamente em outros elementos personalizados, geralmente é mais rápido apenas confiando nas propriedades. Este também é o caso se você precisar transmitir dados complexos, como matrizes ou objetos.
Para facilitar o trabalho com atributos, lit-html-element suporta o mapeamento entre atributos e propriedades automaticamente, apenas definindo o nome do atributo que a propriedade deve mapear via attrName: .
A presença de atributos ou não (em elementos) resulta em valores reais , ou seja. Um atributo ausente para uma propriedade booleana, significa que a propriedade será false e, para todos os outros tipos de propriedade, undefined . Isso significa que, ao mapear propriedades para atributos, não existe um valor padrão como os valores sempre são definidos, dependendo da presença, ou não, dos atributos. Isso significa que a definição value: é ignorada quando attrName: está presente.
Os valores são convertidos usando seus construtores de tipo, isto é, String(attributeValue) para String , Number(attributeValue) para Number , etc.
Boolean possui um manuseio especial para seguir os padrões da plataforma da web.
Do padrão HTML:
A presença de um atributo booleano em um elemento representa o valor verdadeiro, e a ausência do atributo representa o valor falso.
Se o atributo estiver presente, seu valor deve ser a string vazia ou um valor que seja uma correspondência insensível ao caso ASCII para o nome canônico do atributo, sem espaço em branco de liderança ou à direita.
Array e Object são desanimados por atributos e não possuem manuseio especial, portanto, os valores são convertidos usando seus construtores como outros tipos de valor, exceto booleanos.
this é passado para renderizar () para você, o que é mais limpo. particularmente quando destruir. Você ainda pode fazer referência a eles manualmente, no entanto.
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 ( ) ) ; Quando qualquer uma das propriedades no properties() muda, lit-element será renderizado automaticamente. O mesmo vale para atributos que são mapeados para propriedades via attrName .
Se você precisar renderizar novamente manualmente, poderá acionar uma renderização por meio de uma chamada para invalidate() . Isso agendará um microtask que renderizará o conteúdo pouco antes do próximo requestAnimationFrame .
Os elementos personalizados precisam ser atualizados antes de trabalharem. Isso acontece automaticamente pelo navegador quando possui todos os recursos necessários.
Isso significa que, se você fizer um elemento personalizado que depende de outros elementos personalizados e use propriedades para o fluxo de dados, definir essas propriedades antes da atualização do elemento, significa que você acabará com o sombreamento das propriedades lit-html-element , o que significa que a propriedade atualiza e atribui a reflexão não funcionará como esperado.
Há uma API whenAllDefined(result, container) para trabalhar em torno desse problema, permitindo esperar até que todas as dependências tenham sido atualizadas. Uma maneira de usá -lo é substituir o renderCallback() :
renderCallback ( ) {
if ( "resolved" in this ) {
super . renderCallback ( ) ;
} else {
whenAllDefined ( this . render ( this ) ) . then ( ( ) => {
this . resolved = true ;
this . renderCallback ( ) ;
} ) ;
}
}Mas você ainda pode conseguir sombrear as propriedades se você definir valores manuais antes de atualizar como
document . getElementById ( 'ninja' ) . firstName = "Ninja" ;Portanto, proteja isso da seguinte maneira:
customElements . whenDefined ( 'computed-world' ) . then ( ( ) => {
document . getElementById ( 'ninja' ) . firstName = "Ninja" ;
} ) ;Se você precisar de algumas propriedades calculadas e atualizações, dependendo de outras propriedades, isso é possível usando o valor 'calculado', que definiu um método de objeto com argumentos como uma string.
As propriedades computadas são atualizadas apenas quando todas as propriedades dependentes são definidas . O valor padrão pode ser definido usando value:
Observe que as propriedades computadas não podem ser refletidas nos atributos.
Por exemplo.
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 > É possível usar lit-html-element do TypeScript em vez de JavaScript. Ao usar o TypeScript, você pode optar por usar decoradores em vez de definir as propriedades estáticas, acessador static get properties() .
Ao usar os decoradores de propriedades, qualquer acessador de propriedade estático será ignorado e você também não precisa ligar para .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 os decoradores do TypeScript, você precisa ativar a configuração do experimentalDecorators Compiler em seu tsconfig.json ou use o sinalizador --experimentalDecorators .
{
"compilerOptions" : {
"experimentalDecorators" : true
}
}Com o acima ativado, você pode começar a usar decoradores, mas deve especificar as informações de tipo manualmente:
@ property ( { type : String } )
myProperty: string ;Como o tipo geralmente pode ser deriva da propriedade, especialmente no TypeScript, onde você define o tipo, isso parece um pouco de trabalho duplo. Felizmente, existe uma nova proposta de especificação chamada reflexão de metadados que visa resolver esse problema. Esta proposta ainda não foi proposta formalmente ao grupo de trabalho do TC39 (define o padrão JavaScript), mas já existe um poli -preenchimento de trabalho disponível e suporte experimental no TypeScript.
Com a reflexão de metadados permitida, é possível definir os tipos de propriedades de maneira mais concisa:
@ property ( ) myProperty: string ;Para usar os decoradores do TypeScript, siga as etapas a seguir.
emitDecoratorMetadata no seu tsconfig.json ou usar o sinalizador --emitDecoratorMetadata . {
"compilerOptions" : {
"emitDecoratorMetadata" : true
}
}$ npm install --save-dev rbuckton/reflect-metadata < script src =" /node_modules/reflect-metadata/Reflect.js " > </ script >A documentação da API a seguir usa a Web IDL.
PropertyOptions O PropertyOptions é usado para configurar as propriedades para o elemento personalizado. No JavaScript, você precisa implementar um acessador de propriedade estático chamado properties , que retorna um objeto em que cada propriedade desse objeto possui uma PropertyOptions associada:
class {
static get properties ( ) {
return { selfDefinedObjectProperty : ... }
}
} O Dicionário PropertyOptions possui 4 propriedades opcionais, mostradas abaixo no formato da Web IDL.
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 A propriedade type é opcional apenas ao usar decoradores e reflexões de metadados.
value A propriedade value define um valor padrão para a propriedade. Em caso de mapeamento de atributos / propriedade via attrName (veja abaixo), value é ignorado. Ao usar decoradores, o valor está retirando da própria definição da propriedade:
@ property ( ) myProperty: string = "Hello World" ; attrName O attrName define o nome do atributo que deve ser refletido com a propriedade e o contrário. Com attrName , os valores padrão são ignorados e determinados a partir do elemento personalizado, isto é. dependendo da presença ou não dos atributos.
O nome do atributo, estar em cartas latinas (AZ), incluindo '-' (hífen). Todos os atributos nos elementos HTML nos documentos HTML são baseados automaticamente com ASCII-Lower, e o hífen inicial ('-') é ignorado.
Esteja ciente de que os atributos de dados, ou seja. Os atributos começando com data- são acessíveis como propriedades automaticamente via element.dataset .
Quando as propriedades mapeadas são definidas no elemento, o atributo é atualizado com a representação da string do novo valor, a menos que o novo valor seja undefined no qual o atributo seja removido.
Há uma exceção a isso, como propriedades booleanas refletidas de maneira diferente. Definir a propriedade como true e o atributo (digamos attr ) é definido como a string vazia '' (o atributo significa está presente, ou seja, <div attr> ). Definir a propriedade como false e o atributo é removido, ou seja. <div> .
Quando os atributos são definidos, os valores são convertidos usando seus construtores de tipos, isto é, String(attributeValue) para String , Number(attributeValue) para Number , etc.
Boolean possui um manuseio especial para seguir os padrões da plataforma da web.
Do padrão HTML:
A presença de um atributo booleano em um elemento representa o valor verdadeiro, e a ausência do atributo representa o valor falso.
Se o atributo estiver presente, seu valor deve ser a string vazia ou um valor que seja uma correspondência insensível ao caso ASCII para o nome canônico do atributo, sem espaço em branco de liderança ou à direita.
Leia mais na seção de reflexão do atributo acima.
computed As propriedades podem ser calculadas a partir de outras propriedades usando computed , é necessário uma string como 'methodName(property1, property2)' , onde methodName é um método no elemento e property1 e property2 são definidos.
As propriedades computadas são atualizadas apenas quando todas as propriedades dependentes são definidas . O valor padrão pode ser definido usando value:
Observe que as propriedades computadas não podem ser refletidas nos atributos.
renderCallback O renderCallback permite ganchos personalizados antes e depois da renderização.
Se você precisar fazer um trabalho extra antes de renderizar, como definir uma propriedade com base em outra propriedade, uma subclasse pode substituir renderCallback() para fazer o trabalho antes ou depois da classe base render() , incluindo a definição da propriedade dependente antes de render() .
withProperties()PENDÊNCIA:
render(HTMLElement this)TODO: mover documentos aqui
async invalidate()TODO: mover documentos aqui
$(DOMString id)TODO: mover documentos aqui
whenAllDefined(TemplateResult result)TODO: mover documentos aqui
@customElement(USVString tagname)Um decorador de classe para registrar o elemento personalizado
@ customElement ( 'my-element' )
class extends HTMLElement {
...
}@property(optional PropertyOptions options) Um decorador de propriedades para conectar o sistema de propriedades lit-html-element .
Ao usar o decorador de propriedades, você não precisa definir o acessador de propriedades estáticas static get properties() .
Ao usar decoradores de propriedades, qualquer acessador de propriedade estático será ignorado e você também não precisará ligar para .withProperties() .
@ property ( { type : String } )
myProperty: string ;Verifique as extensões do TypeScript para obter mais informações.
@attribute(USVString attrName) Um decorador de propriedades para conectar o sistema de propriedades de lit-html-element e associar uma propriedade a um atributo de elemento personalizado.
Verifique a propriedade attrName para obter mais informações.
@computed(any dependency1, any dependency2, ...) Um decorador de propriedades para conectar-se ao sistema de propriedades de lit-html-element e criar uma propriedade computada automaticamente a partir de outras propriedades.
Verifique a propriedade computed para obter mais informações.
@listen(USVString eventName, (USVString or EventTarget) target) Um decorador de método para adicionar um ouvinte de eventos. Você pode usar uma string para o Target e ela procurará um elemento na raiz de sombra com esse id .
Os ouvintes de eventos são adicionados após a primeira renderização, o que cria a sombra DOM.