在本文中,我们将探讨Svelte 3,这是一个前端JavaScript框架,对框架采用的方法略有不同。尽管诸如React之类的框架附带了大量JavaScript,但Svelte应用程序却被Svelte编译器编译为JavaScript,声称它比等效的React代码小得多。而且由于代码是通过Svelte编译器运行的,因此也可以对其进行优化。
Svelte还采用了一种非常不同的方法来管理数据-看不到useState
钩子-和它一起工作很有趣。即使您是React或任何其他流行框架的忠实拥护者,Svelte也值得一试。在本简介中,我们将构建一个小示例应用程序,以了解Svelte可以提供的功能。让我们开始吧!
入门
在本教程中,我们不会过多地研究捆绑和Svelte应用程序的基础结构,因此,我们将按照Svelte教程来启动和运行应用程序。
我们需要在本地安装Node和Git。然后我们可以运行:
npx degit sveltejs/template github-repository-searcher
这会将Svelte模板存储库克隆到github-repository-searcher
文件夹(我们正在构建的应用程序将使用GitHub API搜索存储库)并为我们设置所有工具。如果您专注于学习Svelte,我强烈推荐这种方法:它将使您直接进入框架,而不会陷入构建配置的困境。
上面的命令完成后,您可以cd github-repository-searcher
转到该目录,然后运行npm install
以安装所有依赖项。完成后,npm run dev
将使用Rollup捆绑程序来构建应用程序,从而启动并运行该应用程序。访问http:// localhost:5000应该会向您显示Svelte Hello World页面,现在我们可以开始构建了!
苗条的组件
在开始构建更多Svelte组件之前,让我们看一下模板随附的现有组件。首先要注意的是Svelte组件是在.svelte
文件中定义的。App.svelte
(位于src
文件夹中)分为三个部分:
<script>
export let name;</script><style>
/* CSS removed to save space */</style><main>
<h1>Hello {name}!</h1>
<p>
Visit the <a href="https://svelte.dev/tutorial">Svelte tutorial</a>
to learn how to build Svelte apps. </p></main>
如果您的编辑器理解这些Svelte文件并且可以使用语法正确突出显示它们,则使用它们将变得更加容易。Svelte提供了我使用的VS Code扩展名,但是如果您使用其他编辑器,建议您在Google上搜索。Svelte有一个规模庞大的社区,因此对于大多数流行的编辑器来说,都有可能存在插件。
苗条的组件分为三个部分:
-
该
script
标签是所有的组件的JavaScript编写。 -
该
style
标签是所有组件的CSS定义。在Svelte组件中,默认情况下,所有CSS的作用域都限于该组件,因此此处的任何样式仅适用于该组件,而不适用于global。 -
组件中提供的任何其他内容都被视为HTML以供组件输出。Svelte还提供了模板逻辑来支持条件渲染,循环遍历数组等。
要运行我们的应用程序,请使用npm run dev
。这将运行Rollup,我们的捆绑程序以及一个小型HTTP服务器,该服务器将在端口5000上为我们的应用程序提供服务。
向用户询问GitHub用户名
我们应用程序的第一步是要求用户提供GitHub用户名。然后,我们将使用该名称并在GitHub上搜索用户拥有的存储库列表。让我们更新App.svelte
以做到这一点。
首先,在script
块中,删除该export let name
行。这就是我们在Svelte中定义props的方式,就像在React中的props一样。export
此处的关键字声明此值是将由组件的父级提供的prop。不过,在我们的情况下,我们的组件将不具有任何属性,因此可以将其删除。您还需要更新src/main.js
以删除props: {...}
代码,因为我们的App
组件没有任何道具。完成此操作后,main.js
应如下所示:
import App from './App.svelte';const app = new App({
target: document.body,});export default app;
该文件包含有效的应用程序入口点。ReactDOM.render
如果您熟悉React,可以认为它等于。
让我们更新App.svelte
所需的HTML。我们将创建一个简单的表单,要求用户输入用户名:
<script></script><style>
main {
width: 80%;
max-width: 800px;
margin: 20px auto;
padding: 20px;
}
label {
font-weight: bold;
}
input {
width: 80%;
}</style><main>
<form>
<label for="username">Enter a GitHub username:</label>
<input type="text" name="username" placeholder="jackfranklin" />
<button type="submit">Load repositories</button>
</form></main>
在本教程中,我们不会专注于CSS(我不是设计师!),但是我应用了少量CSS使外观看起来更好。现在我们有了表单,让我们看看如何将其与Svelte挂钩。首先要注意的是,没有显式的useState
钩子或类似的东西。Svelte采取的方法与Vue或Angular等其他框架的方法非常接近,在这些框架中,您将输入绑定到值。这是Svelte的一个常见主题,鉴于其明确的目标之一是允许开发人员编写更少的代码,这不足为奇。
让我们为输入声明一个变量:
let usernameInputField = '';
然后bind:value
在模板中使用Svelte指令:
<input type="text" name="username" placeholder="jackfranklin" bind:value={usernameInputField}>
Svelte将为我们完成其余工作:随着用户在输入中键入内容,变量usernameInputField
将被更新并保持同步。
用户输入用户名后,我们需要侦听他们提交表单的时间。Svelte使用以下语法来绑定事件侦听器:
<form on:submit={onSubmit}>
onSubmit
用户提交表单时,将调用该函数。Svelte还有另外一招,那就是事件修饰符:
<form on:submit|preventDefault={onSubmit}>
现在,当Svelte在此表单上看到Submit事件时,它将自动event.preventDefault()
为我们打电话。我喜欢这一点:这是我们不必担心的事情,也是我们可以移交给框架的另一件事。
回到我们的script
标签,我们可以定义这个onSubmit
功能。它将使用用户名并调用GitHub API以获取存储库列表(它将返回前30个存储库,因此,如果要获取所有存储库,则需要进行分页,但我们现在将其保留) :
async function onSubmit() {
const url = `https://api.github.com/users/${usernameInputField}/repos`;
const response = await fetch(url);
const repositories = await response.json();
console.log('loaded repositories', repositories)}
一旦有了这些存储库,我们便希望在页面上列出它们,并允许用户搜索给定的存储库。与其在App.svelte
组件中完成所有操作,不如创建一个名为的新组件Search.svelte
。它将获取存储库列表,并为用户提供搜索其所要使用的存储库的输入。
在名为的现有目录中创建一个新文件Search.svelte
。我想以少量样板启动我的组件,只是为了检查是否已完成所有设置:
<script></script><style></style><p>Search component</p>
然后,当我在页面上呈现此组件时,我将能够判断它是否正确呈现。
在零部件之间传递道具和条件渲染
搜索组件将获取的存储库列表作为属性。要声明组件具有属性,我们声明要导出的变量。在中Search.svelte
,将此行添加到<script>
组件的一部分:
export let repositories;
如果您要设置默认值,也可以将其初始化为一个值,如果父级不将其传递进来。这可能看起来有些奇怪,并且确实需要一些时间来适应,因为您并没有真正导出变量在传统的ES模块意义上来说是可变的,但更多地声明您希望您的父组件能够传入一些存储库。
我们希望呈现新Search.svelte
组件,但仅在用户提交表单且已获取存储库时才呈现。Svelte的模板支持条件渲染,形式为#if blocks
。如果您是React用户,这可能需要一些时间来适应,因为您不像在JSX中那样使用常规的JS条件语句,而是使用Svelte模板语言。
我们希望有条件地呈现的任何HTML都可以放在一个#if
块中:
{#if someCondition} <p>someCondition is true!</p>{/if}
我们可以创建一个默认值为的repositories
变量,然后在加载存储库时,将其设置为获取的存储库列表。然后,只有在拥有这些存储库时,才能进行渲染。更新,看起来像这样:App.svelte``undefined``Search.svelte``App.svelte
let usernameInputField = "";let repositories = undefined;async function onSubmit() {
const url = `https://api.github.com/users/${usernameInputField}/repos`;
const response = await fetch(url);
repositories = await response.json();}
通过将repositories
变量移到函数外部,它在我们的组件中都可用,我们也可以在模板中引用它。我们还要更新App.svelte
和导入我们的搜索组件。将其添加到JavaScript的顶部App.svelte
:
import Search from './Search.svelte'
导入组件后,我们可以在模板中对其进行渲染。在获取存储库后,让我们的模板呈现Search组件:
<main>
<form on:submit|preventDefault={onSubmit}>
<!-- snipped to save space -->
</form>
{#if repositories} <Search repositories={repositories} />
{/if}</main>
如果您以前使用过JSX,则创建组件并将道具传递到其中将非常熟悉。Svelte允许进一步的捷径。采取以下代码:
<Search repositories={repositories} />
我们可以将其转换为:
<Search {repositories} />
当道具名称和您要作为道具传递的变量具有相同的名称时,您可以省略第一部分,并将变量包装在一对大括号中传递。这是一个很好的捷径,可以减少打字!
现在,如果您加载了该应用程序,请输入用户名并点击enter,您应该会在页面上看到"搜索组件"文本。现在我们已经开始工作,我们准备深入研究并列出这些存储库,并允许用户对其进行过滤。
Svelte中的每个循环
要遍历我们的存储库,我们可以使用#each blocks
,它采用一个数组,并为数组中的每个项目输出一些HTML。
在中Search.svelte
,添加一个循环,该循环将输出我们找到的每个存储库的名称。请记住,在Svelte模板中,就像JSX一样,我们用于{}
将动态内容插入HTML。Search.svelte
现在应该看起来像这样:
<script>
export let repositories;</script><style></style>{#each repositories as repository}{repository.name}{/each}
输出是混乱的,但是如果您加载该应用程序,您应该会看到我们找到的所有存储库的一个大列表。在执行其他任何操作之前,让我们让它看起来更干净。您可以在此处随意使用自己的CSS,但这是我最终得到的代码及其外观:
<script>
export let repositories;</script><style>
ul {
list-style: none;
margin: 0;
padding: 0;
}
li {
padding: 10px 5px;
}
li:nth-child(odd) {
background-color: lightblue;
}
code {
display: block;
}</style><ul>
{#each repositories as repository} <li><strong>{repository.name}</strong> <code>{repository.url}</code></li>
{/each}</ul>
这是Svelte开箱即用的地方:默认情况下,Svelte组件中的所有CSS都作用于该组件。因此,我可以直接为元素设置样式,而不必担心这些样式会影响此组件之外的其他匹配元素。
这并不意味着我不使用类,ID或其他选择器来精确确定我在Svelte组件中设置的元素的样式,但是,我不必担心默认情况下的全局样式非常好。另外,如果我编写一些未使用的CSS,Svelte会为我突出显示它。由于CSS仅限于组件,因此Svelte可以放心地检测未使用的CSS并提示您将其删除。
搜索存储库
让我们添加一个搜索框,Search.svelte
以便我们允许用户搜索存储库的名称。就像我们向用户询问GitHub用户名的表单一样,我们会将值绑定到变量,以便它在用户键入时自动更新。我还添加了一些额外的样式和CSS,以使外观看起来更好(可以根据自己的喜好随意更改样式):
<script>
export let repositories;
let userSearchTerm = "";</script><style>
/* All the CSS from the previous step is still present, but removed from this code snippet to save space */
.search-wrapper {
border: 1px solid #ccc;
border-radius: 10px;
padding: 5px;
margin: 10px auto;
}
.search-form input {
border-top-left-radius: 10px;
border-top-right-radius: 10px;
width: 100%;
}</style><div class="search-wrapper">
<form class="search-form">
<input
type="text"
bind:value={userSearchTerm}
placeholder="search for repositories" />
</form>
<!-- list of repositories here as per previous code sample --></div>
现在,用户可以在框中键入内容,但是我们现在要做的是在用户键入内容时过滤存储库列表。那么,当用户更新输入时,我们如何运行代码?答案在于Svelte如何处理反应性。
在Svelte组件中,考虑这样的一行:
console.log(userSearchTerm)
如果添加了该内容,则在首次创建并运行该组件时,它只会注销一次。但是,请尝试在此行前面加上$:
,如下所示:
$: console.log(userSearchTerm)
如果您加载应用程序并在搜索框中键入内容,则每次键入该代码时都会记录下来。Svelte使用此语法让您告诉Svelte编译器您希望该代码在每次引用任何更改时都运行。您可能会认为这种语法看起来很奇怪-的确是正确的-但它是完全有效的JavaScript,尽管很少使用JavaScript语法。
如果要运行多行代码,可以将其包装在一对大括号中以创建一个块:
$: {
console.log(userSearchTerm)
console.log('and again', userSearchTerm)}
当您需要基于其他值更新或创建新值时,这非常有用。例如:
$: value = x * 2;
这段代码将设置value
为的两倍x
,但同时也确保以后进行value
更新时都会x
进行更新。
因此,对于我们的特定用例,我们可以定义一个新变量,filteredRepos
该变量在userSearchTerm
更改时会更新,从而将存储库仅过滤到名称与用户搜索的内容匹配的存储库:
$: filteredRepos = repositories.filter((repo) => {
return repo.name.toLowerCase().includes(userSearchTerm.toLowerCase());});
当用户更新搜索词时,或者即使我们通过了一组新的存储库,Svelte也会自动为我们重新运行。
现在,我将更新每一行的模板以使用此新数组filteredRepos
:
{#each filteredRepos as repository}
现在,当我们在搜索字段中键入内容时,它会正确更新,您应该看到现在可以搜索存储库了!
作为用户类型搜索的替代解决方案
我们使用Svelte的$:
语法在用户键入时更新存储库,但是我们也可以略微不同地构造代码以避免这种情况。我们知道Svelte会在数据更改时自动重新渲染模板,因此我们可以将其考虑在内。我们可以定义一个filter
方法来获取我们的存储库和搜索词,并返回匹配的结果:
function filter(repositories, userSearchTerm) {
return repositories.filter((repo) => {
return repo.name.toLowerCase().includes(userSearchTerm.toLowerCase());
});}
现在我们可以直接在模板中调用此函数:
{#each filter(repositories, userSearchTerm) as repository}
而且这仍然可以正常工作。我不确定我个人是否会喜欢这种方法。我不喜欢过滤调用被埋在模板的深处,并且我喜欢具有显式的$: filteredRepos = ...
行使所有阅读代码的人都清楚我们的存储库会随着用户类型的更新而更新。
另一个解决方案是使用事件侦听器。我们可以绑定到on:input
文本字段的事件,并在收到输入事件时过滤存储库。首先,我们绑定到模板中的事件:
<input
type="text"
bind:value={userSearchTerm}
on:input={onUserSearchInput}
placeholder="search for repositories" />
然后filteredRepositories
,当用户键入以下内容时,我们编写一个函数来更新新变量:
let filteredRepositories = repositories;function onUserSearchInput() {
filteredRepositories = repositories.filter((repo) => {
return repo.name.toLowerCase().includes(userSearchTerm.toLowerCase());
});}
最后,我们在模板中使用该新变量:
{#each filteredRepositories as repository}
但是,这种方法使我们面临一个错误。如果repositories
道具更新,我们的filteredRepositories
列表将不会更新,因为我们仅在事件监听器中进行了初始设置。您可以通过在我们的应用程序中搜索一个用户名,然后再搜索另一个用户名来自己尝试。第二次搜索时,您不会看到更新的存储库列表。
我们可以通过更新初始声明为react来解决此问题filteredRepositories
:
$: filteredRepositories = repositories;
但是现在有了另一个错误,即如果在应用过滤器时存储库列表发生更改,则该过滤器不会应用于新的存储库列表。
让我们回到最初的解决方案:
$: filteredRepositories = repositories.filter((repo) => {
return repo.name.toLowerCase().includes(userSearchTerm.toLowerCase());});
我上面提到的两个bug均未发生,并且一切都会按您期望的那样更新。我发现依靠Svelte的功能并利用其对反应性的支持将使您的代码更整洁(请注意,此解决方案使用的代码比我们自己绑定事件侦听器的代码少得多),并减少了发生错误的可能性。用户界面与您的状态不同步。
捆绑生产
现在,我们的应用程序已完全可用,让我们将其捆绑用于生产。Svelte入门模板定义npm run build
为您可以运行的命令,用于捆绑准备生产的应用程序。在我们的应用程序上运行该代码会生成bundle.js
,大小为6kB,并且bundle.css
大小为1kB。虽然6kB听起来可能很多,您可以在没有框架帮助的情况下编写这样的简单应用,但是请注意,该6kB捆绑包的很多成本是固定的:您已经为捆绑Svelte付出了代价,因此文件大小不会随应用程序的增长而变大。当然,它会随着您编写的所有代码一起增长,但是就框架成本而言,这是很小的。而且,您可以通过代码拆分和其他技术使事情更进一步,以尽可能减小初始捆绑包的大小。
结论
我希望这篇文章能引起您的注意:我真的很喜欢Svelte!我非常喜欢使用该框架,并且喜欢Svelte团队为创建一个框架所做的决定,该框架在幕后为我做了很多工作。Svelte框架的一个明确目标是减少开发人员编写的代码量,并且在这个世界上,许多开发人员认为他们编写了大量的样板,Svelte像是一口新鲜空气。