翻译:疯狂的技术宅原文:https://www.valentinog.com/bl...javascript
未经容许严禁转载html
Svelte 是由 Rich Harris 建立的 JavaScript UI 库。 Rich 认为 virtual DOM 带来了额外开销,并提出了 Svelte,如今它正处于第三版的状态。前端
可是你为何要学习Svelte?而不是 React 或 Vue?嗯,它有一些有趣的卖点:java
在下面的教程中,我更关注 Svelte 3 的核心概念。react
无论怎样,不要过度的去追逐潮流。 Svelte 3 确实颇有趣,虽然它在一些细节上还比较粗糙。你能够经过本教程来试试 Svelte 3 的水到底有多深,并造成你本身的观点。git
请慢慢享用。程序员
若是你对如下内容有基本的了解,那么学习本教程就没有问题:es6
若是你是前端初学者,那么这个教程对你来讲也许太过度了。可是不要绝望,先学习如下资源而后再回来。github
若是你须要学习 ES6模块,请查看 JavaScript 中关于 import 和 export 语句的文档。还有优秀的文章 ES6 Modules in depth。面试
要了解有关 Fetch API 的更多信息,请查看 Fetch API。
(是的,对于初学者来讲,要学的东西是不少。但不是个人错!)。
最后还要确保在系统上安装了较新版本的 Node.js.
咱们不会在本教程中构建一个 “全栈的” 程序。相反,我将经过构建一些小的 UI 来引导你完成 Svelte 3 的核心概念。最后,你应该可以开始使用 Svelte 进行构建,并了解了如何建立组件以及如何处理事件等等。
如今享受学习 Svelte 的乐趣!
与全部现代 JavaScript 项目同样,咱们须要完成设置项目全部必需的流程。若是要为项目建立 Git 仓库,请先完成这一步,而后在本地计算机上克隆仓库。
克隆后,你应该已准备好使用 degit 建立一个新的 Svelte 项目了。不用担忧,这不是另外一个须要学习的工具! Degit 是“愚蠢的”。它只是用来制做 Git repos 的副本,在咱们的例子中,咱们将把 Svelte 模板克隆到一个新文件夹中(或者在你的Git repo中)。
回顾一下,若是须要,能够建立一个新的Git仓库,而后在本地机器上克隆它:
git clone git@github.com:yourusername/svelte-tutorial.git
而后用 degit 在新文件夹中建立一个新的 Svelte 项目。若是文件夹不是空的,degit 会报错,因此你须要加上强制标志:
npx degit sveltejs/template svelte-tutorial --force
接下来进入新项目并安装依赖项:
cd svelte-tutorial && npm i
如今你应该很高兴的上路了!
项目就绪后,先来看看里面都有些什么。使用文本编辑器打开项目。你会看到一堆文件:
如今打开App.svelte并查看:
<script> export let name; </script> <style> h1 { color: purple; } </style> <h1>Hello {name}!</h1>
这是一个 Svelte 组件!真的,它须要的只是一个脚本标签、一个样式标签和一些 HTML。 name 是一个变量,而后在 HTML 中的花括号之间插入并使用。如今不要过度关注 export 声明,稍后会看到它的做用。
为了开始探索 Svelte,咱们将当即开始用重火力进攻:先从 API 中获取一些数据。
就此而言,Svelte 与 React 没有什么不一样:它使用名为 onMount 的方法。这是一个所谓的生命周期函数。很容易猜到 Svelte 从哪里借用了这个想法:React 生命周期方法。
如今让咱们在 src 文件夹中建立一个名为 Fetch.svelte 的新 Svelte 组件。咱们的组件从 Svelte 导入 onMount 并向 API 发出获取请求。 onMount 接受回调,并从该回调中发出请求。数据保存在 onMount 内名为 data 的变量中:
<script> import { onMount } from "svelte"; let data = []; onMount(async function() { const response = await fetch("https://academy.valentinog.com/api/link/"); const json = await response.json(); data = json; }); </script>
如今打开 App.svelte 并导入新建立的组件(除了 script 标记,你能够删除全部内容):
<script> import Fetch from "./Fetch.svelte"; </script> <Fetch />
正如你所看到的,自定义组件的语法让人想起 React 的 JSX。由于目前组件只是进行 API 调用,还不会显示任何内容。接下来让咱们添加更多东西。
在 React 中,咱们已经习惯了建立元素列表的映射功能。在 Svelte 中有一个名为“each”的块,咱们要用它来建立一个连接列表。 API 返回一个对象数组,每一个对象都有一个标题和一个 url。如今要添加一个“each”块:
<script> import { onMount } from "svelte"; let data = []; onMount(async function() { const response = await fetch("https://academy.valentinog.com/api/link/"); const json = await response.json(); data = json; }); </script> {#each data as link} // do stuff // {/each}
注意“each”是如何生成变量 data 的,我将每一个元素提取为 “link”。要生成元素列表,只需确保将每一个元素包装在一个 ul 元素中:
<script> import { onMount } from "svelte"; let data = []; onMount(async function() { const response = await fetch("https://academy.valentinog.com/api/link/"); const json = await response.json(); data = json; }); </script> <ul> {#each data as link} <li> <a href={link.url}>{link.title}</a> </li> {/each} </ul>
如今转到你的终端,进入项目文件夹并运行:
npm run dev
访问 http://localhost:5000/ ,你应该看到一个连接列表:
很好!你学会了如何在 Svelte 中生成元素列表。接下来让咱们的组件能够重复使用。
重用UI组件的能力是这些现代 JavaScript 库的“存在理由”。例如在 React 中有 props、自定义属性(甚至函数或其余组件),咱们能够把它们传递给本身的组件,使它们更灵活。
如今 Fetch.svelte 不是可重用的,由于 url 是硬编码的。但没必要担忧,Svelte 组件也能够从外面接收props。让首先将 url 变为一个变量(我将向你展现组件的相关部分):
<script> import { onMount } from "svelte"; let url = "https://academy.valentinog.com/api/link/"; let data = []; onMount(async function() { const response = await fetch(url); const json = await response.json(); data = json; }); </script>
有一个技巧可使 url 成为 props:只需在变量前加上 export:
<script> import { onMount } from "svelte"; // export the variable to make a prop export let url = "https://academy.valentinog.com/api/link/"; let data = []; onMount(async function() { const response = await fetch(url); const json = await response.json(); data = json; }); </script>
如今打开 App.svelte 并经过传递 url prop 来更新 Fetch 组件:
<script> import Fetch from "./Fetch.svelte"; </script> <Fetch url="https://jsonplaceholder.typicode.com/todos" />
如今,你的组件调用的是新端点而不是默认 URL。另外一个好处是标记为 props 的变量可能具备默认值。在咱们的例子中,“https://academy.valentinog.co...”是默认 props,做为没有 props 传递时的后备。
如今看看当咱们须要不止一个 props 时会发生什么。
固然,Svelte 组件可能有多个 props。让咱们为组件添加另外一个名为 title 的 props:
<script> import { onMount } from "svelte"; export let url = "https://academy.valentinog.com/api/link/"; export let title = "A list of links"; let data = []; onMount(async function() { const response = await fetch(url); const json = await response.json(); data = json; }); </script> <h1>{title}</h1> <ul> {#each data as link} <li> <a href={link.url}>{link.title}</a> </li> {/each} </ul>
再次从 App.svelte 传递新的 props:
<script> import Fetch from "./Fetch.svelte"; </script> <Fetch url="https://jsonplaceholder.typicode.com/todos" title="A list of todos" />
当 props 开始增多时,你会发现上述方法不切实际。幸运的是,有一种方法能够传播 props。将 props 声明为对象并将它们分布在组件上:
<script> import Fetch from "./Fetch.svelte"; const props = { url: "https://jsonplaceholder.typicode.com/todos", title: "A list of todos" }; </script> <Fetch {...props} />
很不错不是吗?但我仍然不满意。我想让 Fetch 组件更加可重用,该怎么办?
Fetch 这个命名对于组件来讲并不差劲,若是它是一个 HTML 列表的话。有一种方法能够从外面传递该列表,就像React 中的子 props 同样。在 Svelte,咱们将子组件称为插槽(slot)。
第一步,我将从 Fetch.svelte 中删除全部标记,将其替换为插槽,使它摆脱 prop 的“title”:
<script> import { onMount } from "svelte"; export let url = "https://academy.valentinog.com/api/link/"; let data = []; onMount(async function() { const response = await fetch(url); const json = await response.json(); data = json; }); </script> <slot />
接下来,能够将子元素从外部传递给 Fetch,这就发生在 App.svelte 中:
<script> import Fetch from "./Fetch.svelte"; const props = { url: "https://jsonplaceholder.typicode.com/todos" }; </script> <Fetch {...props}> <h1>A list of todos</h1> <ul> <li>now what?</li> </ul> </Fetch>
但如今咱们遇到了问题。我须要data,它存在于 Fetch.svelte 中,这点很重要,由于我不想手动去建立列表。
在 React 中你能够找到一个 HOC、渲染 props 或 hooks。换句话说,我想渲染一个子组件,可是子组件应该从父组件获取 data。
在 Svelte 中,你能够经过将值反向传递给父组件来得到相同的结果。首先将 data 做为 prop 传递给你的插槽:
<script> import { onMount } from "svelte"; export let url = "https://academy.valentinog.com/api/link/"; export let title = "A list of links"; let data = []; onMount(async function() { const response = await fetch(url); const json = await response.json(); data = json; }); </script> <!-- {data} is a shortand for data={data} --> <slot {data} />
从外面你可使用符号 let:data={data}
访问数据,这里简写为 let:data
:
<script> import Fetch from "./Fetch.svelte"; const props = { url: "https://jsonplaceholder.typicode.com/todos" }; </script> <!-- let:data is like forwarding a component's data one level upward --> <Fetch {...props} let:data> <h1>A list of todos</h1> <ul> {#each data as link} <li>{link.title}</li> {/each} </ul> </Fetch>
如今可使用来自 Fetch 组件的数据了,它可用于个人每一个块。这就像将组件的内部数据向上转发一级。
虽然起初多是反直觉的,但这彷佛是一种简洁的方法。你怎么看?在下一节中,咱们将介绍 Svelte 中的事件处理。
咱们将构建一个表单组件来讲明 Svelte 如何处理事件。建立一个名为 Form.svelte 的新文件。如今它包含用于搜索的 input 和提交类型的 button:
<script> </script> <form> <label for="search">Search:</label> <input type="search" id="search" required /> <button type="submit">Search</button> </form>
(做为练习,你能够将每一个元素提取到其本身的组件中)。
而后在 App.svelte 中包含新组件:
<script> import Form from "./Form.svelte"; </script> <Form />
现用程序应该能够在浏览器中渲染你的表单了。此时若是你尝试提交表单,默认行为是:浏览器触发刷新。
要控制 “vanilla” 中的表单,我会为 submit 事件注册一个事件监听器。而后在处理 handler 内部阻止使用 event.preventDefault()
的默认值:
// vanilla JS example var form = document.getElementsByTagName('form')[0] form.addEventListener('submit', function(event){ event.preventDefault(); });
在 Svelte 组件内部状况有所不一样:使用“on”注册事件handler,后面分别使用事件名称和处理函数:
<script> function handleSubmit(event) { // do stuff } </script> <form on:submit={handleSubmit}> <label for="search">Search:</label> <input type="search" id="search" required /> <button type="submit">Search</button> </form>
此外在 Svelte 中有事件修饰符。其中最重要的是:
能够在事件名称以后使用修饰符 preventDefault 来停用表单上的默认
<script> function handleSubmit(event) { // do stuff } </script> <form on:submit|preventDefault={handleSubmit}> <label for="search">Search:</label> <input type="search" id="search" required /> <button type="submit">Search</button> </form>
还能够将 handleSubmit 做为 prop 来传递,以便使组件更加灵活。这是一个例子:
<script> export let handleSubmit = function(event) { // default prop }; </script> <form on:submit|preventDefault={handleSubmit}> <label for="search">Search:</label> <input type="search" id="search" required /> <button type="submit">Search</button> </form>
而已。如今把这个简单的程序更进一步:我想过滤连接列表。表单已经到位但咱们须要将 Fetch.svelte 与 Form.svelte 链接起来。咱们开始作吧!
让咱们回顾一下到目前为止所作的事情。咱们有两个组件,Fetch.svelte:
<script> import { onMount } from "svelte"; export let url = "https://academy.valentinog.com/api/link/"; let data = []; onMount(async function() { const response = await fetch(url); const json = await response.json(); data = json; }); </script> <slot {data} />
和 Form.svelte:
<script> export let handleSubmit = function(event) { // default prop }; </script> <form on:submit|preventDefault={handleSubmit}> <label for="search">Search:</label> <input type="search" id="search" required /> <button type="submit">Search</button> </form>
App.svelte 是根组件。为方便起见,让咱们在 App 中渲染 Form 和 Fetch:
<script> import Fetch from "./Fetch.svelte"; import Form from "./Form.svelte"; </script> <Form /> <Fetch let:data> <h1>A list of links</h1> <ul> {#each data as link} <li> <a href={link.url}>{link.title}</a> </li> {/each} </ul> </Fetch>
Fetch.svelte 从 API 获取数据并向上转发数据。所以当使用块做为插槽时,能够将数据传递给它的子节点。
如今我但愿用户根据他在表单中输入的搜索词来过滤数据。看起来像 Form 和 Fetch 须要沟通。让咱们看看如何实现这一点。
咱们须要一个搜索项来过滤数据数组。搜索词能够是从外部传递给 Fetch.svelte 的 props。打开 Fetch.svelte 并添加新的 prop searchTerm:
<script> import { onMount } from "svelte"; export let url = "https://academy.valentinog.com/api/link/"; // new prop export let searchTerm = undefined; let data = []; onMount(async function() { const response = await fetch(url); const json = await response.json(); data = json; }); </script> <slot {data} />
(searchTerm 被指定为 undefined,以防止 Svelte 对我抱怨 “Fetch 在建立时找不到预期的 prop searchTerm”)。
接下来须要一个新变量来保存 json 响应,由于咱们将根据 searchTerm 过滤该响应。添加一个名为 jsonResponse 的新变量,使用 jsonResponse 来存储 API 的响应而不是将 json 保存到数据:
<script> import { onMount } from "svelte"; export let url = "https://academy.valentinog.com/api/link/"; // new prop export let searchTerm; // new variable let jsonResponse = []; let data = []; onMount(async function() { const response = await fetch(url); const json = await response.json(); // save the response in the new variable jsonResponse = json; }); </script> <slot {data} />
此时变量数据将包含:
对于过滤数组元素,咱们能够基于 RegExp 对照标题属性进行匹配。 (API返回一个对象数组。每一个对象都有 title 和 url)。第一个实现多是:
const regex = new RegExp(searchTerm, "gi"); const data = searchTerm ? jsonResponse.filter(element => element.title.match(regex)) : jsonResponse;
说得通!让咱们看看完整的组件:
<script> import { onMount } from "svelte"; export let url = "https://academy.valentinog.com/api/link/"; // new prop export let searchTerm = undefined; // new variable let jsonResponse = []; const regex = new RegExp(searchTerm, "gi"); const data = searchTerm ? jsonResponse.filter(element => element.title.match(regex)) : jsonResponse; onMount(async function() { const response = await fetch(url); const json = await response.json(); // save the response in the new variable jsonResponse = json; }); </script> <slot {data} />
在这一点上,咱们须要对 App.svelte 进行一些调整。 searchTerm 应该是来自外部的动态 props。而后咱们在用户提交表单时拦截输入的值。打开 App.svelte 并将 searchTerm 做为 Fetch 的 prop 传递:
<script> import Fetch from "./Fetch.svelte"; import Form from "./Form.svelte"; let searchTerm; </script> <Form /> <Fetch {searchTerm} let:data> <h1>A list of links</h1> <ul> {#each data as link} <li> <a href={link.url}>{link.title}</a> </li> {/each} </ul> </Fetch>
接下来咱们建立并传递 handleSubmit 做为 Form 的 prop,并在 App.svelte 内部保存用户在变量 searchTerm 中输入的搜索词:
<script> import Fetch from "./Fetch.svelte"; import Form from "./Form.svelte"; let searchTerm; function handleSubmit() { const { value } = this.elements.search; searchTerm = value; } </script> <Form {handleSubmit} /> <Fetch {searchTerm} let:data> <h1>A list of links</h1> <ul> {#each data as link} <li> <a href={link.url}>{link.title}</a> </li> {/each} </ul> </Fetch>
几乎完成了。保存全部文件并运行开发服务器。你会看到......一个空白的页面!
这是怎么回事?赶快进入下一节!
Svelte 处理计算值的方式可能一开始看起来不直观。咱们的问题在于 Fetch.svelte,它来自如下几行:
const regex = new RegExp(searchTerm, "gi"); const data = searchTerm ? jsonResponse.filter(element => element.title.match(regex)) : jsonResponse;
思考一下,假设咱们有两个值, regex 取决于 searchTerm,咱们但愿每次后者更改时要从新生成前者。
而后咱们有数据:它应该每次从新处理 searchTerm 和正则表达式。就像电子表格同样:一个值可能取决于其余值。
Svelte 从“反应式编程”中汲取灵感,并对所谓的计算值使用奇怪的语法。这些值在 Svelte 3 中被称为“反应声明”。下面是应该如何调整上述代码:
$: regex = new RegExp(searchTerm, "gi"); $: data = searchTerm ? jsonResponse.filter(element => element.title.match(regex)) : jsonResponse;
$:不是外来的语法。它只是简单的 JavaScript,它被称为标签声明。
这里是完整的 Fetch.svelte:
<script> import { onMount } from "svelte"; export let url = "https://academy.valentinog.com/api/link/"; export let searchTerm = undefined; let jsonResponse = []; $: regex = new RegExp(searchTerm, "gi"); $: data = searchTerm ? jsonResponse.filter(element => element.title.match(regex)) : jsonResponse; onMount(async function() { const response = await fetch(url); const json = await response.json(); jsonResponse = json; }); </script> <slot {data} />
如今,搜索功能将像魔法同样工做:
(过滤 API 级别的连接比每次获取全部连接更好)。
若是你想知道如何用 React实现相同的“app”,请看下一部分。
用 React 构建的相同功能的 demo 看起来是怎样的呢?这是 App.js,至关于 App.svelte:
import React, { useState } from "react"; import Fetch from "./Fetch"; import Form from "./Form"; function App() { const [searchTerm, setSearchTerm] = useState(""); const fetchProps = { url: "https://academy.valentinog.com/api/link/", searchTerm }; function handleSubmit(event) { event.preventDefault(); const { value } = event.target.elements.search; setSearchTerm(value); } return ( <> <Form handleSubmit={handleSubmit} /> <Fetch {...fetchProps} render={links => { return ( <> <h1>A list of links</h1> <ul> {links.map(link => ( <li key={link.url}> <a href={link.url}>{link.title}</a> </li> ))} </ul> </> ); }} /> </> ); } export default App;
这里我使用带有渲染 props 的 Fetch 组件。我可使用 hook,但我想告诉你一样的概念如何适用于 Svelte 和React。
换一种说法:
若是你将 App.js 与 Svelte 对应代码(点击这里)进行比较,能够看到典型的 Svelte 组件比 React 等效组件更加简洁。
经过在 Svelte 3 中的事实很容易解释,不须要显式调用 setSomeState 或相似的函数。 仅经过为变量赋值,Svelte 就能“作出反应”。
接下来是 Form.js,Form.svelte 的 React 实现:
import React from "react"; function Form(props) { return ( <form onSubmit={props.handleSubmit}> <label htmlFor="search">Search:</label> <input type="search" id="search" required={true} /> <button type="submit">Search</button> </form> ); } export default Form;
没有什么可看的,只是一个函数接受一些 props。
最后是 Fetch.js,复制 Fetch.svelte 的功能:
import { useState, useEffect } from "react"; function Fetch(props) { const { url, searchTerm } = props; const [links, setLinks] = useState([]); const regex = new RegExp(searchTerm, "gi"); const data = searchTerm ? links.filter(link => link.title.match(regex)) : links; useEffect(() => { fetch(url) .then(response => response.json()) .then(json => setLinks(json)); }, [url]); return props.render(data); } Fetch.defaultProps = { url: "https://academy.valentinog.com/api/link/" }; export default Fetch;
上面的组件使用 hook 和渲染 props:再次强调这是没必要要的,由于你能够提取 自定义 hook。这里是 Fetch.svelte:
<script> import { onMount } from "svelte"; export let url = "fillThis"; export let searchTerm = undefined; let jsonResponse = []; $: regex = new RegExp(searchTerm, "gi"); $: data = searchTerm ? jsonResponse.filter(element => element.title.match(regex)) : jsonResponse; onMount(async function() { const response = await fetch(url); const json = await response.json(); jsonResponse = json; }); </script> <slot {data} />
他们看起来和我同样帅😀。然而,这些例子远远达不到一个真正的大程序的地步。
我被问到与 React 和 Vue 相比,对 Svelte 的见解是什么?我不能评价 Vue,由于我没有太多的使用经验,但我能够看到 Svelte 如何向其借鉴的。
说到 React,Svelte 对我来讲很合理,看起来更直观。在粗略的一瞥中,Svelte 3 彷佛只是另外一种作事方式,也许比 React 更聪明。
在 Svelte 中真正吸引人的是,它与 React 和 Vue 不一样,没有 virtual DOM。换句话说,库和实际的文档对象模型之间没有抽象:Svelte 3 可被编译为可能的最小原生 JavaScript。若是你在受限制的环境中运行程序,这将很是有用。
回顾一下,Svelte 是一个很是有趣的库,但至少在文档、生态系统和工具将逐渐成熟以前我会给它更多的时间。
为了解更多关于 Svelte 的信息,我不能只推荐官方文档和例子。
本教程的源代码在这里。
另外请务必去看一看 Svelte 做者的演讲:https://www.youtube.com/embed...
还能作些什么?若是你愿意,Svelte 3 还有不少要学的东西。开箱即用的好东西太多了:
说再见以前,我还要再啰嗦几句。
JavaScript是残酷的。各类库来去匆匆,总会有新的东西须要学习。多年来,我学会了不要过于依赖任何特定的 JavaScript 库,但说实话,我真的很喜欢 React 和 Redux。
React 为你们带来了“组件”,另外一方面,库自己须要具备高度专业化的知识才能掌握。相比之下,Vue 更适合初学者,但不幸的是它并不像 React 那样被视为“时尚”(不管那意味着什么)。
Svelte 3 充分利用了两个世界:Svelte 组件看起来像 Vue,而 React 的一些概念也一样适用。
Svelte 比 React 更直观,特别是当一个初学者在 hook 时代去接触 React 时。固然,React 不会很快消失,但我很期待看到 Svelte 的将来。
最后我仍然要老生常谈:要持续不断的学习。