背景
- 本想是探索下
bytemd框架是是如何实现左右两边内容同时滚动的,恰巧框架用的是Svelet框架来写的。 - 也想学习使用一下不同的框架,做一期关于框架之间的对比,主要探讨下各框架的优势与劣势,做技术时我们如何做框架选择。
本文章作为上面两个知识的铺垫,先能够把Svelet框架运行起来。
环境搭建
因为我们研究的是 bytedance/bytemd , 所以选择和框架一样的技术选项。
框架使用的是 Vite + Svelet + TS。 OK,为了不出错,我们使用vite官网提供的模版,一股脑儿执行下面操作
// 选择我们所需要的模块创建出来即可
pnpm create vite
// 进入到我们创建的项目目录
cd swift-editor
// 安装库
pnpm install
// 启动项目
pnpm run dev
//http://localhost:5174/ 就可以访问
添加 可以用来把 里的内容转换为普通的CSS的库
import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'
import sveltePreprocess from 'svelte-preprocess' // 添加的
// https://vitejs.dev/config/
export default defineConfig({
plugins: [svelte({
preprocess: [sveltePreprocess({ typescript: true })],
})],
})
Svelet框架简介
本章节内容如果你有react 或者 vue 框架经验 会更加容易理解。 如果看上去比较晦涩跳过也没关系,在后续其他文章会补充框架之间的横向对比,会介绍的更加详细。
先作者介绍:
Svelte是一个新兴的热门前端框架,作者是 Rich Harris,被称为前端界的【轮子哥】,有Ractive、Rollup 和 Buble开源作品。它被预测为未来十年可能取代React和 Vue 等其他框架的新兴技术。
为什么会说快呢?
写过React、Vue 的设计思路比较了解的话就会知道,他们必须引入运行时 (runtime) 代码。需要通过运行时代码在前端完成VirtualDom的构建,以及节点上状态的绑定,当状态发生改变时还要进行diff更新页面。
这些带了一个弊端:随着页面增大导致页面逻辑越来越复杂,页面出现性能变低情况。
为了缓解这些状况,React 和 Vue 分别做了升级优化:
-
React: 当初设计hooks的时候,当状态发生变化,我就是函数从上到下全部执行的。 🤔.... 这样吧
- 这样 我给开发者提供更多的hooks。例如:useCallback,useMemo,shouldComponentUpdate 等,哈哈,这不明摆着把优化的锅甩给了开发者么,开发者的心智越来越重。【React的历史包袱也太重】
- 那我再优化下diff算法,提出Fiber概念(本质上做了最小计算单元,防止页面卡顿)。在React 16 中引入的新的架构,将原本同步不可中断的更新,变成异步可中断更新,将原本一个耗时的大任务做了时间分片,拆分成一个个小任务,在浏览器空闲的时间执行。 【其实也能理解,用的JSX语法,只能在算法上做功课】
-
Vue3: 轮到尤大大了,思考了片刻,我也有Runtime,可是我用的是Template语法,我在运行时性能干不过你,但是我在预编译阶段可以通过Template语法 对VitualDom做剪枝优化。所以Vue3在预编译节点对没有变量以来的节点设置 paseFlag 为静态的,这样diff阶段工作量就小了。 【那种中国文化一下子有了,你还很难挑出他的毛病】
轮子哥: 看我的,嘿嘿... Runtime拖累性能,干脆我就不要了。
Svelte 的设计思路是【通过静态编译减少框架运行时的代码量,即预编译】,Svelted完全溶入JavaScript,应用所有需要的运行时代码都包含在bundle.js里面,因此不需要额外在引入运行时。
Svelte是一种新的思想设计,但是缺点也是有的:
- 作为一个尚处在起步阶段的框架,Svelte 还有很多的不足,如果是在大型的商业项目中中使用 , 需要特别的谨慎。 【生态不够】
注:更多的优点和缺点读者可以去搜了解更多,这里只是带着大家去体验一下新的框架,拓宽下自己知识面。
基础用法
作者用了1小时左右时间将 入门教程 中的Demo挨着看了一遍。建议读者感兴趣的话,也可以做一遍,下面我们只将一些重点
1.基本用法
在Svelte应用中,一个.svelte就是一个组件,它由html、css和js代码组成,类似Vue的写法。这样做有利于降低开发的学习成本,但事实上很多点和Vue是相似的
<script lang="ts">
let name = 'Rock';
</script>
<h1>Hello {name.toUpperCase()}!</h1>
可以向其中添加一些样式
<style>
h1 {
color: purple;
font-family: 'Comic Sans MS', cursive;
font-size: 2em;
}
</style>
组件嵌套
import Nested from './Nested.svelte';
2. reactivity
反应能力:能够让 DOM 与你的应用程序状态保持同步。 从下面的例子中可以看出Svelet极大的简化了写法。
<script>
let count = 0;
function handleClick() {
count += 1;
}
</script>
<button on:click={handleClick}>
Clicked {count} {count === 1 ? 'time' : 'times'}
</button>
反应式声明(对应Vue的计算属性)
<script>
let count = 0;
$: doubled = count * 2;
function handleClick() {
count += 1;
}
</script>
<p>{count} doubled is {doubled}</p>
<button on:click={handleClick}>
Clicked {count} {count === 1 ? 'time' : 'times'}
</button>
$: doubled = count * 2; 当count的值发生变化时,doubled 的值会自动的更新。
反应式语句(自动收集依赖,执行对应的语句)
<script>
let count = 0;
$: console.log(`the count is ${count}`);
function handleClick() {
count += 1;
}
</script>
<button on:click={handleClick}>
Clicked {count} {count === 1 ? 'time' : 'times'}
</button>
更新数组和对象需要注意:
由于 Svelte 的反应性是由赋值语句触发的,因此使用数组的诸如 push 和 splice 之类的方法就不会触发自动更新。 这里重点提一下和Vue是不相同的。
3.属性
属性的传递 父 -> 子
// 子组件中 Nexted.svelte
<script>
export let answer; // 直接不?! 和Vue中作为函数的参数是不是简单了很多
</script>
<p>The answer is {answer}</p>
//App.svelte
<script>
import Nested from './Nested.svelte';
</script>
<Nested answer={42}/>
属性的传递是不是更加简单了。
如果你需要引用传递到组件中的所有参数,包括未使用export声明的道具,可以利用$$props 直接获取。但通常不建议这么做,因为Svelte难以优化,但在极少数情况下很有用。
4.逻辑
基础的语法
// if语法 else语法
{#if user.loggedIn}
<button on:click={toggle}>
Log out
</button>
{/if}
// 遍历语法
<ul>
{#each cats as cat (cat.id)}
<li><a target="_blank" href="https://#?v={cat.id}">
{cat.name}
</a></li>
{/each}
</ul>
- 同样的如果使用遍历,也同样是建议给节点加上一个id
Await块
{#await promise}
<p>...waiting</p>
{:then number}
<p>The number is {number}</p>
{:catch error}
<p style="color: red">{error.message}</p>
{/await}
promise 是一个异步调用函数。 如果是在Vue中写法,会多借助一个Reactive变量,Loading状态。这种写法就可以完全避免掉融于的变量了。
5.事件
通过on指令给jNode绑定事件
<script>
let m = { x: 0, y: 0 };
function handleMousemove(event) {
m.x = event.clientX;
m.y = event.clientY;
}
</script>
<style>
div { width: 100%; height: 100%; }
</style>
<div on:mousemove={handleMousemove}>
The mouse position is {m.x} x {m.y}
</div>
这点和Vue的操作也很像,甚至事件的修饰符基本上都是一致的
所有修饰符列表:
-
preventDefault:调用event.preventDefault(),在运行处理程序之前调用。比如,对客户端表单处理有用。 -
stopPropagation:调用event.stopPropagation(), 防止事件影响到下一个元素。 -
passive: 优化了对 touch/wheel 事件的滚动表现(Svelte 会在合适的地方自动添加滚动条)。 -
capture— 在 capture 阶段而不是bubbling 阶段触发事件处理程序 (MDN docs) -
once:运行一次事件处理程序后将其删除。 -
self— 仅当 event.target 是其本身时才执行。
子组件怎么给父组件通信?
// App.svelte
<script>
import Inner from './Inner.svelte';
function handleMessage(event) {
alert(event.detail.text);
}
</script>
<Inner on:message={handleMessage}/>
// Inner.svelte
<script>
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
function sayHello() {
dispatch('message', {
text: 'Hello!'
});
}
</script>
<button on:click={sayHello}>
Click to say hello
</button>
哈哈,通过事件事件方式实现实现通信的。
- 与 DOM 事件不同, 组件事件不会 冒泡(bubble) ,如果你想要在某个深层嵌套的组件上监听事件,则中间组件必须 转发(forward) 该事件。
6.绑定
数据流部分和Vue是一样的。
一般来说,Svelte 中的数据流是自上而下的——父组件可以在子组件上设置 props,组件可以在元素上设置属性,但反之则不然。 有时打破这个规则是有用的。 以该组件中的 元素为例 - 我们可以添加一个 on:input 事件处理程序,将 name 的值设置为 event.target.value。 正如我们将看到的,对于其他表单元素,情况会变得更糟。
<input bind:value={name}>
这种绑定方式写过Vue应该很熟悉,这种双向绑定其实数据还是单向的,只是框架屏蔽了数据相互传递的细节。 这一块就不多讲了。
this可以绑定到任何标签 (或组件) 并允许你获取对渲染标签的引用。 例如,我们对<canvas>标签进行绑定:
<canvasbind:this={canvas}width={32}height={32}></canvas>
注意,canvas的值 undefined 会一直存在直至组件挂载完毕,因此我们给 onMount添加一个生命周期函数来处理。
-
组件上的事件是通过子组件的向上
dispatch来实现的。
7. 生命周期
每个组件都会有自己生命周期。
- onMount: which runs after the component is first rendered to the DOM.
- onDestory: To run code when your component is destroyed, use
onDestroy. - beforeUdpate & afterUpdate:
beforeUpdate函数实现在DOM渲染完成前执行。afterUpdate函数则相反,它会运行在你的异步数据加载完成后。 tick函数不同于其他生命周期函数,因为你可以随时调用它,而不用等待组件首次初始化。它返回一个带有resolve方法的 Promise,每当组件pending状态变化便会立即体现到DOM中 (除非没有pending状态变化)。
这5个生命周期和Vue也基本相似的
8.Store & Context
Svelte自带了Store,用于解决跨组件的复杂状态传递,关于Store这里不介绍很多,和VueX类似。对于复杂的应用Store还是很有的, 以及Context 这里作为入门就都不太深入讲解。
9. 动画
- 运动
- 过度
- 指令
这块也不作为入门进行介绍,感兴趣的同学可以自行进行学习。
10.Slot 插槽
插槽部分也存在具名插槽,跟Vue的方式相似。
11. 特殊标签
-
<svelte:self>: 因为不是函数式组件,没办法通过名字自己调用自己。因此发明了这个标签。 -
<svelte:component>:可以根据名称选择组件 -
<svelte:window >:如同事件可以监听 DOM 标签一样,你可以通过window对象给<svelte:window>标签添加事件监听
小结
作为新前端框架:
- 使用shadow等高级特性,导致兼容性会出现一些问题。
- 生态不是很完善,配套的安全、性能测试、自动化等工具不是很完善。
- 语法和功能与Vue还是有很大的相似的,成本应该还好。
- 作为开发者,鼓励多去尝试,还是有很多优点的。