so why?
因为闲着也是贤者。考虑到 Vue 2 已经 EoL 很久了,而这个项目又想要做点新功能,所以想着把依赖项升一升。
之所以尝试 svelte,一个是因为其不像 vue 使用的是虚拟 dom,理论上有些性能优势;二个是其对 typescript 比较友好,整套工具链用起来体验还行。而且之前也尝试过使用 svelte kit 做了一个小网站项目,用着还算顺手。于是这次这次就想着要不直接上 svelte 得了。
还有一个原因是,之前尝试从 vue 2 升级到 vue 3,中间的过程踏入了不少的坑。想着既然升 vue 3 是坑,换 svelte 也是坑,那不如直接换 svelte 得了。于是就有了这次的升级。
升级步骤
0. 从 vue-cli 升级到 vite
vue 2 用的是 vue-cli 这一套打包工具,而到 vue 3 之后,就转向了 vite。恰巧,svelte 也推荐使用 vite,那我们的第一步就是把 vue-cli 升级到 vite。
Vue school 提供了比较详细的教程,供参考。简单来说就是:
1. 删掉老的依赖项,添加 vite 依赖项
2. 移除 bable,修改 .eslint.rc
3. 添加 vite 相关配置
4. 移动 index.html 到根目录,删掉对应的 <%= %>,新增 script 引用。
5. 替换环境变量,process.env.XXX -> import.meta.env.XXX
需要注意的是,我们这里是切换到 svelte,所以里面的那些安装什么 vue 的依赖可以全部换成对应的 svelte 的依赖。
如果你不知道怎么改,那也可以直接新建一个 svelte 的工程,然后把里面的文件复制到老工程目录下,对着最后修改下对应的 package.json 即可。
1. 安装 svelte
直接 npm install –save svelte 即可。如果你在上一步已经改好了所有的依赖,那当我没说。
其他需要的依赖包括:
– @sveltejs/vite-plugin-svelte: vite 打包插件
– @tsconfig/svelte: typescript 相关配置
– svelte-check: 语法检查
2. 开始迁移
需要事先说明的是,我之前使用的是 vue-class-component
+ vue-property-decorator
这两个扩展组件,所以vue的语法可能跟原生的语法相差较大。
修改入口点
main.ts:
-import Vue from "vue";
-import App from "./App.vue";
-new Vue({
- render: h => h(App)
-}).$mount("#app");
+import { mount } from 'svelte'
+import App from './App.svelte'
+const app = mount(App, {
+ target: document.getElementById('app')!,
+})
+export default app
因为我们切换了框架,所以这一步基本上是重写了整个入口点文件。可以看出两边的语法还是有诸多不同的。
修改对应文件
将对应的 App.vue
重命名为 App.svelte
,然后就可以开始真正的迁移之旅了。
模板语法变更
在 Svelte 中,HTML内容不需要被 template 标签所包裹,所以直接删除就可以了。
-<template>
<div></div>
-</template>
对于常见的 if 条件判断,Svelte 这里的语法会比较奇怪:
-<div v-if="cond"></div>
+{#if cond}
+ <div></div>
+{/if}
同理,foreach 也是:
-<div v-for="(item, index) in nav">{{item}}</div>
+{#each nav in item, index}
+ <div>{item}</div>
+{/each}
在 svelte 中,文本变量是通过{}插入的,而不是 vue 中的{{}}。
如果要设置对应的 property,那么可以这么设置:
-<a :href="link"></a>
+<a href={link}></a>
对于特定 class 的设置:
-<div class="A" :class="{ 'hidden': hidden }"></div>
+<div class={["A", hidden ?? 'hidden']}></div>
双向绑定的语法略有不同,Svelte 并没有 v-model 这种通用的的东西:
-<input type="text" v-model="value" />
+<input type="text" bind:value={value} />
-<input type="checkbox" v-model="agree" />
+<input type="checkbox" bind:checked={agree} />
事件语法也不一样:
-<button @click="clickHandler"></button>
+<button onclick={clickHandler}></button>
脚本语法变更
在之前的 vue property decorator 写法里面,需要新建一个类并继承对应的基类。但是在 svelte 中不需要创建类了,所以写法会有一些区别:
-import { Component, Prop, Vue } from "vue-property-decorator";
-@Component({})
-export default class UserRegister extends Vue {
- foo = false;
+let foo = false;
- state = 0;
+let state = $state(0);
- bar() {
- this.state = 1;
- }
+function bar() {
+ state = 1;
+}
同时,对于自定义组件,Svelte 只需要直接 import 就可以了,不需要额外的声明:
+import Foo from "@/component/foo.svelte";
-import Foo from "@/component/foo.vue";
-@Component({
- components: {
- Foo: Foo
- }
-})
对于 Property 的传递,两边有着极大的不同:
-@Prop()
-prop1!: string;
+let { prop1 } = $props();
同时,事件触发传递也有区别:
+let { prop1, event1 } = $props();
-fireEvent() {
- this.$emit("event1");
+function fireEvent() {
+ event1();
}
对于内置的 mounted 和 destroyed 生命周期事件,svelte 也提供了对应的方法:
+import { onMount, onDestroy } from "svelte";
-mounted() {
+onMount(() => {
// do something
-}
+});
-destroyed() {
+onDestroy(() => {
// do something
-}
+});
对于计算属性,svelte 可以使用 derived 和 derived.by 替代:
-get double() {
- return val * 2;
-}
+let double = $derived(val * 2);
+let triple = $derived.by(() => {
+ return val * 3;
+});
你问我 set 怎么办?我也不知道,用下面的 effect 代替吧。
Svelte 中的 effect 方法会监听里面的所有状态变量的变更,可以替代对应的 Watch:
-@Watch("foo")
-onFooChange() {
- this.bar = this.foo;
-}
+$effect(() => {
+ bar = foo;
+});
对于组件的引用,可以使用 bind:this
-<canvas ref="canvas"></canvas>
+<canvas bind:this={canvas}></canvas>
+let canvas: HTMLCanvasElement;
-const ctx = (this.$refs.canvas as HTMLCanvasElement).getContext("2d");
+const ctx = canvas.getContext("2d");
组件迁移
之前肯定安装了一些 vue 的对应组件,这时候你就应该去找找对应的替代品了。比如说 vue-router,你可能需要对应的 svelte5-router
总结
这次其实主要还是一些语法的变更,变更的幅度并不算很大。和 vue 2 升 vue 3 相比,工作量没有差太多。但是,升级完之后,确实感觉好像文件变小+速度变快了,当然也有可能只是我的错觉。
这次的迁移参考了 kowalczyk 博客的文章,它的结论跟我的差不多,迁移都是挺容易的。
我这次其实并没有使用 svelte5-router,因为它不能使用基于 HashTag 的路由,没法满足我的要求。我这里就自己实现了一个非常简单的路由,基于 if 做的,非常暴力,但是它能用。
0 条评论