Svelte简介

前言

前几天在刷github趋势榜,意外的发现了这个开源项目,通过阅读文档与相关资讯了解到Svelte的用法,好处以及缺陷,并对其思想深深的吸引,虽然框架存在诸多不足,生态圈也不算完善,但是我个人认为,这不失为一种web组件化方向的探索。在寻找资料的过程中,我发现Svelte几乎没有什么中文相关资料和中文文档,于是我决定先将其文档翻译成中文,这样能让更多的人了解到Svelte框架。大家也可以一起为它添砖加瓦。

相关资料也会长期更新。

以下是翻译的内容。如果你有什么好的想法,或者翻译中存在什么错误,欢迎提iusse指正,非常感谢。

什么是Svelte

如果您曾经构建过JavaScript应用程序,你或多或少使用过React,Angular,Vue和Ractive。与Svelte很像,这些工具都有一个目标,就是可以轻松的构建交互式用户界面。

但Svelte有一个关键的区别:你的应用程序会在打包构建时(build)被转化成原生的可执行的javascript代码,且不需要一个运行时(runtime)来解释你的应用程序代码。这意味着你无需负担由于框架抽象或者在你的应用首次加载时产生的性能损耗。

并且,由于没有额外的使用成本,你可以轻松地在现有的程序中逐渐采用Svelte,或者将小部件作为独立的组件使用到任何的地方。

阅读介绍性博客文章,了解更多关于Svelte的目标和哲学。

中文介绍

认识与理解svelte组件

在Svelte中,应用程序由一个或多个组件组成。组件是封装了标签的,样式,与行为的可复用的自包含代的码块。(很像.vue文件)。

像Ractive和Vue一样,Svelte也崇尚单文件组件的概念:组件只是一个.html文件。下面是一个简单的例子:

  1. <!-- App.html -->
  2. <h1>Hello {{name}}!</h1>

Svelte会把它转换为可以一个可直接被引入到你的应用程序中的JavaScript模块:

  1. // main.js
  2. import App from './App.html';
  3. const app = new App({
  4. target: document.querySelector( 'main' ),
  5. data: { name: 'world' }
  6. });
  7. // 更改与模板相关的数据
  8. app.set({ name: 'everybody' });
  9. // 卸载组件并做一些清理
  10. app.destroy();

恭喜你,你刚刚已经了解了一半的SvelteAPI!

入门

通常来说,这里会说你需要使用<script>标签把框架引入到你的页面。但是由于Svelte在构建时就已经打包成了可运行的代码,所以它的工作方式会有所不同。

Svelte的最好的使用方式是将其集成到你的构建系统中,这里有针对RollupBrowserifyGulp等的打包工具,更多资料,请参阅这里获取最新的列表。

现在,为了演示的目的,我们将使用svelte-cli命令行工具。

你需要安装node.js并熟悉一些简单的命令行操作

首先,安装svelte-cli

  1. npm install -g svelte-cli

然后为项目创建一个文件夹

  1. mkdir my-svelte-project
  2. cd my-svelte-project

进入my-svelte-project文件夹,创建一个HelloWorld.html的文件,并且输入如下内容:

  1. <h1>Hello {{name}}</h1>

编译它

  1. svelte compile --format iife HelloWorld.html > HelloWorld.js

--format iife是指生成一个立即调用的函数表达式,这样做可以允许我们使用一个<script>标签来使用这个组件(默认情况下,Svelte被编译成一个JavaScript模块,建议在更高级的应用中使用,但需同时也需要额外的步骤)。

创建一个index.html页面并且引入刚才生成的代码

  1. <!doctype html>
  2. <html>
  3. <head>
  4. <title>My first Svelte app</title>
  5. </head>
  6. <body>
  7. <main></main>
  8. <script src='HelloWorld.js'></script>
  9. <script>
  10. var app = new HelloWorld({
  11. target: document.querySelector( 'main' ),
  12. data: {
  13. name: 'world'
  14. }
  15. });
  16. </script>
  17. </body>
  18. </html>

最后,在浏览器里面打开该页面,并在控制台中输入app可以查看更多API。

组件API

正如我们上面看到的,你通过new关键字创建了一个组件实例:

  1. import MyComponent from './MyComponent.html';
  2. const component = new MyComponent({
  3. // `target` 是唯一的必填字段 – 类似vue中的el配置
  4. // 用来描述组件将渲染在哪里
  5. target: document.querySelector( 'main' ),
  6. // `data` 是一个选填字段。一个组件也能有
  7. // 默认 data – 我们稍后会接触到
  8. data: {
  9. questions: [
  10. 'life',
  11. 'the universe',
  12. 'everything'
  13. ],
  14. answer: 42
  15. }
  16. });

