前端三分天下到四强争霸?神奇的svelte

3,279 阅读12分钟

因为本人水平有限,有什么问题或错误大家也可以去我博客指正,谢谢。

WX20200109-091825@2x.png


简介

  • 更少的代码
  • 无Virtual DOM
  • 真正的响应式

Svelte是一种构建用户界面的全新方法。传统框架(如React和Vue)的大部分工作是在浏览器中完成的,而Svelte将这些工作转换为编译步骤,这在构建应用程序时发生。 Svelte没有使用virtual DOM diffing之类的技术,而是编写可在您的应用程序状态更改时通过特定方式更新DOM的代码。

svelte官网

看完上边官网的介绍,我们可能还是不太理解这个框架到底有什么不一样.

下面是大神尤雨溪对svelte的一段回答(但是尤大在说这话的时候svelte版本还是v1或者v2,没去深究,但v1和v2差别不是太大。但目前版本3 的API 设计上已经完全抛弃Ractive那套,v3 版本进行了大改动,跟之前的版本有很大的差别),继续引用这段话的意义只是在于svelte的核心思想大致就是这样。


作者是 Rich Harris,也就是 Ractive, Rollup 和 Buble 的作者,堪称前端界的轮子哥,现在又带来新轮子了!

这个框架的 API 设计是从 Ractive 那边传承过来的(自然跟 Vue 也非常像),但这不是重点。Svelte 的核心思想在于『通过静态编译减少框架运行时的代码量』。举例来说,当前的框架无论是 React Angular 还是 Vue,不管你怎么编译,使用的时候必然需要『引入』框架本身,也就是所谓的运行时 (runtime)。但是用 Svelte 就不一样,一个 Svelte 组件编译了以后,所有需要的运行时代码都包含在里面了,除了引入这个组件本身,你不需要再额外引入一个所谓的框架运行时!


所以Svelte类似于Vue.js的Javascript框架,’传统’的框架需要运行时代码(当前的框架,无论是React,还是VueJS,无论你怎么编译,在使用时都必然需要引入框架本身,这就是运行时代码)来定义和执行模块,保持状态,更新视图并且还要运行这些框架。Svelte完全溶入JavaScript中。就好像没有引用这个框架,这种方式主要有益于文件大小。

该框架实际上是一个工具,可以将您的源代码编译为没有依赖关系的纯JavaScript。你可以使用Webpack,Browserify,Rollup或Gulp编译源代码,Svelte提供了相应的插件。

介绍结束,我准备写一个简单demo,通过引入常用的UI组件库sveltestrap(Bootstrap的svelte组件库)和echarts来体验一下这个框架的语法.

简单体验

创建svelte项目

npx degit sveltejs/template my-svelte-project

cd my-svelte-project

npm install
npm run dev

访问localhost:5000我们就能看到svelte的初始页面.

生命周期钩子

onMount

onMount将在组件挂载到DOM后立即运行回调。注意onMount不会在服务器端组件中运行。

<script>
	import { onMount } from 'svelte';

	onMount(() => {
		console.log('the component has mounted');
	});
</script>

如果从onMount返回一个函数,则在卸载组件时调用该函数。

<script>
	import { onMount } from 'svelte';

	onMount(() => {
		const interval = setInterval(() => {
			console.log('beep');
		}, 1000);

		return () => clearInterval(interval);
	});
</script>

beforeUpdate

任何状态更改后组件更新之前,回调函数会立即运行。第一次回调运行将在初始onMount之前.

<script>
	import { beforeUpdate } from 'svelte';

	beforeUpdate(() => {
		console.log('the component is about to update');
	});
</script>

afterUpdate

在组件更新后立即运行回调。

<script>
	import { afterUpdate } from 'svelte';

	afterUpdate(() => {
		console.log('the component just updated');
	});
</script>

onDestroy

在组件卸载后运行回调。在onMount、beforeUpdate、afterUpdate和onDestroy中,这是唯一一个在服务器端组件中运行的组件。

<script>
	import { onDestroy } from 'svelte';

	onDestroy(() => {
		console.log('the component is being destroyed');
	});
</script>

svelte语法简介

1. 基础语法

数据绑定
<script>
	let name = 'world';
