Svelte 核心语法详解:Vue/React 开发者如何快速上手?
在很多地方早就听到过svelte
的大名了,不少工具都有针对svelte
的配置插件,比如vite
\ unocss
\ svelte
. 虽然还没使用过,但是发现它的star82.9k
数很高哦,学习一下它与众不同的魔法。
这名字有点别扭,好几次都写错。
svelte
是一个web开发框架,它通过编译器编译出可以在浏览器中运行的代码,使用的语言还是前端三剑客html / css /js
。优点就是书写简洁、编译快、效率高。
后记写在前
整体的使用感受感觉还行👍有很多基于使用场景的设计解决方案。集众之长,上手不算困难,书写确实简洁、使用也足够灵活,没有那么多的限制。但是由于版本更新频繁,API并不稳定,版本之间变更比较大,感觉为了简洁就会对API进行非常大的调整,这种框架开发模式有人喜欢、也有人忧愁学习成本变高。但也不可否认,它的设计确实让人然眼前一亮,因为没有过多使用暂时还谈不到它的高效,我想在未来的应用开发中,我也许会选择它来进行开发。
因为它是一个编译时框架,组件必须先编译才能在浏览器中运行,这也就能给它带来一些好处,不受固定平台的限制,可以使用中间件编译为其它系统平台的应用。
初识svelte
当前svelte
的版本是^5.**
,版本发布很是频繁。初始化项目svelte-app
项目,安装
npm add svelte
svelte
是一个编译时框架,它需要先编译后才能在浏览器中运行。svelte
声明组件文件后缀名是*.svelte
,可以从一个简单的组件App.svelte
感受一下组件内容组成结构:
<script>let name = "world";
</script><h1>Hello {name}</h1><style>h1 {color: chocolate;}
</style>
-
标签
script
用于书写js逻辑,可以通过lang='ts'
标记为使用类型typescript书写。
对于js部分,也可以提取到外部文件,文件后缀名为*.svelte.js
let name = "world";
-
标签
style
用于书写样式, -
对于html结构代码可以直接书写。
不同于vue / react
可以直接引入到html中使用,svelte
需要编译打包后才能引入到html中。所以我们不能直接使用App.svelte
组件,我们写一个简单的编译逻辑,执行编译完成后看看生成的代码。
创建compile.js
编译 App.svelte
,svelte/compiler
包提供了用于编译.svelte
文件的方法。
此处可查看包的文档说明👈
compile
方法用于编译.svelte
编译文件导出一个js模块。compileModule
方法用于带有响应式变量的.svelte.js
文件,编译后导出一个js模块。
import { compile as SvelteCompile } from "svelte/compiler";
import fs from "node:fs";// 读取.svelte文件内容
const source = fs.readFileSync("./src/App.svelte", "utf-8");// 编译输出组件内容
const result = SvelteCompile(source, {name: "App",
});// 写入文件 js
fs.writeFileSync("./dist/bundle.js", result.js.code);
// css
fs.writeFileSync("./dist/bundle.css", result.css.code);
通过使用compile
方法编译,看看编译后的产物bundle.js
:
import 'svelte/internal/disclose-version';
import 'svelte/internal/flags/legacy';
import * as $ from 'svelte/internal/client';var root = $.template(`<h1 class="svelte-13s5wg8"></h1>`);export default function App($$anchor) {let name = "world";var h1 = root();h1.textContent = 'Hello world';$.append($$anchor, h1);
}
js内容是esm格式,并且导入了svelte/internal
模块,要想直接引入到html中使用是不可能的。需要通过svelte
提供的挂载方法,再通过打包工具来对编译过的js文件再进行一次编译打包,这里使用rspack
,当然也可以使用其他打包工具。
svelte
版本 4 中可以配置输出的内容格式,可以配置format:"iife"
输出的内容可以在html中直接使用。最新版本5
仅仅输出esm格式的内容了。
npm add @rspack/core @rspack/cli -D
使用rspack
对我们编译过的svelte内容在进行一次依赖关系构建,使得可以在html引入使用。我们将组件挂载到元素#app
上,增加入口文件entry.js
import App from "./dist/bundle.js";
import { mount } from "svelte";mount(App, {target: document.getElementById("app"),
});
我们不能直接使用App.svelte
编译的产物,需要使用svelte
提供的挂载方法mount
指定挂载元素。通过npx rspck
指定入口文件进行构建
npx rspack --entry entry
执行完成后,可以在dist
看到生成的产物mina.js
,我们在index.html
中直接引入即可。通过静态服务器访问html,可以看到页面正常渲染。
对于使用rspck
更复杂的构建,可以通过配置文件rspack.config.js
配置构建配置项。
我们仅处理了产物js
的构建,并未处理css
,感兴趣的可以自行配置实现。
实现一个简易的svelte-loader
解析.svelte
文件
为了方便后续的API测试,我们实现一个简单的svelte-loader
,使得rspack
可以解析.svelte
文件。
增加rspack.config.js
配置文件,关于rspack
可以查看往期文章
- 💥vue-cli项目升级rsbuild,效率提升50%+
rspack 使用构建vue3脚手架这篇文章发布时间较早,可能已经没有参考价值了。
import { defineConfig } from "@rspack/cli";export default defineConfig({entry: {index: "./src/main.js",},
});
main.js
不同于entry.js
,无需提前编译.svelte
文件,直接引入即可。
import { mount } from "svelte";
import App from "./App.svelte";mount(App, {target: document.getElementById("app"),
});
我们现在启动项目npx rspack dev
会报错,无法解析App.svelte
文件,增加解析规则,支持解析.svelte
文件。
我们在目录loaders/svelte-loader.cjs
创建一个loader,这里我们仅处理编译之后的js
内容。
const { compile: SvelteCompile } = require("svelte/compiler");module.exports = function (source) {let result = SvelteCompile(source, {});return result.js.code;
};
修改rspack
配置文件rspack.config.js
增加loader配置,支持解析.svelte
文件。
import { defineConfig } from "@rspack/cli";export default defineConfig({entry: {index: "./src/main.js",},module: {rules: [{test: /\.svelte$/,loader: "./loaders/svelte-loader.cjs",},],},
});
为了我们可以在浏览器中查看视图,还需要设置html模板,使用rspack
内部的插件HtmlRspackPlugin
,并指定我们自己的模板文件plubic/index.html
export default defineConfig({// ... otherplugins: [new rspack.HtmlRspackPlugin({title: "Svelte App",template: "./public/index.html",}),],
});
启动服务,访问 http://localhost:8080
可以看到页面正常渲染了。我们实现了一个简单的.svelte
文件编译loader提供给rspack
使得svelte
文件可以被正常编译。
Runes
符文
svelte
的核心概念,具有神秘魔法的标记。前缀通常为$
,可以在.svelte
或.svelte.js
中使用。
$state
响应式声明
$state
用于声明响应式状态。包括基本类型、数组、对象、set、map等。数组操作方法可以触发更新;对象深度代理,修改属性值会触发更新。
<script>let name = $state("world");
</script><h1>Hello {name},Svelte!</h1>
<button onclick={() => (name = "hboot")}>change</button>
我们再来编译一下文件,查看是如何处理编译响应式变量的。相对于无状态,则对响应式变量做了更多的处理,将$state
转换为了$.state()
;在操作变量时,通过$.set()
来更新变量,通过$.get()
来获取变量;
对于引用变量的DOM结构则提取出来通过副作用函数$.template_effect()
建立了状态依赖关系,可以在状态更新时重新生成DOM并渲染。
import 'svelte/internal/disclose-version';
import * as $ from 'svelte/internal/client';var on_click = (_, name) => $.set(name, "hboot");
var root = $.template(`<h1 class="svelte-13s5wg8"> </h1> <button>change</button>`, 1);export default function App($$anchor) {let name = $.state("world");var fragment = root();var h1 = $.first_child(fragment);var text = $.child(h1);$.reset(h1);var button = $.sibling(h1, 2);button.__click = [on_click, name];$.template_effect(() => $.set_text(text, `Hello ${$.get(name) ?? ''},Svelte!`));$.append($$anchor, fragment);
}$.delegate(['click']);
对于数组、对象的深度监听,往往会带来性能问题,如果我们不需要深度监听,则可以使用$state.raw()
来创建响应式变量,这样创建的变量在改变内部值时不会触发更新。
let user = $state.raw({name: "hboot",email: "",
});// 不会触发更新
user.name = "svelte";// 整个变量重新赋值会触发
user = {name: "svelte",email: "",
};
$derived
派生状态
$derived
可以创建派生值,可以从一个响应式变量计算得到另一个值。相当于vue的computed
计算值。
let name = $state("world");let welcome = $derived(`Good ${name}`);
$derived
可以接受一个简单的表达式,也可以通过$derived.by()
接受一个函数来处理复杂的计算。为什么不直接接受一个函数呢?🧐
let welcome = $derived.by(() => `Good ${name}`);
当依赖的状态更新后,svelte
不会立马触发derived
的计算,而是标记它,等下次有地方读取时再重新计算。
$derived
派生的值可以直接修改,这在版本>5.25
才支持。可以预先设置值,这对于一些耗时的计算可以提前给出反馈。这一点很有意思🤩
$effect
函数
$effect
可以执行副作用函数。包括调用第三方库、操作DOM、接口请求等。这和reactuseEffect
hook 概念一样,但是它可以自动收集在其内部使用的响应式状态,无需手动声明。在svelte
中$effect
可以在任何地方使用,不局限于组件顶层;不局限于script
内,在模板语法中也可以使用。
在
$effect
内部更新state要谨慎,避免死循环。
$effect
接收一个回调函数用于执行副作用逻辑,该回调函数可以返回一个清理函数。这个清理函数会在$effect
重新执行或者组件销毁时自动调用执行。
$effect(() => {let timer = setInterval(() => {console.log(name);}, 1000);return () => {clearInterval(timer);};
});
现在已知的响应式状态来源包括$state
和$derived
创建,后续还有一个$props
来自于父组件的响应式变量。都会被$derived
探测收集。异步任务比如setTimeout
或者await
之后的响应式变量是不会被收集的,也就是它们更新不会触发$effect
的执行。
$effect
更新的时机是在DOM更新之后,它会合并同一时间变更的响应式变量只调用一次。$effect
的执行是异步的。
对于对象类型的$state
响应式变量,更改对象属性不会触发$effect
的执行。但是$derived
派生的对象会被执行,因为派生每次都会重新计算,得到的是不同的对象。
对于在$effect
内部使用了条件判断的语句中存在的响应式变量,这会根据条件分执行决定是否收集该变量
let bool = $state(false);$effect(() => {if (bool) {console.log(name);} else {console.log("nothing");}
});
如果bool
为true,则name
会被收集,name
的更新也会触发$effect
的执行;如果bool
为false,则name
不会被收集,仅在bool
更新时触发$effect
的执行。
$effect.pre()
提供了更早的更新时机,可以在DOM更新之前执行。
$props
父传子参数
父子组件传参很常见,通过$props()
接收来自父组件的参数。
let props = $props();
父组件通过{ }
绑定需要传递的参数。
<script>import User from "./views/user.svelte";
</script><User {name} />
强烈不建议直接更改$props
,$svelte
提供了一种方式用于更新props,在下一节可以通过bindable
$props.id()
可以生成当前组件实例的唯一ID,可以用于服务端、客户端之间的组件关联。
$bindable
双向绑定
为了合理的更新来自父组件的props变量,svelte
提供了一种机制,通过$bindable()
标记的变量,可以在父组件通过bind
语法接受来自组件对于变量的更新。
<script>let { name = $bindable() } = $props();
</script>
在父组件如果不需要监听来自子组件的更新,也可以不需要使用bind
.
<User bind:name />
$bindable()
可以接收一个值,用于默认值。当父组件没有传递值时,$bindable()
会返回默认值。
$inspect
跟踪变化
仅在开发模式下有用,用于跟踪输出响应式变量的值,可以替代console.log
。
<script>let { name = $bindable() } = $props();$inspect(name);
</script>
$inspect().with((type,value)=>fn)
可以覆盖默认的输出行为,with
接收的第一个参数type
,值为init \ update
,回调函数将会在跟踪的值更新时被调用。
<script>$inspect().with((type,value)=>{// 自定义输出逻辑})
</script>
$inspect.trace()
可以展示当前执行的跟踪栈。可以用于$effect()
和$derive()
,重新执行时可以看到是由哪些变量引起的。
<script>$effect(() => {// 执行时,输出执行的$inspect.trace();});
</script>
模板语法
一个比较重要的框架能力就是模板语法了,对于前端开发这是否亲和,是否更容易上手使用。
通常建议html标签都是用小写的div / p
等。对于用户自定义组件则使用驼峰书写MyComponent
。
<User name="hboot" {...userInfo} />
绑定属性时,可以通过{...}
将多个属性传递给组件。按照传递的顺序,如果属性有冲突,则后面的属性会覆盖前面的属性。
#if
条件渲染
{#if condition} .. {:else if condition} .. {:else} .. {/if}
里处理条件渲染。
{#if name == "hboot"}<User bind:name />
{/if}
#each
循环渲染
{#each array as item,index} .. {/each}
里处理循环渲染。
{#each [1, 2, 3, 4] as item}<p>{item}</p>
{/each}
获取数组下标index
.
{#each [1, 2, 3, 4] as item,index}<p>{item}-{index}</p>
{/each}
为每个元素绑定key
,需要在语法后加标识(key)
可以。这个挺让人意外的,不是绑定在元素上,而是声明在循环表达式中。
{#each [1, 2, 3, 4] as item,index (item)}<p>{item}-{index}</p>
{/each}
如果需要渲染某个视图多次,提供了简洁的写法。 👍
{#each { length: 5 }, name}<p>{name}</p>
{/each}
对于渲染的数组如果是空情况下提供了语法渲染占位 :else
👍
{#each [1, 2, 3, 4] as item}<p>{item}</p>
{:else}<p>no data</p>
{/each}
#key
标记渲染
{#key expression} ... {/key}
标记一块区域在表达式的值发生变更时,内部的节点会重新渲染。
感觉这个API也不错,手动控制渲染更加灵活 👍👍👍
#await
异步渲染
{#await promise} ... {:then} ... {:catch} ... {/await}
异步渲染,在promise
执行的各个阶段渲染不同状态下的UI。
这对于异步组件或者耗时任务封装组件也很方便。避免了手动控制状态渲染,意图更清晰。
如果不关注pending状态,还可以简写为:{#await promise then value} ... {:catch} ... {/await}
{#await import("./User.svelte")}<span>loading...</span>
{:then { default: User }}<User />
{:catch error}<span>error</span>
{/await}
#snippet
渲染片段
{#snippet name(params)} ... {/snippet}
可以创建一个代码片段,然后通过{@render name(image)}
来渲染,可以实现UI复用。
{#snippet list(item)}<p>{item.name}</p>
{/snippet}{#each UserList as item}{@render list(item)}
{/each}
{#each NoticeList as item}{@render list(item)}
{/each}
渲染片段不仅可以使用传递进去的参数,也可以使用最外层声明的变量,比如script
标签里的变量。它们也有自己的作用域,在当前标签中声明时,仅对当前标签以及子节点有效。
<div>{#snippet avatar(url)}<img src={url} alt="avatar" />{/snippet}
</div><!-- 这个是错的,它访问不到div元素中创建的片段 -->
{@render avatar(userInfo.avatUrl)}
可以作为组件的props传递,这相当于web中的slot概念,但是更加的灵活。
<div>{#snippet avatar(url)}<img src={url} alt="avatar" />{/snippet}<User {avatar} />
</div>
还有一个隐形传递内容的方式,而无需使用$snippet
创建,就是在标签中的内容,他可以直接在子组件通过children()
访问。有点像默认的slot.
<script>let { children } = $props();
</script><div>{@render children()}
</div>
所以最好避免使用children
作为props名传递参数。如果不确定是否传递了默认内容,可以通过{@render children?.()}
渲染或者通过{#if children}
条件判断。
还可以在<script module>
文件导出声明的片段渲染。这对于一些公共的渲染逻辑提取很有用。
<script module>export { NavSider };
</script>{#snippet NavSider()}<p>渲染左侧菜单</p>
{/snippet}
感觉这比slot
要灵活的多,覆盖了很多使用常见,组件内复用,跨组件复用,父传子复用等。👍👍👍
@render
渲染代码片段
渲染由$snippet
创建的代码片段。
@html
渲染HTML片段
一个常用的场景可以渲染html片段。
<div>{@html content}</div>
因为在 .svelte
文件中,style
定义的样式具有作用区域限制。而渲染的html不会被svelte检测,所以样式不会生效,需要增加:global
<style>.html-container :global {p {color: red;}}
</style>
@attach
DOM附件
{@attach fn()->callback}
可以绑定到DOM节点上,在DOM节点渲染完后调用执行。返回一个回调函数callback
在附件函数执行之前执行;也会在DOM节点卸载后执行。
<script>let divAttch = (element) => {// DOM节点渲染后执行return () => {// 卸载};};
</script>
<div {@attach divAttch}>content</div>
如果函数内部存在响应式变量的使用,那么svelte
会收集并在它们更新时重新执行附件函数fn
<script>let divAttch = (element) => {element.textContent = name;return () => {// 卸载};};
</script>
有时候函数内部逻辑太多,避免重复执行,我们可以将有使用了响应状态的逻辑使用$effect
包裹,这样只有$effect
内部的逻辑会由响应式状态更新而触发执行。外部的逻辑则仅会执行一次,不会重复执行。
<script>let divAttch = (element) => {console.log("divAttch", element);$effect(() => {element.textContent = name;});return () => {// 卸载};};
</script>
附件函数的定义可以行内定义,也就是在模板中定义逻辑。而无需在script
中声明一个方法。
#const
模板中的变量定义
在使用模板语法时如果需要定义变量,可以使用{#const variableName = value}
定义,可以关注一下使用地方限制,比如可以在块中#if
#each
等语法中使用。
#debug
标记断点
通过{#debug variable1,variable2,...}
标记指定变量在更新时启动断点,以便查看更新情况。
{@debug name}
bind:
双向绑定
在前面的svelte
符文$bindable
中简单介绍了它的作用,可用于子组件更新父组件的数据。它的实现是同时给元素绑定了一个事件监听器来更新绑定值,如果不小心绑定了同名的监听事件,手动绑定的事件会先于更新值之前触发。
<User bind:name />
还可以通过绑定回调函数显示定义get/set
函数,以便我们实现对绑定值的处理bind:variable={ setFn, getFn}
。
<User bind:name={() => name, (val) => (name = val)} />
可以通过设置setFn
为null
,从而实现绑定值只读,这对于一些元素属性不可设置时有用,比如DOM元素的clientWidth
双向绑定对于form表单来说比较重要,业务中永远存在form表单,那对于form组件的绑定便捷性则尤为重要。
<!-- input -->
<input bind:value={name} defaultValue="hboot" />
<!-- input number-->
<input bind:value={total} type="number" defaultValue={100} min={0} max={200} />
<!-- input checkbox -->
<input bind:checked type="checkbox" defaultChecked={true} />
<!-- input select -->
<select bind:value={name}><option value="hboot">hboot</option><option value="admin" checked>admin</option><option value="test">test</option>
</select>
<!-- input radio -->
<input bind:group={names} type="radio" value="hboot" />
<input bind:group={names} type="radio" value="admin" />
<!-- input files-->
<input bind:files multiple type="file" />
对于原生的html标签的属性,都可以通过bind:
去实现双向绑定控制,如果有多个属性可控制,则可以同时绑定。而对于只读的属性通过bind:
绑定时,默认就是只读的,而无需再次设置只读处理。
想要获取DOM的实例或者自定义组件的实例,可以通过bind:this
绑定获取,对于获取到自定义组件的实例后,可以访问到子组件export
导出的方法。
<script>let divRef = null;$effect(() => {console.log(divRef.getBoundingClientRect());});
</script><div bind:this={divRef}></div>
获取元素实例需要等待挂载后才能获取到,所以我们可以在$effect
执行对于DOM元素的操作。
transition:
过程动画
当DOM元素进入/离开时追加的动画效果,svelte/transition
子包提供了一些常见的转换效果。
<script>import { fade } from "svelte/transition";
</script>{#if name == "hboot"}<div transition:fade><User /></div>
{/if}
仅能使用到DOM节点,不能应用到自定义组件,因为自定义组件没有根部元素。默认元素的转换效果仅在当前块作用域中有效,也就是当有多层{#if ...}
嵌套时,只做用于就近的条件渲染。可以通过gloabl
设置对父级节点条件显隐时也触发效果。
{#if total > 50}{#if name == "hboot"}<div transition:fade|global><User /></div>{/if}
{/if}
可以通过定义参数对转换效果自定义设置,比如针对fade
可设置参数
<div transition:fade|global={{duration:500, delay: 200, easing: easingFn}}><!-- inner element -->
</div>
不仅可以覆盖默认的参数设置外,还可以自定义转换效果的实现,自定义函数的实现返回值类型必须是TransitionConfig
interface TransitionConfig {delay?: number,duration?: number,easing?: (t: number) => number,css?: (t: number, u: number) => string,tick?: (t: number, u: number) => void
}
自定义函数的参数固定格式(node: HTMLElement, params: any, options: { direction: 'in' | 'out' | 'both' })
node
是当前绑定的DOM节点;params
是绑定的自定义参数;options
可以指定转换效果生效的模式,默认进入/离开都执行;可以指定仅进入或仅离开执行效果。也可以分别指定进入/离开不同的转换效果。
{#if name == "hboot"}<div in:myFade><User /></div>
{/if}
animate:
变化动画
和transition:
不同的是它针对的是列表渲染,因顺序变化而重排实现的动画效果。内置动画可从svelte/animate
查看。
<script>import { flip } from "svelte/animate";
</script>{#each [1, 2, 3, 4] as item (item)}<p animate:flip>{item}</p>
{:else}
列表渲染时必须设置唯一键值key
,也可以自定义动画实现,自定义动画函数固定的参数及响应值
const animatieFn = (node: HTMLElement, { from: DOMRect, to: DOMRect } , params: any) => {delay?: number,duration?: number,easing?: (t: number) => number,css?: (t: number, u: number) => string,tick?: (t: number, u: number) => void
}
style:
样式绑定
可以直接在DOM元素上设置style=""
属性,也可以通过style:
指令绑定样式,绑定变量值。
<h2 style="font-size:26px;" style:color="red">{welcome}</h2>
要标记某个样式的权重,可以增加|important
<h2 style:color|important="red">{welcome}</h2>
指定style:
绑定的样式优先级高于属性style
设置的样式。
class:
类名绑定
除了常规的字符串、数组、对象绑定到属性class
,值得关注的是指令class:
可以直接绑定变量类名
<button class:buttonPrimary>change</button>
style
样式定义
在.svelte
文件中可以通过<style>
标签定义样式,默认样式具有作用域的,也就是只针对当前文件里的类名生效,可以看一下编译后的样式。
<style>h1 {color: chocolate;}
</style>
执行编译后生成的css文件。可以看到追加了.svelte-13s5wg8
这是编译后追加的类名,在所有<h1>
标签上会增加类名svelte-13s5wg8
h1.svelte-13s5wg8 {color: chocolate;}
要想全局样式生效,可以使用:global()
指定某个类名适用全局;使用:global {...}
定义一组类样式适用全局。
<style>.html-container :global {p {color: red;}h1 {font-weight: normal;}}
</style>
对于自定义动画帧@keyframes
名称也会被修改作用限制,可以追加前缀-global-
保证全局生效,使用时则忽略-global-
.svelte
编译时会移除掉。这一块会让人感觉有点疑惑🧐
<style>
不仅可以在顶部声明使用,还可以出现在元素块或逻辑中。嵌套的<style>
没有作用域的限制,会全局生效,所以不建议这样写。
<h1><style>h1 {color: blue;}p {color: green;}</style>Hello {name}
</h1>
内置元素
svelte
内置了一些元素用于处理一些特殊场景下的问题。
<svelte:boundary>
错误边界
<svelte:boundary>
可以捕获内部子组件渲染中发生的错误,提供了两种方式帮助我们处理渲染错误
{#snippet failed(error, reset)}
可以提供failed
命名的错误处理片段函数,当渲染错误时会调用并渲染该片段。函数有两个参数,第一个参数是错误对象,第二个参数是重新渲染的函数。
<svelte:boundary><User bind:name />{#snippet failed(error, reset)}<button onclick={() => reset()}>Oops, Try again</button>{/snippet}
</svelte:boundary>
第二种方式是监听错误事件onerror
,接收参数和failed
一样,可以通过监听方法上报错误信息。
<script>const onerror = (error, reset) => {console.error("Error occurred:", error);// 可以在这里处理错误// 比如重置状态或显示错误信息};
</script><svelte:boundary {onerror}><User bind:name />
</svelte:boundary>
<svelte:window>
访问window对象
可以通过在组件使用<svelte:window>
来监听window对象上的事件,或者通过bind:
获取window对象的属性。
<svelte:window onresize={handleResize} />
与之类似的还有<svelte:document>
和 <svelte:body>
,它们必须在组件最外层使用,不能嵌套或块中使用。
<svelte:head>
定义头部信息
通过<svelte:head>
可以设置页面的头部信息,比如<title>
,<meta>
,<link>
等等。
<svelte:head><title>Svelte App</title>
</svelte:head>
<svelte:element>
动态替换标签
<svelte:element>
可以动态替换标签,比如<svelte:element this="div">
可以替换成<div>
。
也可以使用非标准的标签,为了使得svelte
识别它,可以指定xmlns
属性显示设置。
<svelte:options
编译配置项
通过<svelte:options>
标签,可以配置组件在编译时的编译选项。
runes={true}
设置是否启用符文模式,启用后不再兼容svelte
3和4.namespace="..."
标签命名空间,默认是html
,可选svg
和mathml
。customElement = {...}
自定义标签配置项,可以是字符串类型表示标签名;也可以通过对象配置其他属性。css="injected"
将组件样式注入到js中。
生命周期
在svelte
中最小更新单元不是组件,所以组件的生命周期只关注于创建和销毁。因为更新内部状态可能并不会引起组件视图的更新,所以不存在更新前后的钩子。这点和react
不同,又和vue
有点像,状态依赖收集,仅在需要更新视图时更新。
onMount
挂载
svelte
组件DOM挂载完成后调用。可以接受一个返回值函数,在组件卸载的时候调用。
<script>import { onMount } from 'svelte';onMount(() => {console.log("mounted");return () => {console.log("unmounted");};});
</script>
onDestroy
卸载
svelte
组件卸载时调用。
tick
更新完成
有时候我们需要等待视图更新完成再执行某些操作。可以使用tick()
,它返回一个Promise
在UI更新完成之后会被调用。
在$effect
中,提供了.pre()
可以监听到DOM更新之前调用,搭配tick()
<script>import { tick } from "svelte";$effect.pre(() => {console.log("dom update");tick().then(() => {console.log("dom update done");});});
</script>
数据共享
除了父子组件通过$props
传递共享数据之外,常见的还有跨组件的数据共享。跨组件的数据共享又可以分为两种,有嵌套关系的组件数据共享和无关系的组件数据共享。
svelte
提供了上下文Context
允许后代组件共享来自父级提供的上下文数据。
setContext(key,value)
设置上下文数据。getContext(key)
获取上下文数据。hasContext(key)
判断是否存在指定的数据。getAllContexts()
获取所有上下文数据。
在上级组件App.svelte
中设置数据
<script>import { setContext } from "svelte";setContext("name", "hboot");
</script>
在后代组件About.svelte
组件中获取数据,它们之间的关系About->User->App
成嵌套关系。
<script>import { getContext } from "svelte";let name = getContext("name");
</script><h4>About! {name}</h4>
运行项目可以看到展示数据为顶层组件设置的数据。
使用响应式数据以便得到数据的同步更新,目前仅支持对象类型的响应式数据,不能是基本类型和数组。使用其他类型时会有warn
提示,而且数据也不会响应式更新。
<script>let userInfo = $state({name: "svelte",});setContext("userInfo", userInfo);
</script>
为了保持共享数据的响应式链接,对于更新状态userInfo
,不能直接赋值一个对象,需要指定更新某一属性,比如:userInfo.name = "hboot"
为了更好的组织跨组件数据共享,可以将这部分提取到.svelte.js
文件,然后在需要的组件导入使用即可
import { setContext, getContext } from "svelte";const userContextId = Symbol("userInfo");export default {get() {return getContext(userContextId);},set(data) {setContext(userContextId, data);},
};
这样我们提供了统一的地方用于获取、设置共享的用户数据,还可以针对数据做权限验证控制等操作。对于使用typescript
时的数据类型验证可以更方便的管理。
非关系组件数据共享
对于非嵌套关系的组件,可以使用响应式$state
在外部.svelte.js
中声明响应式状态,然后在使用的组件导入。比如创建一个userStore.svelte.js
/*** 共享用户信息*/
export const userStore = $state({name: "hboot",
});
在需要的组件中导入使用,我们可以在user.svelte
组件中设置数据;然后在home.svelte
中使用它。它们的关系是同级,都在App.svelte
中渲染
<script>import { userStore } from "./store.js";$effect(() => {userStore.name = "Admin";});
</script>
直接设置共享值,在需要的地方导入访问即可实现共享,并同步更新响应值。可以看到我们创建的响应式数据类型是对象,如果是基础数据类型,svelte
无法实现数据的响应式更新。对于更新,也不能直接赋值整个对象。
为了处理不能是基础类型的麻烦,svelte
提供一个实现最小存储状态的方案svelte/store
,通过包装变量,并提供操作方法,实现数据响应式更新。
svelte/store
是一个完整的数据状态管理方案,当直接使用符文$state
无法满足数据存储时,可以选择使用。
svelte/store
最小存储实现
svelte/store
提供了按照最简单约定实现数据存储共享。在组件内访问数据时,需要使用$
前缀,它定义了访问数据时采取订阅模式,这也导致在svelte
声明变量时最好不要使用$
前缀来声明变量。
相较于使用符文$state
来共享数据,Stores
提供了控制更新值的方法,可以监听到数据变化的事件。如果你需要这些能力,选择Stores
则更适合。
我们重新定义之前的userStore.svelte.js
,通过svelte/store
提供工的API可以直接定义基本类型变量。
import { writable } from "svelte/store";export const userName = writable("Store hboot");// 订阅变化
userName.subscribe((val) => {console.log("The store userName changed:", val);
});
在需要使用的组件中导入使用,导入到Home.svelte
组件进行展示。使用之前必须加前缀$
,因为导入的userName
是一个对象,挂载了一些其他的操作方法。使用$userName
访问,svelte
会解析并自动订阅Store
数据,监听变化以便实时更新数据。
<script>import { userName } from "../stores/userStore.svelte.js";
</script><h4>Home! {$userName}</h4>
对于更新值,在实例化创建store
后,导入的对象提供了两个方法set
和update
. set
直接接受一个值用于更新(比较是否相等);update
接受一个回调函数(oldVal)=>newVal
它有一个参数是当前变量值,函数返回一个值作为变量的新值被设定。
<script>import { userName } from "../stores/userStore.svelte.js";$effect(() => {setTimeout(() => {userName.set("Store Admin");}, 4 * 1000);});
</script>
.subscribe()
订阅方法会在初始化时调用一次。
writable(val,callback)
还接受一个可选参数callback
函数,函数有两个参数set
和update
,同上可用于更新状态值。该回调函数用于在有人订阅该变量时触发调用一次(从0-1的变化)。该回调函数返回一个停止订阅的函数(从1-0的变化)触发调用。
readable
外部只读共享状态
svelte/store
提供了外部只读共享数据状态,不允许外部更改。通过readable
定义,用法和writable
一样,区别就是实例对象上没有set
和update
方法。仅可通过readable
的第二个回调函数去修改状态值。
import { readable } from "svelte/store";export const userAge = readable(18);
derived
派生状态
derived
派生状态,类似于计算值与之前符文的$derived
概念一样,不同之处在于它依赖的是共享数据状态变量,并且需要在第一个参数指定依赖的变量;第二个参数回调函数callback
参数为依赖的变量值,返回值作为派生状态的值。
import { derived } from "svelte/store";export const welcomeUser = derived(userName, (name) => {return `Welcome ${name}`;
});
回调函数可选的第二个参数set
和第三个参数update
用于手动更新值。
export const welcomeUser = derived(userName, (name, set) => {set(`Welcome ${name}`);
});
因为set
和update
执行是异步的,派生值初始值可能会是undefined
,所以derived
接受第三个参数设置默认值initialValue
。
export const welcomeUser = derived(userName,(name, set) => {set(`Welcome ${name}`);},"Welcome User"
);
derived
可以通过第一个参数为数组类型指定多个依赖项,则接受依赖值的回调函数的第一个参数也是数组访问依赖值。
export const welcomeUser = derived([userName, userAge],([name, age], set) => {set(`Welcome ${name}. You are ${age} years old.`);}
);
readonly
只读共享状态
readonly
使得共享变量变为只读状态,它不能被修改,但是仍可以共享数据变化。不同于readable
,它没有任何可以修改的方法,只能由原始store
变量自己去修改。
export const userNameReadonly = readonly(userName);
参考
svelte docs
svelte example
rspack-cli
相关文章:
Svelte 核心语法详解:Vue/React 开发者如何快速上手?
在很多地方早就听到过svelte的大名了,不少工具都有针对svelte的配置插件,比如vite \ unocss \ svelte. 虽然还没使用过,但是发现它的star82.9k数很高哦,学习一下它与众不同的魔法。 这名字有点别扭,好几次都写错。 sve…...
Fullstack 面试复习笔记:HTML / CSS 基础梳理
Fullstack 面试复习笔记:HTML / CSS 基础梳理 之前的笔记: Fullstack 面试复习笔记:操作系统 / 网络 / HTTP / 设计模式梳理Fullstack 面试复习笔记:Java 基础语法 / 核心特性体系化总结Fullstack 面试复习笔记:项目…...

408第一季 - 数据结构 - 树与二叉树II
二叉树的先中后序遍历 理解 那主播,请问你有没有更快的遍历方式呢 有的,兄弟有的 以中序遍历为例啊 找左边有没有东西,左边没东西那它就自由了,就按上面的图举例子 A左边有东西,是B,B左边没东西…...
打卡第47天
作业:对比不同卷积层热图可视化的结果 核心差异总结 浅层卷积层(如第 1-3 层) 关注细节:聚焦输入图像的边缘、纹理、颜色块等基础特征(例:猫脸的胡须边缘、树叶的脉络)。热图特点:区…...

从上下文学习和微调看语言模型的泛化:一项对照研究
大型语言模型表现出令人兴奋的能力,但也可以从微调中表现出令人惊讶的狭窄泛化。例如,他们可能无法概括为简单的关系反转,或者无法根据训练信息进行简单的逻辑推理。这些未能从微调中概括出来的失败可能会阻碍这些模型的实际应用。另一方面&a…...

智慧城市建设方案
第1章 总体说明 1.1 建设背景 1.2 建设目标 1.3 项目建设主要内容 1.4 设计原则 第2章 对项目的理解 2.1 现状分析 2.2 业务需求分析 2.3 功能需求分析 第3章 大数据平台建设方案 3.1 大数据平台总体设计 3.2 大数据平台功能设计 3.3 平台应用 第4章 政策标准保障…...
phosphobot开源程序是控制您的 SO-100 和 SO-101 机器人并训练 VLA AI 机器人开源模型
一、软件介绍 文末提供程序和源码下载 phosphobot开源程序是控制您的 SO-100 和 SO-101 机器人并训练 VLA AI 机器人开源模型。 二、Overview 概述 🕹️ Control your robot with the keyboard, a leader arm, a Meta Quest headset or via API 🕹️…...

pygame开发的坦克大战
使用Python和Pygame开发的精美坦克大战游戏。这个游戏包含玩家控制的坦克、敌方坦克、各种障碍物、爆炸效果和完整的游戏机制。 游戏说明 这个坦克大战游戏包含以下功能: 游戏特点 玩家控制:使用方向键移动坦克,空格键射击 敌人AI&#x…...
C++2025.6.7 C++五级考题
城市商业街主干道是一条笔直的道路,商业街里有 n 家店铺,现给定 n 个店铺的位置,请在这条道路上找到一个中心点,使得所有店铺到这个中心点的距离之和最小,并输出这个最小值。 #include <bits/stdc.h> using nam…...
【原神 × 二叉树】角色天赋树、任务分支和圣遗物强化路径的算法秘密!
【原神 二叉树】角色天赋树、任务分支和圣遗物强化路径的算法秘密! 作者:星之辰 标签:#原神 #二叉树 #天赋树 #任务分支 #圣遗物强化 #算法科普 发布时间:2025年6月 总字数:6000+ 一、引子:提瓦特大陆的“树型奥秘” 你是否曾留意过《原神》角色面板的天赋树? 升级技能…...

功能安全实战系列09-英飞凌TC3xx LBIST开发详解
本文框架 0. 前言1.What?1.1 基本原理1.1.1 检测范围1.1.2 LBIST与锁步核对比1.1.3 控制寄存器1.2 关联Alarm2. How?2.1 LBIST触发?2.1.1 SSW配置自动触发2.1.2 软件手动触发LBIST2.2 实现策略2.3 测试篇LBIST对启动时间的影响如何确定当前LBIST是否已使能?如何确定当前LBI…...

一个完整的日志收集方案:Elasticsearch + Logstash + Kibana+Filebeat (二)
📄 本地 Windows 部署 Logstash 连接本地 Elasticsearch 指南 ✅ 目标 在本地 Windows 上安装并运行 Logstash配置 Logstash 将数据发送至本地 Elasticsearch测试数据采集与 ES 存储流程 🧰 前提条件 软件版本要求安装说明Java17Oracle JDK 下载 或 O…...

RT-Thread内核组成——内核移植
内核移植就是指将 RT-Thread 内核在不同的芯片架构、不同的板卡上运行起来,能够具备线程管理和调度,内存管理,线程间同步和通信、定时器管理等功能。移植可分为 CPU 架构移植和 BSP(Board support package,板级支持包&…...
Docker_Desktop开启k8s
Docker_Desktop开启k8s 原文地址:在 Docker Desktop 中启用 K8s 服务 - 墨墨墨墨小宇 - 博客园 开启k8s服务 打开docker的设置界面,选择Docker Engine,修改如下: {"debug": false,"experimental": false,…...
MS2691 全频段、多模导航、射频低噪声放大器芯片,应用于导航仪 双频测量仪
MS2691 全频段、多模导航、射频低噪声放大器芯片,应用于导航仪 双频测量仪 产品简述 MS2691 是一款具有 1164MHz 1615MHz 全频段、低功耗的低噪声放大器芯片。该芯片通过对外围电路的简单配置,使得频带具有宽带或窄带特性。支持不同频段的各种导…...

基于Java(SpringBoot、Mybatis、SpringMvc)+MySQL实现(Web)小二结账系统
结账系统 1.引言 1.1.编写目的 此说明书在概要设计的基础上,对小二结账系统的各个模块、程序分别进行了实现层面上的要求和说明。在以下的详细设计报告中将对在本阶段中对系统所做的所有详细设计进行说明。在本阶段中,确定应该如何具体的实现所要求的…...
Java泛型中的通配符详解
无界通配符 通配符的必要性 通过WrapperUtil类的示例可以清晰展示通配符的使用场景。假设我们需要为Wrapper类创建一个工具类WrapperUtil,其中包含一个静态方法printDetails(),该方法需要处理任意类型的Wrapper对象。最初的实现尝试如下: …...
Java方法引用深度解析:从匿名内部类到函数式编程的演进
文章目录 前言问题场景第一种:传统的匿名内部类技术解析优缺点分析 第二种:Lambda表达式的革命技术解析Lambda表达式的本质性能优势 第三种:方法引用的极致简洁技术解析 方法引用的四种类型1. 静态方法引用2. 实例方法引用3. 特定类型的任意对…...

三维GIS开发cesium智慧地铁教程(4)城市白模加载与样式控制
一、添加3D瓦片 <!-- 核心依赖引入 --> <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"><!-- 模型数据路径 --> u…...

越狱蒸馏-可再生安全基准测试
大家读完觉得有帮助记得关注!!! 摘要 大型语言模型(LLMs)正迅速部署在关键应用中,这引发了对稳健安全基准测试的迫切需求。我们提出了越狱提炼(JBDISTILL),这是一种新颖…...

64、js 中require和import有何区别?
在 JavaScript 中,require 和 import 都是用于模块导入的语法,但它们属于不同的模块系统,具有显著的区别: 1. 模块系统不同 require 属于 CommonJS 模块系统(Node.js 默认使用)。 语法:const…...

手机号段数据库与网络安全应用
手机号段数据库的构成与原理 手机号段数据库存储着海量手机号段及其关联信息,包括号段起始与结束号码、运营商归属、地区编码、卡类型等核心数据。这些数据主要来源于通信管理机构的官方分配信息、运营商的业务更新数据以及合法采集的使用数据。经过数据清洗、校验…...

Kafka 入门指南与一键部署
Kafka 介绍 想象一下你正在运营一个大型电商平台,每秒都有成千上万的用户浏览商品、下单、支付,同时后台系统还在记录用户行为、更新库存、处理物流信息。这些海量、持续产生的数据就像奔腾不息的河流,你需要一个强大、可靠且实时的系统来接…...
MATLAB实战:视觉伺服控制实现方案
以下是一个基于MATLAB的视觉伺服控制项目实现方案,结合实时图像处理、目标跟踪和控制系统设计。我们将使用模拟环境进行演示,但代码结构可直接应用于真实硬件。 系统架构 图像采集 → 目标检测 → 误差计算 → PID控制器 → 执行器控制 完整代码实现 …...

Oracle正则表达式学习
目录 一、正则表达简介 二、REGEXP_LIKE(x,匹配项) 三、REGEXP_INSTR 四、REGEXP_SUBSTR 五、REGEXP_REPLACE 一、正则表达简介 相关网址: https://cloud.tencent.com/developer/article/1456428 https://www.cnblogs.com/lxl57610/p/8227599.html https://…...
校招 java 面试基础题目及解析
我将结合常见的校招Java面试基础题目,从概念阐述、代码示例等角度展开,为你提供一份可用于学习的技术方案及应用实例。 校招Java面试基础题目解析与学习指南 在Java校招面试中,扎实掌握基础知识是成功的关键。本文将围绕常见的Java基础面试…...
# STM32F103 SD卡读写程序
下面是一个基于STM32F103系列微控制器的SD卡读写完整程序,使用标准外设库(StdPeriph)和FatFs文件系统。 硬件准备 STM32F103C8T6开发板(或其他F103系列)SD卡模块(SPI接口)连接线缆 硬件连接 SD卡模块 STM32F103 CS -> PA4 (SPI1_NSS) SCK -> PA5 (SPI…...
Spring中循环依赖问题的解决机制总结
一、解决机制 1. 什么是循环依赖 循环依赖是指两个或多个Bean之间相互依赖对方,形成一个闭环的依赖关系。最常见的情况是当Bean A依赖Bean B,而Bean B又依赖Bean A时,就形成了循环依赖。在Spring容器初始化过程中,如果不加以特殊…...
青少年编程与数学 01-011 系统软件简介 04 Linux操作系统
青少年编程与数学 01-011 系统软件简介 04 Linux操作系统 一、Linux 的发展历程(一)起源(二)早期发展(三)成熟与普及(四)移动与嵌入式领域的拓展 二、Linux 的内核与架构(…...

微软PowerBI考试 PL300-使用适用于 Power BI 的 Copilot 创建交互式报表
微软PowerBI考试 PL300-使用适用于 Power BI 的 Copilot 创建交互式报表 Microsoft Power BI 可帮助您通过交互式报表准备数据并对数据进行可视化。 如果您是 Power BI 的新用户,可能很难知道从哪里开始,并且创建报表可能很耗时。 通过适用于 Power BI …...