Lit-Element快速入门
Lit Elemnet 是一个简单的web-component工具库
模板语法
基础使用
import { LitElement, html } from 'lit-element';
class MyElement extends LitElement {
render() {
return html`<p>template content</p>`;
}
}
动态属性
- 文本:
<p>${...}</p> - 基础属性:
id="${...}" - 逻辑属性:
?disabled="${...}" - 元素属性:
.value="${...}" - 事件处理:
@click="${...}"
例子:
import { LitElement, html } from 'lit-element';
class MyElement extends LitElement {
static get properties() {
return {
prop1: String,
prop2: String,
prop3: Boolean,
prop4: String
};
}
constructor() {
super();
this.prop1 = 'text binding';
this.prop2 = 'mydiv';
this.prop3 = true;
this.prop4 = 'pie';
}
render() {
return html`
<!-- 文本 -->
<div>${this.prop1}</div>
<!-- 基础属性 -->
<div id="${this.prop2}">attribute binding</div>
<!-- 逻辑属性 -->
<div>
boolean attribute binding
<input type="text" ?disabled="${this.prop3}"/>
</div>
<!-- 元素属性 -->
<div>
property binding
<input type="text" .value="${this.prop4}"/>
</div>
<!-- 事件 -->
<div>event handler binding
<button @click="${this.clickHandler}">click</button>
</div>
`;
}
clickHandler(e) {
console.log(e.target);
}
}
customElements.define('my-element', MyElement);
模板逻辑
类似react:
循环
html`<ul>
${this.myArray.map(i => html`<li>${i}</li>`)}
</ul>`;
三目运算
html`
${this.myBool?
html`<p>Render some HTML if myBool is true</p>`:
html`<p>Render some other HTML if myBool is false</p>`}
`;
slot
基本slot
render(){
return html`
<div>
<slot></slot>
</div>
`;
}
命名slot
render(){
return html`
<div>
<slot name="one"></slot>
</div>
`;
}
内建指令(TODO)
样式
基础使用
import { LitElement, css, html } from 'lit-element';
class MyElement extends LitElement {
static get styles() {
return css`
div { color: red; }
`;
}
render() {
return html`
<div>I'm styled!</div>
`;
}
}
多组样式
static get styles() {
return [ css`...`, css`...`];
}
样式逻辑
const mainColor = css`red`;
// ...
static get styles() {
return css`
div { color: ${mainColor} }
`;
}
或者:
static get styles() {
const mainColor = 'red';
return css`
div { color: ${unsafeCSS(mainColor)} }
`;
}
继承
class MyElement extends SuperElement {
static get styles() {
return [
super.styles,
css`...`
];
}
}
共享
被共享的样式:
import { css } from 'lit-element';
export const buttonStyles = css`
.blue-button {
color: white;
background-color: blue;
}
.blue-button:disabled {
background-color: grey;
}`;
调用共享的样式:
import { buttonStyles } from './button-styles.js';
class MyElement extends LitElement {
static get styles() {
return [
buttonStyles,
css`
:host { display: block;
border: 1px solid black;
}`
]
}
...
}
CSS in Shadow Dom
CSS 天然样式隔离, 只能影响改组件中的样式, 例如下面的代码:
class MyElement extends LitElement {
static get styles() {
// Write styles in standard CSS
return css`
* { color: red; }
p { font-family: sans-serif; }
.myclass { margin: 100px; }
#main { padding: 30px; }
h1 { font-size: 4em; }
`;
}
render() {
return html`
<p>Hello World</p>
<p class="myclass">Hello World</p>
<p id="main">Hello World</p>
<h1>Hello World</h1>
`;
}
}
宿主样式
:host: 指定到宿主元素:host(selector): 指定选择器的宿主元素
static get styles() {
return css`
/* Selects the host element */
:host {
display: block;
}
/* Selects the host element if it is hidden */
:host([hidden]) {
display: none;
}
`;
}
slot 样式
::slotted(): 指定到slot元素的样式::slotted(*): 匹配所有的slot元素::slotted(p): 匹配slot中的所有p元素p ::slotted(*): 匹配p标签中的所有slot元素
注意:
::slotted()样式应该被看做可以覆盖的默认样式。
import { LitElement, html, css } from 'lit-element';
class MyElement extends LitElement {
static get styles() {
return css`
::slotted(*) { font-family: Roboto; }
::slotted(p) { color: blue; }
div ::slotted(*) { color: red; }
`;
}
render() {
return html`
<slot></slot>
<div><slot name="hi"></slot></div>
`;
}
}
使用css变量
static get styles() {
return css`
:host { color: var(--themeColor); }
`;
}
<style>
html {
--themeColor: #123456;
}
</style>
<my-element></my-element>
<style> 标签
内部style
不推荐: 会产生性能限制以及可能产生内部变量不会更新的问题
render() {
return html`
<style>
/* updated per instance */
</style>
<div>template content</div>
`;
}
外部style
ShadyCSS polyfill不支持外部样式表- 外部样式在加载时可能导致内容未显示样式(
FOUC)闪烁 - 路径是相对于主文档的, 需要处理路径问题
// css
:host {
display: block;
color: blue;
}
div {
background: aliceblue;
}
button {
width: 200px;
}
// js
render() {
return html`
<link rel="stylesheet" href="./app-styles.css">
<button>a button</button>
<div>a div</div>
`;
}
classMap & styleMap
import { LitElement, html, css } from 'lit-element';
import { classMap } from 'lit-html/directives/class-map';
import { styleMap } from 'lit-html/directives/style-map';
class MyElement extends LitElement {
static get properties() {
return {
classes: { type: Object },
styles: { type: Object }
}
}
static get styles() {
return css`
.mydiv { background-color: blue; }
.someclass { border: 1px solid red; }
`
}
constructor() {
super();
this.classes = { mydiv: true, someclass: true };
this.styles = { color: 'green', fontFamily: 'Roboto' };
}
render() {
return html`
<div class=${classMap(this.classes)} style=${styleMap(this.styles)}>
Some content
</div>
`;
}
}
属性
基础使用
static get properties() {
return {
greeting: {type: String},
data: {attribute: false},
items: {}
};
}
属性选项
attribute: 是否为attributeconverter: 自定义转换器hadChanged: 提供一个函数在属性变化的时候触发noAccessor: 不允许被覆盖reflect: 反射type: 属性的类型
两种声明方式
// 方式1
static get properties() {
return {
prop1: { type: String },
prop2: { type: Number },
prop3: { type: Boolean }
};
}
// 方式2
@property({type : String}) prop1 = 'Hello World';
初始化
JS下
static get properties() { return { /* Property declarations */ }; }
constructor() {
// Always call super() first
super();
// Initialize properties
this.prop1 = 'Hello World';
}
组件实现类的所有构造方法中都必须手动调用父类的构造方法 super()
TS下
@property({ type : String }) prop1 = 'Hello World';
标签中
<my-element
mystring="hello world"
mynumber="5"
mybool
myobj='{"stuff":"hi"}'
myarray='[1,2,3,4]'></my-element>
属性配置
properties/arrtibutes
properties: 可以是任何类型
attributes: 始终是字符串
oberve: attribute => propertyreflect: property => arrtibute
type
// Use LitElement's default converter
prop1: { type: String },
prop2: { type: Number },
prop3: { type: Boolean },
prop4: { type: Array },
prop5: { type: Object }
converter
自定义转换, 在attribute转换到property的过程中, 存在默认的转换方式, 也可以自定义:
prop1: {
converter: {
fromAttribute: (value, type) => {
// `value` is a string
// Convert it to a value of type `type` and return it
},
toAttribute: (value, type) => {
// `value` is of type `type`
// Convert it to a string and return it
}
}
}
或者提供一个函数:
myProp: {
converter: (value, type) => {
// `value` is a string
// Convert it to a value of type `type` and return it
}
}
监听属性和非监听属性
// myprop
myProp: { type: Number }
// my-prop
myProp: { attribute: 'my-prop' }
// 非监听
myProp: { attribute: false }
监听属性一旦发生变化, 就会调用attributeChangedCallback. 某个属性触发次回调, 就会调用属性的fromAttribute
反射
当prop改变是, 其值会反射到对应的observer attribute中
myProp: { reflect: true }
自定义访问器
static get properties() { return { myProp: { type: String } }; }
set myProp(value) {
const oldValue = this.myProp;
// Implement setter logic here...
this.requestUpdate('myProp', oldValue);
}
get myProp() { ... }
...
// Later, set the property
this.myProp = 'hi'; // Invokes your accessor
装饰器写法
_myProp: string = '';
@property({ type: String })
public get myProp(): string {
return this._myProp;
}
public set myProp(value: string) {
const oldValue = this.myProp;
this._myProp = value;
this.requestUpdate('myProp', oldValue);
}
noAccessor
防止LitElement生成覆盖父类的已定义访问器的属性访问器, 可以将属性声明为true
// 父类中
static get properties() {
return { prop: { type: Number } };
}
// 子类中
static get properties() {
return { prop: { reflectToAttribute: true, noAccessor: true } };
}
hadChanged
要为属性定制hasChanged,可以指定该选项来自定义属性更改的比较规则
myProp: { hasChanged(newVal, oldVal) {
// compare newVal and oldVal
// return `true` if an update should proceed
}}
事件
事件绑定位置
模板中绑定
render() {
return html`<button @click="${this.handleClick}">`;
}
构造函数中绑定
可以添加监听将组件添加到DOM之前可能发生的事件.
constructor() {
super();
this.addEventListener('DOMContentLoaded', this.handleLoaded);
}
firstUpdated
首次更新和渲染组建后, 会触发firstUpdated.
firstUpdated(changedProperties) {
this.addEventListener('click', this.handleClick);
}
connectedCallback
当将自定义元素附加到文档链接的元素中, connectedCallback会被处罚.
如果想要吧事件监听到自身或者子级以外的任何事物(window, document)等, 可以在这个钩子中添加监听, 并且在disconnectedCallback删除.
connectedCallback() {
super.connectedCallback();
document.addEventListener('readystatechange', this.handleChange);
}
disconnectedCallback() {
document.removeEventListener('readystatechange', this.handleChange);
super.disconnectedCallback();
}
事件中的this
this指向组件本身:
class MyElement extends LitElement {
render() {
return html`<button @click="${this.handleClick}">click</button>`;
}
handleClick(e) {
console.log(this.prop);
}
}
事件触发
自定义事件
let event = new CustomEvent('my-event', {
detail: {
message: 'Something important happened'
}
});
this.dispatchEvent(event);
标准事件
let click = new Event('click');
this.dispatchEvent(click);
处理事件
绑定事件:
<my-element @my-event="${(e) => { console.log(e.detail.message) }}"></my-element>
监听事件:
const myElement = document.querySelector('my-element');
myElement.addEventListener('my-event', (e) => {console.log(e)});
冒泡重定位
shadow-dom中的冒泡会被重新定位, 对组件外部来说, 这些事件看起来都来自于组件本身.
<my-element onClick="(e) => console.log(e.target)"></my-element>
render() {
return html`
<button id="mybutton" @click="${(e) => console.log(e.target)}">
click me
</button>`;
}
可以使用composedPath寻找起源:
handleMyEvent(event) {
console.log('Origin: ', event.composedPath()[0]);
}
事件穿透
默认的, 组件内部的事件冒泡到shadow-root就停止了, 需要穿透的话需要将composed和bubbles置为true:
firstUpdated(changedProperties) {
let myEvent = new CustomEvent('my-event', {
detail: { message: 'my-event happened.' },
bubbles: true,
composed: true });
this.dispatchEvent(myEvent);
}
生命周期
组件更新流程
- 属性更改
- 检查是否需要更新
- 执行更新
- properties/attribute
- render
- promise.resolve()
默认的组件钩子
LitElement 继承默认的生命周期.
connectedCallback: 组件被添加到documentdisconnectedCallback: 组件被删除adoptedCallback: 组件被移动到一个新的documentattributeChangedCallback: 组件属性被修改
adoptedCallback 没有 polyfilled
所有的生命周期回调中都需要调用其父类的回调:
connectedCallback() {
super.connectedCallback()
console.log('connected')
}
异步
// `async` makes the function return a Promise & lets you use `await`
async myFunc(data) {
// Set a property, triggering an update
this.myProp = data;
// Wait for the updateComplete promise to resolve
await this.updateComplete;
// ...do stuff...
return 'done';
}
let result = await myFunc('stuff');
// `result` is resolved! You can do something with it
生命周期函数
按照调用顺序:
someProperty.hasChanged: 调用属性更改requestUpdate: 手动更新performUpdate: performUpdate在执行下一次浏览器事件循环之前被调用shouldUpdate: 控制是否应继续进行更新, 默认情况下,此方法始终返回true。update: 将property值反射为attributes属性,并通过lit-html调用render来渲染DOMrender: 使用lit-html渲染元素模板firstUpdate: 在元素的DOM第一次更新之后,即在调用更新之前立即调用update: 在元素的DOM已更新和呈现时调用updateComplete: 完成更新
手动更新
//手动调用更新
this.requestUpdate();
// 从一个自定义属性的setter函数中调用
this.requestUpdate(propertyName, oldValue);