聊聊Svelte.js技术它做了什么以及如何实现的(下)
一. 循环语句
循环渲染这个我们肯定是离不开了, Svelte.js
给我们提供了有趣的书写方式。
<script>
const arr = ['x1', 'x2', 'x3'];
</script>
<ul>
{#each arr as item}
<li>{item}</li>
{/each}
</ul>
虽然Svelte.js
没有虚拟dom, 但是循环的时候也需要key
, 它的写法有点特殊。
<script>
const arr = ['x1', 'x2', 'x3'];
</script>
<ul>
{#each arr as item, i (i)}
<li>{i} --- {item}</li>
{/each}
</ul>
上面的(i)
就是key
值, i
是序号必须使用,
与item
隔开。
二. 循环语句编译成什么
初始化就一个变量这个没啥说的。
图二的ctx
是个素组, ctx[0]
是我们定义的arr
, ctx[1]
是当前li对应的值。
三. 循环语句如何处理key
下面是设置了key的循环体编译后的代码。
我们重点看下更新dom数据的时候key是怎么工作的。
其实在Svelte.js
里面也需要diff算法来更新dom, 这个点需要明确。
四. 父子组件
父组件
/src/App.svelte
<script>
import Son from './子组件.svelte';
const options = {
msg: 'xxxxxx',
};
// 如此传递属性
const footer = {
footer1: 'footer1',
footer2: 'footer2',
};
// 处理订阅事件
function handleEmit(res) {
console.log('相应事件1', res);
console.log('相应事件2', res.detail);
}
</script>
<div>
<p>父组件</p>
<Son {options} {...footer} on:emit={handleEmit} />
</div>
子组件
/src/子组件.svelte
<script>
import { createEventDispatcher } from 'svelte';
export let options;
export let footer1;
export let footer2;
// 触发事件发布
const dispatch = createEventDispatcher();
function emit() {
dispatch('emit', {
msg: '你好',
});
}
</script>
<div>
<p>子组件: {options.msg}</p>
<p>footer1: {footer1}</p>
<p>footer2: {footer2}</p>
<button on:click={emit}>点击触发事件</button>
</div>
- 子组件直接
import
引入子组件即可使用。 - 父组件行间传递参数, 子组件
export
接收。 - 父组件可使用扩展运算符传递参数。
- 父组件
on:emit={handleEmit}
的形式监听子组件发布的事件。 - 子组件引入
createEventDispatcher
实现事件的发布。 - 子组件可以随便改变传递过来的值, 不会影响该值在父组件内的值(
重点
)。 - 父组件传递给子组件的值的改变, 会重新渲染子组件。
五. 父子组件打包分析
六. 样式
在Svelte.js
里面样式是默认沙盒的, 也就是说我们在一个.svelte
文件里面写的样式不会渗透到全局。
很方便很爽的一点是我们不用模板字符串的方式拼接, 这样写样式真爽。
<script>
const color = 'red';
const isBlack = true;
</script>
<div>
<p>文字1</p>
<p style="color:{color}" class:black={isBlack}>文字2</p>
</div>
<style>
p {
color: blueviolet;
font-size: 29px;
font-weight: 800;
}
.black {
border: 1px solid black;
}
</style>
class:black={isBlack}
的意思就是只有isBlack
为true
才会赋予black
这个className。
如果要设置全局样式可以在html
或者main.js
文件里面引入。
七. 生命周期
生命周期的概念现在基本所有库都有, svelte
在这一点做的也很不错。
<script>
import { onMount, onDestroy, beforeUpdate, afterUpdate } from 'svelte';
onMount(() => {
console.log('mounted');
});
onDestroy(() => {
console.log('onDestroy');
});
beforeUpdate(() => {
console.log('beforeUpdate');
});
afterUpdate(() => {
console.log('afterUpdate');
});
</script>
onMount
将在组件首次呈现到DOM之后运行。onDestroy
当销毁组件时调用。beforeUpdate
在DOM更新前运行。afterUpdate
在DOM更新后运行。
注意生命周期可以多次调用如下:
onMount(() => {
console.log('mounted1');
});
onMount(() => {
console.log('mounted2');
});
八. 异步请求
异步请求与组件结构的融合设置, 比如我们平时些项目要为不同dom块写loading效果, 这样就要有n个loading变量, 而下面的方法会使我们少定义一些变量。
<script>
function ajax() {
return new Promise((res) => {
setTimeout(() => {
res('请求成功');
}, 1000);
});
}
</script>
<div>
{#await ajax()}
<p>...loading</p>
{:then res}
<p>
res: {res}
</p>
{/await}
</div>
效果为请求中显示loading, 请求完显示内容, 后面还可以加一个{:catch err}
标签, 但是在这里处理错误其实不太好。
打包代码的样子
在handle_promise
方法里面, 如果判断我们传入的是promise则替我们执行promise并把结果赋予上去, 他还有其他复杂操作我们不用深究。
看起来挺实用的写法其实也不太实用。
九. 计算属性
你可以在 JavaScript 中用标识符标记一个语句,如下所示:$: foo = bar。它会在 foo = bar 语句中添加一个名为 $ 的标识符(如果之前未定义 foo,则严格模式下会出错)。
所以在这种情况下,当 Svelte 看到任何带有 $: 前缀的语句时,它就知道左边的变量要从右边的变量中获取值。我们现在有了一种方法可以将一个变量的值绑定到另一个变量。
在js里面直接编写是不会报错的, 长知识了原来我们可以利用这点, 开发自己的编译器来创造新的语法规则:
计算属性的用法:
<script>
let n = 1;
$: nn = n * 2;
function addn() {
n++;
}
</script>
<div>
<button on:click={addn}>点了{nn}次</button>
</div>
上述的nn
就永远等于n*2
。
打包后如何实现的
十. 观察者
<script>
let n = 0;
// 1: 大括号内的全部执行
$: {
const titel = 'n的值为: ';
console.log(titel + n);
}
// 2: 加判断条件
$: if (n > 5) {
alert('n 大于 5');
}
function addn() {
n++;
}
</script>
<div>
<button on:click={addn}>点了{n}次</button>
</div>
- 最开始大括号的内容默认会执行一次。
- 大括号内的值如果发生变化就导致大括号里的代码整体执行一次。
$:
可以标示条件语句。
打包后的代码
不得不佩服svelte
把标识符玩的很出彩。
十一. 动画
第一种: 自带动画(淡入淡出)
<script>
import { fade } from 'svelte/transition';
let visible = true;
function change() {
visible = !visible;
}
</script>
<button on:click={change}>点击动画</button>
{#if visible}
<div transition:fade>第一种</div>
{/if}
- 第一要引入动画
fade
。 - 标签定义
transition:fade
。
第二种自定义动画
<script>
import { elasticOut } from 'svelte/easing';
let visible = true;
function change() {
visible = !visible;
}
// 自定义
function whoosh(node, params) {
const existingTransform = getComputedStyle(node).transform.replace(
'none',
''
);
return {
delay: params.delay || 0,
duration: params.duration || 400,
easing: params.easing || elasticOut,
css: (t, u) => `transform: ${existingTransform} scale(${t})`,
};
}
</script>
<button on:click={change}>点击动画</button>
{#if visible}
<div in:whoosh>自定义</div>
{/if}
in:whoosh
指定了动画使用whoosh函数。whoosh
返回的是动画的延迟时间、执行时间、以及css效果等等。
这个就不讨论打包文件了。
十二. 输入框双向绑定
<script>
let value = '';
</script>
<div>
<input type="text" bind:value />
<p>value: {value}</p>
</div>
打包文件
做法也比较强硬, 就是
十三. vue
里面使用svelte
组件
svelte
的一大优势就是跨平台, 它可以使用在任何框架内, 因为他就是原生js代码, 这里看下我们如何在vue
项目中使用它。
<script>
let n = 0;
function addn() {
n++;
console.log('触发了:addn');
}
</script>
<div>
<button on:click={addn}>点了{n}次</button>
</div>
/src/main.js
import App from './App.svelte';
export default App;
这里是把我们简单写个点击事件
的代码打包, yarn build
之后把bundle.js
复制到名为xxx
的文件夹中的index.js
文件中, 放到目标工程的node_modules
, 这是为了模拟真实的使用场景。
index.js
文件我们要处理一下, 方便导出。
改装前
改装后
具体的使用方式
<script>
import xxx from "xxx";
export default {
name: "App",
mounted() {
new xxx({
target: document.body, // 随便传入你想插入的元素与初始值
});
},
};
</script>
十四. 技术选型
平时开发怎么可能两个技术混合使用, 比如我用vue
开发已经引入了vue的runtime就没必要在使用这门技术了, 但是如果你是用svelte
开发了一些跨平台的兼容性特别好的组件还是可以考虑使用svelte
来做的, 这样不管是react
还是vue
都可以方便使用你的组件。
要注意在工程体量较小的时候svelte
确实有优势的, 但是逻辑复杂之后就不太好用了, 我们看了它的打包文件的写法就能得知它在打包后必然会出现大量的逻辑代码, 所以逻辑多到一定程度后其实性能不一定比runtime的形式好了。
end.
这次就是这样, 希望和你一起进步。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。