Lit简介:基于标准的反应库(反应标准的标杆是)
Lit基于Mozilla的Web组件标准,以提供简单、合规的反应前端。在一个技术替代品庞大的世界里,它可能会提供一条明确的前进道路。
在用于被动编程的前端JavaScript框架中,Lit是一个有趣的选择。它引起了开发人员的相当大的兴趣,但与其他一些反应性框架相比,它仍然相对低调。Lit建立在Mozilla Web Components标准之上,优先考虑速度和一小套有用的功能。
Mozilla Web组件标准
要理解Lit,你必须了解Web组件。作为所有主要浏览器支持的浏览器标准,Web组件提供了定义UI组件的一致方法。Web组件的想法是在浏览器中为开发人员提供一套工具,以处理UI组件的普遍需求。在一个理想的世界里,每个框架——无论是React、Vue还是其他东西——都会位于Web组件层的顶部,为Web开发提供更多的一致性。
Lit是一个干净、专注的库,它促进了开发人员使用Web组件的更舒适的体验。它通过生成网络组件来工作,这些组件只是自定义HTML元素。这些元素可以广泛使用,例如在React中。这是一个基于标准构建的简单问候组件:
组件的工作原理和作用是显而易见的,尽管有几个有趣的功能,如构造函数和shadowRoot。主要需要注意的是,Web组件允许您使用浏览器标准定义封装功能,该标准可以直接在Web控制台中运行。
使用Lit开发网络组件
现在让我们来看看相同的功能,但使用Lit。
此代码片段的目的与我们的Web组件示例相同,但您可以立即看到大小和复杂性已经减少。以@开头的装饰器(又名注释)让我们以简洁的方式声明customElement(这是Web组件最终所做的)和name属性。由于Lit的css函数(标记的模板字面函数),我们还删除了默认构造函数,不再需要CSS的内联标记。
Lit还允许我们使用render方法来返回由html函数生成的模板。html函数参数的内容允许您将HTML与变量插值相结合。这与JSX和其他模板语法相似,但请注意,我们使用${}而不是{},并且我们用this来指代组件。
尝试这个最简单的方法是使用Lit在线游乐场。请注意,在这个游乐场中,您需要使用TS(TypeScript)切换才能使注释工作。(此限制仅与游乐场有关;注释将与构建中的JavaScript配合使用。)
为Lit组件增加反应性
现在让我们在反应性方面迈出下一步,让Lit的name变量具有交互性。我们将添加一个输入,允许我们更改名称——aninput组件和模板中显示的名称之间的双向绑定。Lit让他们保持同步。
以下代码仅包含已更改的有意义的部分:
render() {
return html`
<div>
<input
type="text"
.value=${this.name}
@input=${this._handleNameInput}
placeholder="Type a name..."
/>
<p>Hello, <span>${this.name} </span>! </p>
</div>
`;
}
_handleNameInput(event: Event) {
const inputElement = event.target as HTMLInputElement;
this.name = inputElement.value;
}
这里的功能与之前的示例相同,但现在我们有一个input元素和一个处理函数。输入是标准HTML类型文本。它也是一个标准值属性,但它的前缀是Lit的dot运算符。dot运算符将输入绑定到${this.name},这是使输入值对该变量反应的神奇成分。dot运算符告诉Lit,你想要值的实时JavaScript属性,而不是静态值。这确保了Lit将使输入保持最新,并对值进行任何编程更改。
@input属性允许我们将更改处理程序指向我们的_handleNameInput 。该函数本身使用标准DOM操作来检索输入元素的值,然后将其分配给the.name变量。那是双向绑定的另一面。当用户更改输入中的值时,处理程序会更新this.name。Lit确保无论this.name 出现在哪里,它都会获得新的值。
在 Lit 中使用内部组件状态
所有反应库共同的另一个基本特征是内部组件状态。Lit还简化了反应编程的这一方面。例如,假设我们需要一个显示/隐藏功能。这将取决于纯粹的内部布尔值,因此不需要将其与与父属性或任何外部内容交互的属性连接起来。我们可以声明一个新的状态变量,如下所示:
@state()
private _showSecretMessage = false;
现在,这将在用户界面中提供给我们。我们可以用它来切换一个部分的可见性:
${this._showSecretMessage
? html` <p class="conditional-text">This is the secret message! </p>`
: '' /* Render nothing if false */
}
This will go in the template, as part of the render function. It uses a template expression (the ${} construct) and within that, a JavaScript ternary operator (the ? : syntax). This will evaluate to the segment following the ? if this._showSecretMessage is true, or the part following : if it’s false. The net result is, if the value is true, we get a chunk of template HTML placed into the view at this point, and if not, we get nothing.
这正是我们想要的——基于我们的切换的有条件渲染。为了实际切换值,我们可以添加一个按钮:
${this._showSecretMessage
? html` <p class="conditional-text">This is the secret message! </p>`
: '' /* Render nothing if false */
}
此按钮代码使用状态变量来有条件地显示适当的标签。以下是@click处理程序的外观:
_toggleSecretMessage() {
this._showSecretMessage = !this._showSecretMessage;
}
在这里,我们只是交换状态变量的值,Lit根据我们的三元显示来显示视图中的变化。现在,我们有一个可以展示和隐藏的面板。
在Lit中渲染集合
现在让我们来看看Lit渲染集合的能力。首先,我们将创建一个作为属性的Hobbits列表:
@property({ type: Array })
hobbits = ["Frodo Baggins", "Samwise Gamgee", "Merry Brandybuck", "Pippin Took"];
我们在这里使用属性而不是状态,因为我们可能会从父级设置此值。接下来,我们想展示我们的Hobbits:
<h4>The Fellowship's Hobbits: </h4>
${this.hobbits && this.hobbits.length > 0
? html`
<ul>
${this.hobbits.map(
(hobbitName) => html` <li>${hobbitName} </li>`
)}
</ul>
`
: html` <p>(No hobbits listed in this roster!) </p>`
}
如果Hobbits是空的,我们再次使用三元条件运算符来显示消息。通过我们的默认数据,我们显示了最著名的霍比特人列表(除Bilbo外)。主要工作是通过在this.hobbits变量上使用map功能运算符来完成的。这让我们可以移动每个元素,并通过Lit的html函数输出适当的列表项目标记。
使用Lit进行API调用
现在让我们从中土世界切换到Westeros,并从远程API加载一些字符数据。
首先,我们将创建一个内部state变量来管理获取承诺:
@state()
private _characterDataPromise: Promise <unknown>;
接下来,我们将实现一个constructor,因为在首次加载组件时,我们需要做一些事情。在这种情况下,我们正在加载数据:
constructor() {
super();
this._characterDataPromise = this._fetchCharacterData();
}
在这里,我们调用_fetchCharacterData函数:
private async _fetchCharacterData() {
const apiUrl = "https://www.anapioficeandfire.com/api/characters?page=1&pageSize=10";
try {
const response = await fetch(apiUrl);
if (!response.ok) {
throw new Error(`API request failed with status: ${response.status}`);
}
const json: Array <{ name: string, culture: string, born: string, aliases: string[] }> = await response.json();
if (json && json.length > 0) {
const characterTemplates = json.map((char) => {
const displayName = char.name || (char.aliases && char.aliases[0]) || "Unnamed Character";
return html`
<li>
<strong>${displayName} </strong>
${char.culture ? html` <span class="character-details"> - Culture: ${char.culture} </span>` : ''}
${char.born ? html` <span class="character-details">, Born: ${char.born} </span>` : ''}
</li>
`;
});
return html` <ul>${characterTemplates} </ul>`;
} else {
return html` <p>No characters found in these lands! </p>`;
}
} catch (error) {
console.error("Failed to fetch Game of Thrones character data:", error);
return Promise.resolve(html` <p class="error-message">Could not summon characters: ${error.message} </p>`);
}
}
这里的代码主要是标准JavaScript,除了我们使用Lit'shtml函数来为获取结果中的每个情况返回适当的模板标记。但请注意,实际的_fetchCharacterData函数返回一个承诺。在出现错误的情况下,它会明确这样做,但在所有情况下,异步函数都会返回一个承诺。另请注意,resolve方法是用html函数调用的内容调用的。
我们在
this._characterDataPromise之前保存了这个承诺的句柄。保存的句柄让我们在主组件模板中智能地等待此调用的结果:
return html`
<h4>Characters from the Seven Kingdoms (or thereabouts): </h4>
${until(
this._characterDataPromise,
html` <p class="loading-message">Sending a raven for news (loading characters...). </p>`
)}
`;
再次,我们使用until()函数来等待承诺的最终结果。请注意,第二个参数显示等待的内容。
结论
Lit包含大量有趣的想法,其受欢迎程度并不奇怪,特别是考虑到其基于Web组件标准。最大的问题是,Lit是否会作为React、Svelte和Vue等一系列其他框架的通用组件系统起飞。如果是这样,我们将在相关性和采用方面进入一个全新的阶段。然而,就目前而言,Lit本身是一种可行的方法,对高度重视标准合规性的项目特别有吸引力。