GemElement 更多内容

除了 Attribute/Property/Store/State 外的特性。

模板语法扩展

Gem 对 lit-html 进行了很多修改,将一些常用功能内置而不需要通过指令。

引用 DOM

如果你想要在元素内操作 DOM 内容,例如读取 <input> 的值,你可以使用 querySelector 来获取你想要的元素, 为了获得 TypeScript 的类型支持,GemElement 提供了 createRef 完成这项工作:

// 省略导入... @customElement('my-element') class MyElement extends GemElement { #inputRef = createRef(); render = () => { return html`<input ${this.#inputRef} />`; } focus = () => { this.#inputRef.value.focus(); } }

剩余属性

有时候属性通过参数传递到元素上,需要写很多重复代码,比如:

const { prop1, prop2, prop3 } = props; html`<my-element .prop1=${prop1} .prop2=${prop2} .prop3=${prop3}></my-element>`

Gem 支持剩余属性,写法类似 React:

html`<my-element ${...props}></my-element>`

条件渲染

开发中经常会遇到条件渲染,一般会这样写:

html`${isA ? html`<div>a</div>` : isB ? html`<div>b</div>` : html`<div>c</div>`}`

这样的可读性很低,所以 Gem 支持了 v-if,写法类似 Vue:

html` <div v-if=${isA}>a</div> <div v-else-if=${isB}>b</div> <div v-else>c</div> `

NOTE

相比原来的写法,使用 v-if 会损失一点点性能,在元素初始化时,那些不会渲染的元素也会参数解析、创建。

自定义事件

自定义事件是一种传递数据的方法,使用 dispatch(new CustomEvent('event')) 能轻松完成,为了获得 TypeScript 的类型支持, GemElement 允许快速定义方法来触发自定义事件:

// 省略导入... @customElement('my-element') class MyElement extends GemElement { @emitter valueChange; render = () => { return html`<input @change=${(e) => this.valueChange(e.target.value)} />`; } }

添加自定义事件处理程序:

html`<my-element @value-change=${console.log}></my-element>`;

副作用

很多时候,元素需要根据某个属性执行一些副作用,比如网络请求,最后来更新文档。 这时 @effect 就派上用场了,它能在元素每次渲染后检查依赖,如果依赖发生变化就会执行回调。

// 省略导入... @customElement('my-element') class MyElement extends GemElement { @attribute src; @effect((i) => [i.src]) #fetch = () => fetch(this.src); }

下面是依赖子元素的 @effect 例子(其他框架实现):

import { resizeEffect } from './effect.js'; @customElement('app-root') export class App extends GemElement { #ref = createRef(); #state = createState({ height: 0, visible: true }); @effect((i) => [i.#ref.value]) #resize = resizeEffect((height) => { this.#state({ height }); }); render() { const { visible, height } = this.#state; return html` <button @click=${() => this.#state({ visible: !visible})}> ${visible ? 'hidden' : 'show'} </button> <div>${height}</div> <textarea v-if=${visible} ${this.#ref}></textarea> `; } }export function resizeEffect(callback) { return ([ele]) => { if (!ele) return callback(0); const ro = new ResizeObserver(([entry]) => { callback(entry.contentRect.height); }); ro.observe(ele, {}); return () => ro.disconnect(); } }<app-root></app-root>

Memo

为了避免在重新渲染时执行一些复杂的计算,@memo 能在指定依赖变更时执行回调函数,和 @effect 不同的是,他在渲染之前执行。

// 省略导入... @customElement('my-element') class MyElement extends GemElement { @attribute src; #href; @memo((i) => [i.src]) #updateHref = () => (this.#href = new URL(this.src, location.origin).href); }

NOTE

  • @memo 支持 getter,但装饰器暂不支持私有名称,使用 SWC 插件可以解除这一限制。
  • 装饰器 @effect @memo 基于 GemElement.effectGemElement.memo,有必要时,可以使用 GemElement.effectGemElement.memo 动态添加 effectmemo