Web 框架层出不穷,作为主流 Web 框架之一的 Svelte,有着独特的优势。它不仅可以构建完整的 Web 应用程序,还可以创建自定义元素,并在其他框架实现的已有 Web 应用程序中使用。本文将对 Svelte 进行详细的介绍,并带领读者了解使用 Svelte 从头开始构建 Web 应用程序所需的基础知识。
Svelte 是什么?
Svelte 是 React、Vue 和 Angular 等 Web 框架的替代方案。与其同类产品一样,Svelte 可用于构建完整的 Web 应用程序。它还能用来创建自定义元素,这些自定义元素可以在使用其他框架实现的已有 Web 应用程序中使用。
Svelte 由 Rich Harris 开发,Rich Harris 曾在《卫报》工作,目前任职于《纽约时报》。Harris 先前创建的Ractive Web框架被《卫报》采用,并成为 Vue 的一部分功能的灵感来源。Harris 还创建了Rollup模块打包器,它是 Webpack 和 Parcel 的替代品。
Svelte 尚未得到应有的重视。人们提到它时往往更关注它生成打包代码的能力,它打包出来的代码明显比竞争对手更小。但除此之外 Svelte 还简化了许多工作,包括定义组件、管理组件状态、管理应用程序状态以及添加动画等。
本文对 Svelte 做了详尽的介绍,并带读者了解用它从头开始构建 Web 应用程序所需的基础知识。
为什么要用 Svelte?
与其他 Web 框架创建的应用程序相比,Svelte 应用程序的包体积较小。它将应用程序代码编译到一个只包含少量框架代码的优化过的 JavaScript 文件来做到了这一点。
Svelte 是用 TypeScript 实现的 Web 应用程序编译器。它不是运行时库。
例如,稍后介绍的 Todo 应用程序的包体积只有等效 React 应用程序的 13%。这两款应用程序的链接在这里和这里。
这个网站统计了现实世界中 Web 应用使用多种 Web 框架构建的相关数据。根据统计,使用一些流行框架的应用经过 gzip 压缩后的大小(KB)分别为:
Angular + ngrx:134
React + Redux:193
Vue:41.8
Svelte:9.7
某些 Web 框架(包括 React 和 Vue)使用虚拟 DOM 来优化渲染更改。重新渲染组件时,框架会在内存中构建 DOM 的新版本,然后将其与以前的版本做对比,不一样的部分才会被应用到实际的 DOM 上。
尽管这比更新实际 DOM 中的所有内容要快,但构建虚拟 DOM 并将其与前一个 DOM 进行比较是需要花时间的。
Svelte 无需使用虚拟 DOM 就可以提供反应性。为了做到这一点,它会跟踪影响各个组件渲染的顶级组件变量的更改,并仅在检测到更改时才重新渲染 DOM 的这些部分。这样就能获得良好的性能表现。
Svelte 大大简化了组件和应用程序状态管理。相关功能包括上下文、存储和模块上下文,稍后将逐一详细介绍。
Svelte 为可访问性问题提供了运行时警告。例如,没有 alt 属性的<img>
元素会被标记出来。
Svelte 当前不支持 TypeScript,但正在推进相关工作。
Svelte Native 支持开发移动应用程序。它基于 NativeScript。
Svelte 会消失吗?
有人说一旦应用程序构建完毕,Svelte 就会消失。
Svelte 库主要由 node_modules/svelte 目录中的.js 文件定义。主要函数在 internal.js 中定义,目前大约有 1400 行代码。
其他库文件则针对特定功能,具体包括:
easing.js
motion.js
register.js
store.js
transition.js
输入 npm run build 会在 public 目录中生成文件,包括 bundle.js。应用程序使用的 Svelte 库函数将复制到 bundle.js 的顶部。后文展示的 Todo 应用程序中,这里大约是 500 行代码。
因此 Svelte 库代码不会消失;只是它与其他 Web 框架相比体积很小。
重要资源
下面是学习 Svelte 时需要查看的重要资源列表:
这是 Rich Harris 在 You Gotta Love Frontend(YGLF)Code Camp 2019 上的演讲。它解释了 Svelte 3 背后的动力,并提供了一些简要介绍。
想要尝试编写少量 Svelte 代码的话这个网站很有用。它还可以显示生成的代码并保存代码,以供分享和提交问题。
入门
下面我们来一步步创建并运行一个 Svelte 应用程序。
从https://nodejs.org安装 Node.js。
这将安装 node、npm 和 npx 命令。
npx degit sveltejs/template app-name
Rich Harris 创建了 degit 工具来简化项目框架。它会下载一个 git 仓库,默认为 master 分支。本例中“sveltejs”是用户名,“template”是存储库。第二个参数是要创建的目录名称。
cd app-name
npm install
npm run dev
这将启动本地 HTTP 服务器并提供实时重载,这与 npm run start 是不一样的,后者忽略了实时重载。语法错误是在运行该命令的窗口中,而不是在浏览器中报告的。这是因为如果出现了错误,Svelte 不会生成该应用程序的新版本。
浏览
localhost:5000
这一步会输出紫色的“Hello world!”。
现在你可以开始修改应用程序了。
初探package.json
文件会发现两件事。首先是 Svelte 默认使用 Rollup 来打包模块。需要的话,可以将其更改为使用 Webpack 或 Parcel。其次是 Svelte 应用程序没有必需的运行时依赖项,只有 devDependencies。
最重要的起始文件包括:
public/index.html
src/main.js
src/App.svelte
这些文件使用 tab 缩进,但需要的话可以将 tab 替换为空格。
文件
public/index.html
包含以下内容:
请注意这里提取了两个 CSS 文件和一个 JavaScript 文件。
global.css
包含可以影响任何组件的 CSS。bundle.css
由每个组件中的 CSS 生成。bundle.js
是由每个组件中的 JavaScript 和 HTML,以及应用程序中其他所有的 JavaScript 生成的。
文件
src/main.js
包含以下内容:
这里会渲染 App 组件。target 属性指定应在何处渲染组件。对于大多数应用程序来说,这就是文档的主体。
name prop 会传递给 App 组件。
通常来说最顶层的组件不需要 props,此处的 props 属性可以删除。
文件
src/App.svelte
包含以下内容:
可以在使用该组件的文件中将导出的变量设置为 props。
大括号用于输出 JavaScript 表达式的值。这里称为插值。稍后我们将看到,大括号也用于动态属性值。
定义组件
流行的 Web 框架使用多种 JavaScript 容器来定义组件。
Angular 使用类。
React 使用函数或类。
Vue 使用对象字面量。
Svelte 不使用任何 JavaScript 容器。
Svelte 组件由包含 JavaScript 代码、CSS 和 HTML 的.svelte 文件定义。它们组合在一起形成组件定义,该定义将自动成为默认导出。
.svelte 文件可以在 src 目录下的任何位置。它们包含以下三部分,这三部分都是可选的。
请注意,每个部分都可以使用不同的注释语法。
组件名称
Svelte 组件定义未指定组件名称。其他框架中组件名称是在源文件中由类名称、函数名称或属性值提供的,这里不是这样;导入.svelte 文件时组件名称会被关联,并且必须以大写字母开头。
小写名称保留给预定义元素,例如 HTML 和 SVG 提供的元素。
例如:
共享数据
在 Svelte 组件之间共享数据有四种方法。
Props
它们将数据从父组件传递到子组件。
上下文
祖先组件可以用它们来使数据可用于后代组件。
存储
它们将数据存储在所有组件外,并使其对所有组件可用。
模块范围
它们将数据存储在组件模块中,并使数据可用于组件的所有实例。
这些内置方法非常有用,实际上你都不需要状态管理库。
Props
组件可以通过 props 接受输入。它们被指定为父组件渲染的组件元素上的属性。
例如,父组件可以执行以下操作:
这里 name prop 的值是字面量字符串。
作为 JavaScript 表达式或非字符串字面量的 prop 值必须用大括号括起来,不能用引号。
src/Hello.svelte 中定义的子组件可以这样做:
这里使用 export 关键字在组件的<script>
部分中声明 props。这里用 Svelte 特有的方式使用了有效的 JavaScript 语法。
由于父元素可以更改值,因此必须使用 let 关键字而不是 const。
为 props 分配默认值是可选的。
目前 Svelte 没有像 React、Vue 和 Angular 那样(通过 TypeScript)进行 prop 类型检查的功能。
属性
元素的属性值可以用 JavaScript 表达式提供。
其语法为:
表达式也可以嵌入到字符串值中。
例如:
当属性值位于与该属性同名的变量中时,可以使用简写语法。
例如:
如果多个属性位于一个对象中,则可以使用散布运算符插入多个属性,其中键是属性名称,值是它们的值。
例如:
上面的示例使用 bind 模拟双向数据绑定。这将在后文详细说明。
样式
.svelte 文件的<style>
标记中的样式将自动确定组件的范围。
Svelte 将生成的相同的 CSS 类名称(svelte-hash)添加到可能受这些 CSS 规则影响的组件的每个渲染元素中,从而实现了作用域。
全局样式应在 public/global.css 中定义。
与标准 CSS 一样,样式标记中的注释必须使用/ * * /注释定界符。
"svelte3"ESLint 插件会对未使用的 CSS 选择器发出警告。
可以有条件地将 CSS 类添加到元素。在以下示例中,仅当 status 大于零时才添加 CSS 类 error。
导入组件
组件可以在其<script>
标记内导入其他组件。
例如:
可以在组件的 HTML 部分中使用导入的组件。
插入 HTML
要渲染一个值为 HTML 字符串的 JavaScript 表达式,请使用语法{@html expression}
。
假设 markup 是一个包含 HTML 字符串的变量。下面的代码将渲染它:
为了避免跨站点脚本,请 escape 不受信任来源中的 HTML。
反应性
插值中引用的顶级变量的更改会自动导致这些插值被重新计算。
例如:
必须分配一个新值以触发此操作。将新元素推送到数组上不会触发它。
可以使用以下方法:
响应式声明
在 JavaScript 语句开头写一个名称,后面跟一个冒号,就会创建一个标签语句。标签语句可以用作 break 和 continue 语句的目标。
有趣的是,在同一范围的多个语句中使用相同的标签名称在 JavaScript 中不是错误。
当这个语法用在顶层语句(未嵌套在函数或块中)上且名称为美元符号时,Svelte 会将顶层语句视为响应式声明。
这是 Svelte 编译器以特殊方式处理有效的 JavaScript 语法的另一个例子。当这类语句引用的任何变量的值更改时,它们自己就会重复。这有点像 Vue 中的计算属性。
例如:
将 $:应用于未声明变量的赋值时(比如上面的 average 赋值),不允许使用 let 关键字。
$:可以应用于一个块。
例如:
这也可以应用于多行语句,比如 if 语句。
例如:
如果条件或主体中引用的任何变量发生更改,上面的示例就会执行;但当然只有在条件为 true 时主体才执行。
例如,如果条件包括对函数的调用,则如果主体中的任何引用发生更改就会调用它们。
Markup 中的逻辑
在 markup 中添加条件逻辑和迭代逻辑有三种常见方法。
React 使用 JSX,其中逻辑由大括号中的 JavaScript 代码实现。
Angular 和 Vue 支持特定于框架的逻辑属性。例如,Angular 支持 ngIf 和 ngFor,而 Vue 支持 v-if 和 v-for。
Svelte 支持包装元素的类似 Mustache 的自定义语法。例如{#if}和{#each}。
IF 语句
Svelte 的条件逻辑以{#if condition}开始。开头的 #表示块的起始标记。
用{/ if}标记结尾。开头的/表示块的结束标记。
有条件渲染的 markup 位于这两者之间。
它们之间可以包含的其他块标记有{:else if condition}和{:else}。开头的:表示块的继续标记。
例如:
虽说这里的语法乍看起来似乎很奇怪,但它确实能有条件地渲染多个元素。Angular/Vue 中向元素添加特殊属性的方法需要指定一个公共父元素。
Each 语句
Svelte 的迭代从{#each iterable as element}开始。
用{/each}标记结尾。
每个元素要渲染的 markup 放在两者之间。
一般来说可迭代的是数组,但任何可迭代的值都能用。
{:else}之后的内容在可迭代内容为空时渲染。
例如,假设变量 colors 设置为[‘red’, ‘green’, ‘blue’]:
如果要添加、删除或修改列表中的项目,则应为每个元素提供唯一的标识符。这类似 React 和 Vue 中所需的 key prop。
在 Svelte 中,唯一标识符是 #each 语法的一部分,而不属于元素 prop。在以下示例中,每个 person 的唯一标识符是对应的 id 属性。
Promise
Svelte 提供了 markup 语法来等待 promise 解析或拒绝。它可以根据 promise 的未完成、已解析或已拒绝的状态提供不同的输出。
以下示例假定组件具有返回 Promise 的 getData 函数。在:then 和:catch 之后可以使用任何变量名来接收解析或拒绝的值。
下一个示例在等待 Promise 解析时省略了要渲染的 markup。:catch 部分也可以省略。
插槽
插槽允许子内容传递到组件。接收组件可以决定是否渲染它,在何处渲染。
请注意,空格算作子内容。
接收组件可以标记使用渲染所有子内容的位置。这称为默认插槽。
它还可以为没有向插槽提供内容的父元素提供默认内容来渲染。例如,<slot>Thanks for nothing!</slot>
。
命名插槽允许父元素提供多组内容,接收组件可以针对这些内容决定是否渲染以及在何处渲染。父元素使用 slot 属性标识它们。子元素则定义它们在何处用带有匹配的 name 属性的 slot 元素来渲染。
下面是来自父元素的 HTML 示例,该 HTML 元素的目标是子元素 ShippingLabel 中的多个命名槽:
这是 ShippingLabel.svelte:
绑定表单元素
像<input>
、<textarea>
和<select>
这样的表单元素可以绑定到变量上。这将模拟双向数据绑定。
除了提供当前值之外,双向数据绑定还提供事件处理,以便在用户更改表单元素值时更新变量。
对于类型为 number 或 range 的<input>
元素,双向数据绑定会自动将值从字符串强制转换为数字。
例如,考虑以下 HTML 表单:
这是使用单个 Svelte 组件的实现。注意在多处使用了 bind:。
除了绑定到基本变量之外,表单元素还可以绑定到对象属性。然后用户输入会使这些对象发生突变。
绑定定制 props
Svelte 可以将子组件 prop 绑定到父组件中的变量。这将允许子组件更改父组件变量的值。
例如,这是父组件:
这是子组件:
按下 Child 组件中的按钮时,cValue 会加倍,并且加倍的值由于绑定到 cValue 而成为 pValue 的新值。
事件处理
事件处理由 on:event-name 属性指定,该属性的值是调度事件时要调用的函数。事件名称可以是标准 DOM 事件或自定义事件的名称。事件对象将传递给给定的函数。
例如:
可以为同一个事件指定多个事件处理函数,并且在分派事件时将分别调用每个函数。
例如:
事件处理程序可以使用修饰符名称前面的竖线指定任意数量的事件修饰符。
例如:
支持的修饰符有:
capture
它导致处理程序函数仅在捕获阶段,而不是默认的冒泡阶段被调用。
once
它将在第一次发生事件后删除处理程序。
passive
它可以提高滚动性能。更多信息参阅这里。
preventDefault
它可以防止事件的默认操作发生。例如,它可以停止表单提交。
stopPropagtion
它可以防止捕获/冒泡流程中的后续处理程序被调用。
从 on:属性中省略事件处理函数可以快速将事件转发到父组件。
例如,假设组件结构的一部分是 A> B> C,并且 C 发出事件“foo”。B 可以使用<C on:foo/>
将其转发到 A。
请注意,on:属性没有值。
此方法也可以用于转发 DOM 事件。
组件可以调度事件。
例如:
这些事件仅转到父组件。它们不会自动在组件结构中冒泡。
父组件使用 on:侦听子组件的事件。
例如,如果父组件定义了函数 handleEvent,则它可以在 Child 组件分派具有给定名称的事件时注册要调用的函数。
生命周期函数
Svelte 支持在组件实例的生命周期中发生四个特定事件时调用注册函数。这些事件包括:
已挂载时
更新前
更新后
被销毁时
术语“已挂载”表示已将组件实例添加到 DOM。
术语“被销毁”表示该组件实例已从 DOM 中删除。
要为这些事件注册函数,请从 svelte 包中导入所提供的生命周期函数。
然后调用这些函数,并在事件发生时向它们传递要调用的函数。
最常用的生命周期函数是 onMount。一种用法是将焦点移至给定的表单元素。另一种用途是从 REST 服务检索组件所需的数据。
下面是一个移动焦点的示例。
属性 bind:this 会将指定为其值的变量设置为对 input 的 DOM 元素的引用。这在传递给 onMount 的函数中使用,以将焦点移至 input。
从 DOM 中删除组件实例时,使用 onDestroy 注册要调用的函数的另一种方法是从通过 onMount 注册的函数返回该函数。这种方法有点像 React 中的 useEffect hook,不同之处在于 Svelte 中传递给 useEffect 的函数在挂载和更新时都运行。
生命周期函数可以从辅助函数中调用。这些可以在单独的.js 文件中定义,从而导入它们并用于多个组件。这很像定义自定义的 React hooks。
建议以“on”开头命名这些辅助函数,就像 React hook 名称以“use”开头那样。
动作
将特定元素添加到 DOM 时,动作(Action)会注册要调用的函数。
动作是在具有属性 use:fnName = {args}的元素上指定的。已注册的函数将传递 DOM 元素和参数(如果存在)。如果不需要除元素以外的其他参数,则省略= {args}。
这在某种程度上与 onMount 生命周期函数有关,该函数注册了将组件的每个实例添加到 DOM 时要调用的函数。将组件中的特定元素添加到 DOM 时,就将调用动作。
例如:
动作函数可以选择返回具有 update 和 destroy 属性(也就是函数)的对象。这个功能不常用。
每当参数值更改时都会调用 update 函数。如果没有参数当然是不行的。
从 DOM 中删除元素时将调用 destroy 函数。
上下文
上下文(Context)提供了一种替代方法,可以使用 props 和存储(接下来介绍)来使组件中的数据在其他组件中可用。上下文数据只能在后代组件中访问。
要在组件中定义上下文,请导入 setContext 函数并调用它,并提供上下文键和值。
例如:
要在后代组件中使用上下文,请导入 getContext 函数并调用它,并提供上下文键。这将从已使用该键定义上下文的最近的祖先组件获取上下文值。
例如:
上下文键可以是任何类型的值,不只是字符串。
上下文值可以是任何类型的值,包括具有后代组件可以调用的方法的对象和函数。
如果创建了上下文的组件再使用相同的键和不同的值调用 setContext,则后代组件将不会接收到更新。它们只能看到组件初始化期间可用的内容。
与 props 和存储不同,上下文不是响应式的。
下面是一个使用上下文使数据在后代组件中可用的示例。
这将渲染以下内容:
原文链接:
https://objectcomputing.com/resources/publications/sett/july-2019-web-dev-simplified-with-svelte
评论 3 条评论