除了你添加的自定义方法之外,每一个组件实例都提供少量的方法,你能控制这些方法操作组件。

component.set(data)

该方法会根据传入的新data值更新组件状态并引起DOM的改变。data必须是一个纯粹的javascript对象。任何没有包含在data之内的属性将会保持和原来一样。

  1. component.set({
  2. questions: [
  3. 'why is the sky blue?',
  4. 'how do planes fly?',
  5. 'where do babies come from?'
  6. ],
  7. answer: 'ask your mother'
  8. });

如果你之前使用过Ractive,这里会与ractive.set(...)非常类似,但是你必须始终使用ractive.set(...)而不能使用ractive.set('foo', 'bar')这种方式,并且你不是直接给对象设置关键字(比如,不能使用ES6中用{‘class’:1}这种)。它也非常类似于ReactsetState,除了svelte会导致同步更新,这意味着DOM总是处于可预测状态。

component.get(key)

返回当前key的值

  1. console.log( component.get( 'answer' ) ); // 'ask your mother'

这也会取出计算属性的值

component.observe(key, callback[, options])

此方法允许你响应状态更改,结合生命周期钩子和双向绑定时这将会特别有用

  1. const observer = component.observe( 'answer', answer => {
  2. console.log( `the answer is ${answer}` );
  3. });
  4. // 会立即出发修改过后的answer
  5. // -> 'the answer is ask your mother'
  6. component.set({ answer: 'google it' });
  7. // -> 'the answer is google it'
  8. observer.cancel(); // further changes will be ignored

回调函数有两个参数,当前值和上一个值。(如果是第一次调用第二个参数会是undefined):

  1. thermometer.observe( 'temperature', ( newValue, oldValue ) => {
  2. if ( oldValue === undefined ) return;
  3. console.log( `it's getting ${newValue > oldValue ? 'warmer' : 'colder'}` );
  4. });

如果你不希望在首次添加observer时触发回调,你可以设置init:false

  1. thermometer.observe( 'temperature', ( newValue, oldValue ) => {
  2. console.log( `it's getting ${newValue > oldValue ? 'warmer' : 'colder'}` );
  3. }, { init: false });

对于字符串和数值类型的值,只有当值更改的时候才会触发observer的回调。但是由于值可能是一个对象或者数组,而传入的值如果引用与之前是相同的时候Svelte会谨慎处理(因为引用相同没则需要判断内部是否每个值都相同)也就是说,如果你调用component.set({foo: component.get('foo')}),而foo是一个对象或者数组的时候,任何一个fooobserver都将会触发。

默认情况下,observer会在DOM更新之前被调用,让你有机会执行任何其他更新,而不需要修改DOM。 在某些情况下,你需要在DOM变更之后再触发回调,例如,如果在DOM更新后需要计算元素可以使用defer:true

  1. function redraw () {
  2. canvas.width = drawingApp.get( 'width' );
  3. canvas.height = drawingApp.get( 'height' );
  4. updateCanvas();
  5. }
  6. drawingApp.observe( 'width', redraw, { defer: true });
  7. drawingApp.observe( 'height', redraw, { defer: true });

如果需要监听嵌套的组件,可以使用refs:

  1. <Widget ref:widget/>
  2. <script>
  3. export default {
  4. oncreate () {
  5. this.refs.widget.observe( 'xxx', () => {...});
  6. }
  7. };
  8. </script>

component.on(eventName, callback)

允许你响应事件

  1. const listener = component.on( 'thingHappened', event => {
  2. console.log( `A thing happened: ${event.thing}` );
  3. });
  4. // some time later...
  5. listener.cancel();

component.fire(eventName, event)

component.on(...)有关:

  1. component.fire( 'thingHappened', {
  2. thing: 'this event was fired'
  3. });

乍一看,component.on(...)component.fire(...)不是特别有用,但是当我们了解嵌套组件时,它会变得非常好用。(因为没有类似Vuex的东西组件间通信就靠它了)

component.on(...)component.observe(...)看起来很相似,但是它们有不同的用途,Observers被用来响应应用的数据流的变化和任何时候持续的变化,然而事件在处理离散的时刻会更好用,比如用户做了一个选择,选择引发了很多变动。

