1. 生命周期
每个组件都有一个生命周期,于创建时开始,于销毁时结束。以下是一些有帮助的函数,允许你在生命周期的关键时刻运行代码。
2. onMount 挂载
最常用的是onMount,它在组件第一次呈现给DOM之后运行。
用法:
<script>
import { onMount } from "svelte";
let finalVal = '初始值'
onMount(() => {
finalVal = '变更值' // 可以在此处发起网络请求,进行值的变更
})
</script>
- 由于服务端渲染,建议将获取数据的方式放置在onMount中,而不是放在script标签的最上面。除了onDestory之外,生命周期函数在SSR期间不会运行,这意味着我们可以避免抓取那些应该在组件被挂载DOM时才加载的数据。
- 生命周期函数必须在组件初始化时调用,以便回调函数绑定到组件实例,而不是在setTimeout中。
- 如果onMount回调函数返回一个函数,该函数将在组件被销毁时被调用。
3. onDestroy 销毁
要在组件被销毁时运行代码,请使用onDestroy。
- 例如,我们可以在组件初始化时添加 一个setInterval函数,并在它不再使用时清除它,这样做可以防止内存泄漏。
import { onDestory } from 'svelte'
let seconds = 0
const myInterval = setInterval(() => {
seconds ++
}, 1000);
onDestory(() => {
cleatInterval(myInterval)
})
- 虽然组件在初始化期间调用生命周期函数很重要,但从哪里调用它们并不重要。因此,为了方便管理,我们可以将区间逻辑抽象到某个文件中。这样,相同需要此逻辑的内容,可以引入对应的方法,在使用后进行清除
utils.js
import { onDestroy } from 'svelte';
export function onInterval(callback, milliseconds) {
const interval = setInterval(callback, milliseconds);
onDestroy(() => {
clearInterval(interval);
});
}
使用组件中:
import { onInterval } from './utils.js';
let seconds = 0;
onInterval(() => seconds += 1, 1000);
4. beforeUpdate 和 afterUpdate
- beforeUpdate 函数实现在DOM渲染完成前执行
- afterUpdate 函数则相反,在异步数据加载完成后
以下是一个聊天机器人的小例子。生命周期主要用于在双方一问一答拼接内容时,计算聊天框的高度和聊天内容的高度,确保聊天内容展示在最新回复上。
<script>
import Eliza from "elizabot";
import { beforeUpdate, afterUpdate } from 'svelte'
const eliza = new Eliza(); // 创建机器人聊天实例
let comments = [ // 聊天框内容,初始聊天框由eliza发起
{ author: 'eliza', text: eliza.getInitial() }
]
function handleKeydown(event) {
// 如果是回车键
if (event.which === 13) {
const text = event.target.value
// 如果没有内容直接回车,不执行任何操作
if(!text) return;
// 输入内容拼接至聊天数组中,通过author进行区分,样式展示
comments = comments.concat({
author: 'user',
text
})
// 消息发送成功后将内容清空
event.target.value = '';
// 机器人回复消息
const reply = eliza.transform(text)
console.log(reply)
// 过渡,用于未回复时表示正在输入的状态
setTimeout(() => {
comments = comments.concat({
author: 'eliza',
text: '...',
placeholder: true
})
// 返回机器人对话
setTimeout(() => {
// 拼接返回内容,并移除最近的...
comments = comments.filter(comment => !comment.placeholder).concat({
author: 'eliza',
text: reply
})
}, 500 + Math.random() * 500); // 设定回复机器人的随机响应时间
}, 200 + Math.random() * 200)
}
}
let div;
let autoscroll;
// 进行更新之前,将滚动高度设为展示当前内容
beforeUpdate(() => {
autoscroll = div && (div.offsetHeight + div.scrollTop) > (div.scrollHeight - 20);
})
// 数据更新后,滚动条根据y轴滑到最下面,展示最新回复
afterUpdate(() => {
if (autoscroll) div.scrollTo(0, div.scrollHeight);
});
</script>
<main>
<div class="chat">
<!-- 聊天对话标题 -->
<h1>聊天机器人</h1>
<!-- 滑动聊天区域 -->
<div class="scrollable" bind:this={div}>
{#each comments as comment}
<article class={comment.author}>
<span>{comment.text}</span>
</article>
{/each}
</div>
<!-- 聊天内容输入区 -->
<input on:keydown={handleKeydown}>
</div>
</main>
<style>
.chat {
display: flex;
flex-direction: column;
height: 400px;
max-width: 320px;
}
.scrollable {
flex: 1 1 auto;
border-top: 1px solid #eee;
margin: 0 0 0.5em 0;
overflow-y: auto;
}
/* 每条聊天记录之间的间隔 */
article {
margin: 0.5em 0;
}
/* 本人发的消息靠右侧展示 */
.user {
text-align: right;
}
/* 两个人对话框内容样式 */
span {
padding: 0.5em 1em;
display: inline-block;
}
/* 机器人聊天内容背景 */
.eliza span {
background-color: #eee;
border-radius: 1em 1em 1em 0;
}
/* 用户聊天内容背景 */
.user span {
background-color: #0074D9;
color: white;
border-radius: 1em 1em 0 1em;
word-break: break-all;
}
</style>
beforeUpdate函数需要在组件挂载前运行,所以我们需要先将div与组件标签绑定,并判断div是否已被正常渲染
5. tick
-
tick函数不同于其他生命周期函数,因为你可以随时调用它,而不用等待组件首次初始化。它返回一个带有resolve方法的Promise,每当组件pending状态变化便会立刻体现到DOM中。
-
在Svelte中每当组件状态失效时,DOM不会立即更新。反而Svelte会等待下一个microtask以查看是否还有其他变化的状态或组件需要应用更新。这样做避免了浏览器做无用功,使之更加高效。
以下示例场景:在选中内容后,将选中内容大写,且不取消选中,光标不自动移动至最后。
<script>
import { tick } from "svelte";
let text = 'Select some 1 text and then 2 hit 汉字 the tab key to toggle uppercase'
async function handleKeyDown(event) {
if (event.which !== 9) return
event.preventDefault();
// this指向的是当前textarea的DOM,其中解析出来的这三个变量分别代表选中内容的起始下标、结束下标、textarea全部内容值
const { selectionStart, selectionEnd, value } = this;
// 根据下标和全部内容截取出选中内容
const selection = value.slice(selectionStart, selectionEnd)
// 使用正则表达式进行匹配, 如果包含小写那么转成大写,不然执行转换成小写
const replacement = /[a-z]/.test(selection) ? selection.toUpperCase() : selection.toLowerCase();
// 执行成功后,replacement已经是代码想要达成的值了,此时需要将该值更新到textarea的value上
text = (value.slice(0, selectionStart) + replacement + (value.slice(selectionEnd)))
// 增加tick,确保DOM更新后光标选中内容
await tick()
this.selectionStart = selectionStart;
this.selectionEnd = selectionEnd;
}
</script>
<main>
<textarea cols="30" rows="10" value={text} on:keydown={handleKeyDown}></textarea>
</main>
<style>
</style>
官方文档
如果有用,点个赞呗~
总结用法,希望可以帮助到你,
我是Ably,你无须超越谁,只要超越昨天的自己就好~