</script>

<h1>Hello {name}!</h1>

就像可以使用大括号来控制文本一样,也可以使用大括号来控制元素属性。

<script>
	let src = 'tutorial/image.gif';
	let name = 'Rick Astley';
</script>

<img {src} alt="{name} dances.">

就像在HTML中一样,您可以向组件添加标签。让我们为元素添加一些样式:

<style>
	p {
		color: purple;
		font-family: 'Comic Sans MS', cursive;
		font-size: 2em;
	}
</style>

<p>This is a paragraph.</p>
创建组件

为了避免与html的自定义标签等等混淆,svelte的组件名要求得大写.

// Nested.svelte
<p>This is another paragraph.</p>

// App.svelte
<script>
	import Nested from './Nested.svelte';
</script>

<style>
	p {
		color: purple;
		font-family: 'Comic Sans MS', cursive;
		font-size: 2em;
	}
</style>

<p>This is a paragraph.</p>
<Nested/>   // 大写

2. 响应

Svelte的核心是一个功能强大的反应系统,用于保持DOM与应用程序状态同步(例如,响应某个事件)。

<script>
	let count = 0;

	function handleClick() {
		count += 1;
	}
</script>

<button on:click={handleClick}>
	Clicked {count} {count === 1 ? 'time' : 'times'}
</button>

当组件的状态改变时,Svelte会自动更新DOM。通常,组件状态的某些部分需要从其他部分(例如从firstname和lastname派生的完整名称)计算,并在更改时重新计算或者运行任意语句。

<script>
	let count = 0;
	$: doubled = count * 2;

	function handleClick() {
		count += 1;
	}
</script>

<button on:click={handleClick}>
	Clicked {count} {count === 1 ? 'time' : 'times'}
</button>

<p>{count} doubled is {doubled}</p>
<script>
	let count = 0;

	$: if (count >= 10) {
		alert(`count is dangerously high!`);
		count = 9;
	}

	function handleClick() {
		count += 1;
	}
</script>

注意: svelte的响应是基于赋值操作的,那我们push,splice等操作就不会进行更新.所以我们遇到这些操作时可以额外的进行赋值操作来触发响应.

3. 传值

在任何实际应用程序中,都需要将数据从一个组件传递到它的子组件。为此,我们需要声明属性,通常缩写为'props'。在Svelte中,我们使用export关键字来实现这一点。

// App.svelte
<script>
	import Nested from './Nested.svelte';
</script>

<Nested answer={42}/>

// Nested.svelte
<script>
	export let answer;   // 可以指定默认 export let answer = '2234';
</script>

<p>The answer is {answer}</p> // The answer is 42

如果你有一个属性对象,你可以把它们“扩展”到一个组件上,而不是逐个指定:

// App
<script>
	import Info from './Info.svelte';

	const pkg = {
		name: 'svelte',
		version: 3,
		speed: 'blazing',
		website: 'https://svelte.dev'
	};
</script>

<Info {...pkg}/>

// Info
<script>
	export let name;
	export let version;
	export let speed;
	export let website;
</script>

<p>
	The <code>{name}</code> package is {speed} fast.
	Download version {version} from <a href="https://www.npmjs.com/package/{name}">npm</a>
	and <a href={website}>learn more here</a>
</p>

4. 逻辑处理

if-else判断
<script>
	let x = 7;
</script>