component.destroy()

把组件从DOM中移除,同时也会移除所有创建的observers和事件监听,这也会触发一个destory事件。

  1. component.on( 'destroy', () => {
  2. alert( 'goodbye!' ); // please don't do this
  3. });
  4. component.destroy();

模板语法

Svelte不重复造轮子,学习Svelte模板只需要在HTMLCSSJavaScript基础上少量的学习一些新知识即可。

标签

标签允许你把数据绑定到模板上,无论何时修改数据(比如,在component.set(…)之后),DOM将自动更新。你可以在模板中使用任何JavaScript表达式,并且也会自动地更新:

  1. <p>{{a}} + {{b}} = {{a + b}}</p>

你还可以使用标签属性

  1. <h1 style='color: {{color}};'>{{color}}</h1>

虽然使用{{}}分隔标签,但Svelte不使用Mustache语法。 标签只是JavaScript表达式。

Triples(直接输出html)

普通的分隔标签将会生成存文本,如果你需要将表达式转化成HTML,你可以把他包含在三个{{{,}}}

  1. <p>This HTML: {{html}}</p>
  2. <p>Renders as: {{{html}}}</p>

与普通标签一样,你也可以在其中使用Javascript表达式,并且当数据变化的时候也会自动更新内容

使用三个花括号的形式将不会对HTML进行转义!如果你想显示用户输入,你有义务先对数据进行转义,否则你可能会遭受不同形式的攻击

条件渲染

通过将其包装在if块来控制模板的一部分是否被渲染。

  1. {{#if user.loggedIn}}
  2. <a href='/logout'>log out</a>
  3. {{/if}}
  4. {{#if !user.loggedIn}}
  5. <a href='/login'>log in</a>
  6. {{/if}}

你也可以把上面两个block通过{{else}}合并:

  1. {{#if user.loggedIn}}
  2. <a href='/logout'>log out</a>
  3. {{else}}
  4. <a href='/login'>log in</a>
  5. {{/if}}

你也可以使用{{elseif ...}}:

  1. {{#if x > 10}}
  2. <p>{{x}} is greater than 10</p>
  3. {{elseif 5 > x}}
  4. <p>{{x}} is less than 5</p>
  5. {{else}}
  6. <p>{{x}} is between 5 and 10</p>
  7. {{/if}}

列表渲染

循环数据列表

  1. <h1>Cats of YouTube</h1>
  2. <ul>
  3. {{#each cats as cat}}
  4. <li><a target='_blank' href='{{cat.video}}'>{{cat.name}}</a></li>
  5. {{/each}}
  6. </ul>

你可以使用name,index的方式来访问当前元素的索引

  1. <div class='grid'>
  2. {{#each rows as row, y}}
  3. <div class='row'>
  4. {{#each columns as column, x}}
  5. <code class='cell'>
  6. {{x + 1}},{{y + 1}}:
  7. <strong>{{row[column]}}</strong>
  8. </code>
  9. {{/each}}
  10. </div>
  11. {{/each}}
  12. </div>

指令

最后,Svelte模板语法不同于传统HTML的地方在于:指令允许你添加添加事件处理程序,双向绑定,refs(引用)等特殊申明。我们在后面将会逐一介绍,你现在需要知道的是,可以通过:字符的方式来识别指令:

  1. <p>Count: {{count}}</p>
  2. <button on:click='set({ count: count + 1 })'>+1</button>

从技术上来说,:用来表示HTML属性的命名空间,如果遇到的话,将不会被视为指令

样式作用域

Svelte的一个重要原则就是组件是独立的,可以在不同的上下文中复用,因此它有一个独特的CSS隔离机制,这样就不会意外的干扰到相同选择器的其他页面了

添加样式

你的组件可以有一个<style>标签,像这样:

  1. <div class='foo'>
  2. Big red Comic Sans
  3. </div>
  4. <style>
  5. .foo {
  6. color: red;
  7. font-size: 2em;
  8. font-family: 'Comic Sans MS';
  9. }
  10. </style>

如何工作的

打开示例,检查元素以查看发生了什么,你会发现,Svelte已经向元素添加了一个svelte-[uniqueid]属性。并相应地转换了CSS选择器。因为页面上没有其他元素可以共享该选择器,所以页面上class ='foo'的任何其他元素都不会受到我们的样式的影响。

这比通过Shadow DOM实现相同的效果要简单得多,并且在没有polyfills的地方使用。

Svelte将会向含有该组件的页面添加一个