如何学习在下一个 JS 项目中使用 Svelte
在Twitter上关注我,很高兴接受您对主题或改进的建议/Chris
Svelte,我听到这个名字的次数越来越多。
它应该会改变一切
我说真的,另一个 SPA 框架?
是的,这就是 IT。它可能会颠覆 Angular、React 和 Svelte 三大巨头之一。
当然,我有点怀疑。它肯定会随着时间的推移而好转,还是我们已经到了这个地步了?
让我们来看看 :)
那么,什么会促使我们抛弃当前使用的框架,或者添加新的框架呢?
嗯,我做的第一件事就是查看 GitHub,看看这个框架有多流行?
看看大约3万次启动,其中9.5万次被使用。我觉得这相当不错。
这事儿是凭空而来的吗?
经过一些研究表明它是在 2016 年创建的,目前是第 3 版。所以它已经存在了,这很好。
假设我们选择 Svelte 作为我们下一个项目的测试工具,那么我们应该对它抱有怎样的期望才能认真对待它呢?
好吧,这是我的必备清单,你的清单可能会有所不同:
- 以组件为中心,我的意思是当今所有伟大的框架都是以组件为中心的
- 路由,是的,我需要路由
- 测试,如果没有测试库,我不会编写一堆代码
- 表格,听起来很无聊,是的,需要有适当的支持来将数据收集到表格中。
- 数据绑定,某种数据绑定就是我们想要的
- 工具方面,我希望有一个命令行界面 (CLI),这样我就可以搭建项目、运行我的应用,如果能支持热加载就更好了。此外,我还希望有一个简便的方法来打包我的应用。
好的,我们列出了一些需要研究的需求/功能。但首先,我们来谈谈 Svelte 是如何运作的。
什么
Svelte 是一种构建用户界面的全新方法。React 和 Vue 等传统框架的大部分工作都在浏览器中完成,而 Svelte 则将这些工作转移到构建应用时的编译步骤中。
好的,编译时会发生很多事情。您还能告诉我什么?
Svelte 不使用虚拟 DOM 差异等技术,而是编写代码,在应用程序状态发生变化时精确更新 DOM。
好吧,所以没有虚拟 DOM。不过,外科手术式的更新,听起来挺奇葩的。
Svelte 是一个组件框架,就像 React、Vue 和 Angular 一样。
好的,我们得到了组件
不过,两者之间还是有区别的。上述框架使用声明式状态驱动代码,需要将其转换为 DOM 操作。这会带来帧率和垃圾回收的开销。
我猜 Svelte 以某种方式避免了这种情况?
Svelte 则不同,它在构建时运行。它们的组件被转换成命令式代码,从而提供了出色的性能。
听起来很有希望
Svelte 目前处于第 3 版,经历了重大变化,以确保开发人员体验良好,并清除了大部分样板代码。
开发人员经验,是的,必须具备。
资源
以下是我认为您应该在阅读本文时或之后查看的一些资源。
-
https://svelte.dev/
官方资源网站,其中包含我强烈推荐的交互式教程 -
https://svelte.dev/blog/svelte-3-rethinking-reactivity
宣布 Svelte 到来的博客文章。 -
https://svelte.dev/blog/virtual-dom-is-pure-overhead
关于虚拟 DOM。这篇文章大致解释了为什么虚拟 DOM 并非免费,以及它存在哪些限制等等。 -
https://svelte.dev/blog/setting-up-your-editor
这篇文章介绍了如何设置你的 IDE 以识别 Svelte 文件,以及如何安装支持自动补全等功能的扩展程序。VS Code 和 Vim 均提供扩展程序。 -
https://dev.to/vintharas/discovering-svelte-getting-started-with-svelte-writing-a-pomodoro-technique-app-2lph Jaime 撰写的精彩文章介绍了
Svelte 以及如何使用它来实际构建某些东西。
成分
Svelte 就像 Vue、React、Angular 这三大 SPA 一样,都是面向组件的。那么,我们来聊聊 Svelte 中的组件。
Svelte 中的组件存储在一个单独的文件中,该文件以 结尾.svelte
。它包含一个script
包含代码的部分、一个style
包含样式的部分和一个标记部分。
一个简单的组件看起来像这样:
<script>
let name = 'world';
</script>
<h1>Hello {name}</h1>
就是这样?
是的,没什么变化。然而,看看最终的代码,却发现情况并非如此:
/* App.svelte generated by Svelte v3.16.7 */
import {
SvelteComponent,
detach,
element,
init,
insert,
noop,
safe_not_equal
} from "svelte/internal";
function create_fragment(ctx) {
let h1;
return {
c() {
h1 = element("h1");
h1.textContent = "Hello world!";
},
m(target, anchor) {
insert(target, h1, anchor);
},
p: noop,
i: noop,
o: noop,
d(detaching) {
if (detaching) detach(h1);
}
};
}
class App extends SvelteComponent {
constructor(options) {
super();
init(this, options, null, create_fragment, safe_not_equal, {});
}
}
export default App;
已经很多了。好消息是,我们不必再写这些了。
插值
请注意我们是如何使用插值的{}
。
这也可以用于 HTML 属性,如下所示:
<script>
let src = 'tutorial/image.gif';
</script>
<img src={src}>
造型
除了将代码放在script
标签中之外,我们还将样式放在style
标签中,如下所示:
<style>
p {
color: purple;
font-family: 'Comic Sans MS', cursive;
font-size: 2em;
}
</style>
最好的部分是它的范围限定在组件内 - 它不会泄漏。
导入组件
import
您可以使用如下关键字导入组件:
<script>
import Nested from './Nested.svelte';
</script>
像这样使用它:
// App.svelte
<script>
import Nested from './Nested.svelte';
</script>
<p>Some text</p>
<Nested />
是不是很简单?你几乎看不到框架,只有 HTML、CSS 和 JS。
你的第一个项目
理论讲得够多了。让我们开始构建一些东西吧。使用 Svelte 构建任何东西最简单的方法是使用以下命令搭建一个新的 Svelte 项目:
npx degit sveltejs/template <name of project>
然后运行:
npm install
其次是
npm run dev
您应该看到以下内容:
我们好像有LiveReload
,很好!
它已在端口 上启动并运行5000
。我们去检查一下吧!
就是这样。你好,Svelte。
那么实时重新加载呢?我们应该能够进入代码,修改一个变量,然后在浏览器中看到它的变化,而无需启动/停止应用程序。
浏览器现在显示:
太棒了!这可行。是啊,我有点被宠坏了,想要让实时重新加载功能正常工作。我记得我刚开始用 JS 的时候没有这个。
幸好现在这是必须的:)
构建我们的第一个组件
好的,我们有一个项目,让我们继续创建我们的第一个组件并学习一些技巧,例如如何渲染数据以及如何使用属性或道具。
让我们通过创建文件来创建一个 CV 组件CV.svelte
并赋予它以下内容:
<script>
let title = 'chris'
</script>
<h1>{title}</h1>
现在打开,App.svelte
因为我们需要通过以下方式使用这个组件:
- 导入,我们需要导入组件才能使用它
- 将其添加到标记中
您需要以下行进行导入,请将其放在script
标签内:
import CV from './CV.svelte';
要使用它,我们需要将它放在标记中,如下所示:
<main>
<CV />
</main>
您现在应该在浏览器中看到以下内容:
道具
接下来,我们想学习如何向组件发送数据。我们使用 Svelte 中的属性(props)来实现。那么如何使用它们呢?
很简单,使用关键字export
。
返回到您的CV.svelte
文件并添加关键字,export
如下所示:
<script>
export let title = 'chris'
</script>
现在我们可以从外部主动设置title
属性了。让我们打开App.svelte
文件并执行此操作。
我们在script
部分中定义一个新对象:
let person = {
name: 'chris'
}
然后我们在标记部分引用它,如下所示:
<main>
<CV title={person.name} />
</main>
这似乎仍然在我们的浏览器中起作用,太棒了:)
使用 for 循环
当然,我们希望能够渲染比字符串或数字更复杂的数据。列表怎么样?我们可以通过使用如下结构轻松实现:
{#each skills as skill}
<div>Name: {skill.name}, Level: {skill.level}</div>
{/each}
skills
上面是一个列表,skill
是我们为列表中的特定项目指定的名称。我们需要执行以下操作才能使所有这些正常工作:
- 更新我们的人员对象以包含技能列表
- 更改我们的输入属性以获取对象
- 将for 循环渲染代码添加到我们的 CV 组件
让我们开始App.svelte
并更新我们的数据对象,如下所示:
let person = {
name: 'chris',
skills: [
{
name: 'Svelte',
level: 5
},
{
name: 'JavaScript',
level: 5
}
]
}
现在让我们发送整个对象,而不仅仅是标题。因此,我们将标记更改为App.svelte
:
<main>
<CV person={person} />
</main>
现在我们打开CV.svelte
并将其更改为以下内容:
<script>
export let person;
</script>
<h1>{person.name}</h1>
{#each person.skills as skill}
<div>Skill: {skill.name} Level: {skill.level}</div>
{/each}
现在看起来应该是这样的:
使用条件
好的,看起来好多了,但我们应该学习如何使用 IF、ELSE 之类的语句。让我们处理skills
数据,并根据级别进行不同的渲染。
假设我们想要输出REALLY GOOD
级别是否为 5
以及GOOD
级别是否为 4。我们可以使用 Svelte 中的条件结构来解决这个问题,如下所示:
{#if condition}
// render something
{:else if otherCondition}
// render something else
{:else}
// render
{/if}
逻辑
我们可以使用模板逻辑来表达 IF 和 FOR 循环,像这样
如果
{#if condition}
// markup
{/if}
以下登录组件就是一个例子:
<script>
let user = { loggedIn: false };
function toggle() {
user.loggedIn = !user.loggedIn;
}
</script>
{#if user.loggedIn}
<button on:click={toggle}>
Log out
</button>
{/if}
{#if !user.loggedIn}
<button on:click={toggle}>
Log in
</button>
{/if}
别的
我们可以使用 ELSE 来改进上述代码。它的语法位于{:else}
a 内部{#if}
。以下是示例:
{#if user.loggedIn}
<button on:click={toggle}>
Log out
</button>
{:else}
<button on:click={toggle}>
Log in
</button>
{/if}
否则,如果
我们还可以使用 ELSE IF 来表达更多布尔开关逻辑。就像 ELSE 一样,它使用:
类似 的{:else if condition}
。更长的示例如下所示:
{#if x > 10}
<p>{x} is greater than 10</p>
{:else if 5 > x}
<p>{x} is less than 5</p>
{:else}
<p>{x} is between 5 and 10</p>
{/if}
让我们在skills
列表中添加一个条目{ name: 'Photoshop', level: 3 }
并调整组件CV.svelte
使其看起来像这样:
<script>
export let person;
</script>
<h1>{person.name}</h1>
{#each person.skills as skill}
<div>Skill: {skill.name}
Level: {skill.level}
{#if skill.level == 5}
REALLY GOOD
{:else if skill.level == 4}
GOOD
{:else}
DECENT
{/if}
</div>
{/each}
好的,很好,我们也知道如何处理条件。
添加 HTTP
Svelte 中非常酷的一点是它能够非常轻松地处理 HTTP 端点并渲染结果。为此,我们将使用一个名为 的模板构造await
。
让我们来谈谈我最喜欢的 SWAPI 端点之一——星球大战 API。为了能够使用我们的await
构造,我们需要按照以下方式进行:
- 构建我们的 Promise,这是我们对端点进行实际调用的地方
- 定义我们的异步模板,在这里我们将设置标记,以便我们可以在数据到达时呈现数据,并且如果出现问题,我们也有能力进行呈现
构建我们的承诺
让我们在组件中定义一个函数,如下所示:
<script>
let promise = getData();
async function getData() {
const response = await fetch('https://swapi.co/api/people');
const json = await response.json();
return json.results;
}
</script>
定义我们的异步模板
它的模板如下所示:
{#await promise}
<p>...loading</p>
{:then data}
<p>Here is your data {data}</p>
{#each data as row}
<div>{row.name}</div>
{/each}
{:catch error}
<p>Something went wrong {error.message}</p>
{/await}
如上所示,我们指定了promise
变量作为需要等待的对象。我们还指定了{:then data}
获取的数据应该渲染到哪里,并将该数据命名为data
。最后,我们用 指定了错误渲染到哪里{:catch error}
。
我们将所有这些添加到一个单独的组件中HttpDemo.svelte
,并使其看起来像这样:
<!-- HttpDemo.svelte -->
<script>
let promise = getData();
async function getData() {
const response = await fetch('https://swapi.co/api/people');
const json = await response.json();
return json.results;
}
</script>
<style>
.row {
margin: 10px;
box-shadow: 0 0 5px gray;
padding: 10px 20px;
}
.error {
background: lightcoral;
border: solid 1px red;
padding: 10px 20px;
}
</style>
{#await promise}
<p>...loading</p>
{:then data}
<div>
{#each data as row}
<div class="row">{row.name}</div>
{/each}
</div>
{:catch error}
<div class="error">
Something went wrong {error.message}
</div>
{/await}
运行该应用程序你应该看到类似这样的内容:
活动
好的,现在我们对如何使用不同的指令、如何渲染数据、如何处理 HTTP 等有了更多的了解。那么事件呢?我们感兴趣的有两种类型的事件:
- DOM 事件,通常发生在点击按钮、移动鼠标、滚动等操作时。我们可以为这些事件分配一个处理程序
- 自定义事件,这些是我们创建并可以调度的事件。就像 DOM 事件一样,我们可以设置处理程序来捕获这些事件。
那么,我们如何在应用程序中学习这些事件类型呢?让我们尝试通过添加数据来改进我们的简历。
添加技能
好的,为了能够添加技能,我们需要两样东西
- 输入字段,这些字段应该捕获技能的名称和你当前的技能级别
- 一个按钮,这将引发一个事件,最终将技能保存到简历中
- 广播,我们需要告诉我们的组件添加了一项新技能。毕竟父组件是存放简历数据的组件,所以我们需要在那里进行更改。
输入字段
让我们添加以下标记
<h1>{person.name}</h1>
<h2>Add skill</h2>
<div>
<input bind:value={newSkill} placeholder="skill name">
<input bind:value={newSkillLevel} type="number" min="1" max="5" />
<button on:click={saveSkill} >Save</button>
</div>
一个按钮
现在我们需要在部分中添加以下代码script
:
let newSkill = '';
let newSkillLevel = 1;
function saveSkill() {
// TODO save skill
console.log('saving skill', newSkill, newSkillLevel);
}
播送
现在我们需要实现该方法saveSkill()
。它需要引发父组件可以监听的自定义事件。我们在 Svelte 中使用createEventDispatcher
如下方式引发自定义事件:
function sayHello() {
dispatch('message', {
text: 'Hello!'
});
}
让我们将其应用到我们当前的代码中:
<script>
import { createEventDispatcher } from 'svelte';
export let person;
const dispatch = createEventDispatcher();
let newSkill = '';
let newSkillLevel = 1;
function saveSkill() {
dispatch('newSkill', {
skill: newSkill,
level: newSkillLevel
});
}
</script>
好的,我们知道如何向上发送消息,我们如何捕获它?
很简单,我们使用on:<nameOfCustomMessage>
并为其分配一个处理程序。现在打开App.svelte
并将以下代码添加到我们的标记和脚本部分:
<CV person={person} on:newSkill={handleNewSkill} />
对于我们的script
部分:
function handleNewSkill(newSkill) {
console.log('new skill', newSkill);
}
运行此程序时,您应该在控制台中看到以下内容:
请注意上面我们的消息在detail
属性中是怎样的。
让我们完成代码,以便我们将新技能分配给我们的person
属性并确保 UI 按预期工作。
function handleNewSkill(newSkill) {
const { detail: { skill, level } } = newSkill;
person.skills = [...person.skills, { name: skill, level }];
}
我们的用户界面如下:
概括
我以为就此打住。这篇文章已经够长了。我计划在 Svelte 上写更多篇幅,我认为你一次就能消化这么多内容。下一篇我们来讨论如何使用路由和测试,因为这些也包含在内。
文章来源:https://dev.to/itnext/how-you-can-learn-to-use-svelte-for-your-next-js-project-3k8h