{#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}
each遍历

如果需要遍历数据列表,我们可以使用each块:

<ul>
	{#each cats as cat}
		<li><a target="_blank" href="https://www.youtube.com/watch?v={cat.id}">
			{cat.name}
		</a></li>
	{/each}
</ul>

// 加索引
{#each cats as cat, i}
	<li><a target="_blank" href="https://www.youtube.com/watch?v={cat.id}">
		{i + 1}: {cat.name}
	</a></li>
{/each}

// 解构
	{#each cats as { id, name }, i}
		<li><a target="_blank" href="https://www.youtube.com/watch?v={id}">
			{i + 1}: {name}
		</a></li>
	{/each}

但是这样存在一个问题就是我们进行数据变动时,会导致所有数据都再次刷新.所以,我们为每个块指定一个惟一的标识符.

{#each things as thing (thing.id)}
	<Thing current={thing.color}/>
{/each}
异步处理

大多数web应用程序在某些时候必须处理异步数据。Svelte使您可以方便地直接在标记中等待promise的值

<script>
	let promise = getRandomNumber();

	async function getRandomNumber() {
		const res = await fetch(`tutorial/random-number`);
		const text = await res.text();

		if (res.ok) {
			return text;
		} else {
			throw new Error(text);
		}
	}

	function handleClick() {
		promise = getRandomNumber();
	}
</script>

<button on:click={handleClick}>
	generate random number
</button>

{#await promise}
	<p>...waiting</p>
{:then number}
	<p>The number is {number}</p>
{:catch error}
	<p style="color: red">{error.message}</p>
{/await}

5. 事件

dom事件

利用on:指令进行dom事件的绑定.

<script>
	function handleClick(event) {
		alert('你好')
	}
</script>

<div on:click={handleClick}>
	button
</div>

目前一些框架为了性能不允许内联事件,但svelte支持内联语法的写法.

<div on:click="{e => alert('你好')}">    // 可以不加引号,只是为了区分语法
	button
</div>

dom事件还支持修饰符的使用,例如once修饰符表示只调用一次.

<div on:click|once="{e => alert('你好')}"> 
	button
</div>

除了once之外,常见的有self,preventDefault,stopPropagation,passive,capture等修饰符,具体作用参考文档.并且修饰符还支持连接,on:click|once|capture={...}.

组件事件

组件内分派事件,只需要借助事件分发机制就行.

但是我们必须在组件首次实例化时调用createEventDispatcher,以后将无法在内部执行此操作,例如 setTimeout回调中。

// App
<script>
	import Outer from './Outer.svelte';

	function handleMessage(event) {
		alert(event.detail.text);
	}
</script>

<Outer on:message={handleMessage}/>
// Inner
<script>
	import { createEventDispatcher } from 'svelte';

	const dispatch = createEventDispatcher();

	function sayHello() {
		dispatch('message', {
			text: 'Hello!'
		});
	}
</script>

<button on:click={sayHello}>
	Click to say hello
</button>

// Outer
与DOM事件不同,组件事件不会冒泡。如果希望监听某个深度嵌套组件上的事件,中间组件必须转发该事件。但是中间组件需要做重复的事件分发操作,所以svelte为我们提供了简写的形式,不带值,表示转发.
<script>
	import Inner from './Inner.svelte';
</script>

<Inner on:message/>

当然,dom事件也是支持转发的.

<button on:click>
	Click me
</button>

上面只是简单介绍了部分简单语法,更深入的事件,方法,像slot等等,可以去官方文档详细了解.

分析

当然,任何东西,有优点,那必有缺点.此部分的分析在个人目前比较浅薄的基础和一些第三方参考文章上进行的总结分析,如果有什么问题或者错误请海涵并且帮助指正.谢谢!

为什么使用svelte?

  • 你一个人工作或者团队很小,上手容易,很适合小型项目.
  • 你特别在意捆绑包的大小.
  • 你开始接触前端写js代码,svelte可以让你自由编码,无需学习什么jsx或者ts等等.让前端回归最初的形式.

为什么不使用svelte?

  • 您将找不到开箱即用的UI组件,生态小。
  • 有时会隐含错误。
  • 如果您认为您的项目将来需要移动应用程序。您有“ Svelte Native”,其基于NativeScript。但是目前,它的使用效果并不太乐观。
  • 您应该为团队编写严格的命名和代码约定,因为Svelte教程不会为您这样做,因此您将获得非常复杂的代码库.大型项目中使用有待验证.

与其他框架对比

其实svelte更像是一个编译器,其他的更像框架或库(个人愚见,认为react和vue更像库,而真正能称得上框架的应该只有angular.因为angular有一套完整的开发体系与模式,涵盖各方面,表单验证,http等等(大而全),而react和vue更纯粹的专注于组件和界面(轻量级,小而精.当然各家也在不断更新,不断借鉴,不断的扩展).

Svelte React Angular Vue
类型 更像编译器 框架 库或者框架
打包 最小,快速打包 较小,快速打包 中等大小,快速打包 较小,快速打包
API 核心功能 核心功能 庞大的功能集 中等大小的功能集
成熟度及市场 新框架,受欢迎但采用率欠佳 成熟,很受欢迎并且用户最多 成熟,很受欢迎 相对成熟,非常受欢迎(俺们国家很热情)
后台 个人 facebook google 开始于个人,目前整个开源社区集体
常见生态 Svelte Native RN,React VR等等 ionic等等 Vant,vux等等

第三方项目使用

引入sveltestrap

下载

npm install --save sveltestrap 
npm install --save bootstrap

引入css

下载好组件库和bootstrap后,我们需要在src/main.js中引入bootstrap的样式文件.

import App from './App.svelte';
import 'bootstrap/dist/css/bootstrap.min.css';

const app = new App({
	target: document.body,
	props: {
		name: 'world'
	}
});

export default app;

这时候一般会报没有插件可以处理css的错误,是因为svelte的默认打包工具(rollup.js)配置里面没有处理css的导入.

Note that you need plugins to import files that are not JavaScript

这时我们需要用到rollup的css插件postcss:

npm install --save-dev rollup-plugin-postcss 
'or' 
yarn add rollup-plugin-postcss --dev

在rollup.config.js中填写配置:

import postcss from 'rollup-plugin-postcss';
  ...
export default {
	plugins: [
		postcss({
			extensions: [ '.css' ],
		}),
	]
};

使用

在src/App.svelte中进行修改:

<script>
  import {
    Button,
    Modal,
    ModalBody,
    ModalFooter,
    ModalHeader
  } from "sveltestrap";

  let open = false;
  const toggle = () => (open = !open);
</script>

<main>
  <Button on:click={toggle}>打开!</Button>
  <Modal isOpen={open} {toggle}>
    <ModalHeader {toggle}>测试</ModalHeader>
    <ModalBody>
      我是模态框
    </ModalBody>
    <ModalFooter>
      <Button color="primary" on:click={toggle}>Do Something</Button>
      <Button color="secondary" on:click={toggle}>CancBl</Button>
    </ModalFooter>
  </Modal>
</main>

<style>
  main {
    text-align: center;
    padding: 1em;
    max-width: 240px;
    margin: 0 auto;
  }

  @media (min-width: 640px) {
    main {
      max-width: none;
    }
  }
</style>

引入echarts

// 下载
npm install echarts --save

// App.svelte
<script>
  import {
    Button,
    Modal,
    ModalBody,
    ModalFooter,
    ModalHeader
  } from "sveltestrap";
  import Chart from "./chart.svelte";

  let open = false;
  const toggle = () => (open = !open);
</script>

<main>
  <Button on:click={toggle}>打开!</Button>
  <Modal isOpen={open} {toggle}>
    <ModalHeader {toggle}>测试</ModalHeader>
    <ModalBody>
      <Chart />
    </ModalBody>
    <ModalFooter>
      <Button color="primary" on:click={toggle}>Do Something</Button>
      <Button color="secondary" on:click={toggle}>CancBl</Button>
    </ModalFooter>
  </Modal>
</main>

<style>
  main {
    text-align: center;
    padding: 1em;
    max-width: 240px;
    margin: 0 auto;
  }

  @media (min-width: 640px) {
    main {
      max-width: none;
    }
  }
</style>


// chart.svelte
<script>
  import echarts from "echarts";
  import { onMount } from "svelte";

  // 基于准备好的dom,初始化echarts实例
  onMount(() => {
    const myChart = echarts.init(document.getElementById('main'));

    // 指定图表的配置项和数据
    var option = {
      title: {
        text: "ECharts 入门示例"
      },
      tooltip: {},
      legend: {
        data: ["销量"]
      },
      xAxis: {
        data: ["衬衫", "羊毛衫", "雪纺衫", "裤子", "高跟鞋", "袜子"]
      },
      yAxis: {},
      series: [
        {
          name: "销量",
          type: "bar",
          data: [5, 20, 36, 10, 10, 20]
        }
      ]
    };

    // 使用刚指定的配置项和数据显示图表。
    myChart.setOption(option);
  });
</script>

<style>

</style>

<div id="main" style="width: 500px;height:400px;" />