新玩具Svelte

搭建一个Svelte + ts + vite2.0的环境
创建一个项目https://link.juejin.cn/?target=https%3A%2F%2Fwww.sveltejs.cn%2Fblog%2Fsvelte-for-new-developers


npx degit sveltejs/template svelte-app
cd svelte-app
 上面如果创建失败,可以将sveltejs的模板通过git下载到本地 
git clone https://github./comsveltejs/template.git
cd template
npm install

如果要使用typescript,执行下面

# 如果要使用typescript,执行下面
node scripts/setupTypeScript.js
添加数据
声明一个变量,在页面中使用花括号就可以将变量的内容显示到页面上(跟React很像)
<script lang="ts">
	let name: string = 'world'
        setTimeout(() => {
            name = 'shibin'
	}, 2000)
</script>
<main>
    <h1>Hello {name}!</h1>
</main>

在页面上显示的html

<h1>Hello world!</h1>
添加样式
在组件中添加一个<style>标签,<style>标签中的样式只在改组件内有效(类似vue中的<style>标签中加上scoped)
<script lang="ts">
	let name: string = 'world'
</script>

<main>
	<h1>Hello {name}!</h1>
	<p>
		Visit the <a href="https://svelte.dev/tutorial">Svelte tutorial</a> to learn
		how to build Svelte apps.
	</p>
</main>

<style>
	main {
		text-align: center;
		padding: 1em;
		max-width: 240px;
		margin: 0 auto;
	}

	h1 {
		color: #ff3e00;
		text-transform: uppercase;
		font-size: 4em;
		font-weight: 100;
	}

	@media (min-width: 640px) {
		main {
			max-width: none;
		}
	}
</style>
嵌套组件
<script> 中导入组件就可以使用了
<script lang="ts">
	import Hello from './Hello.svelte'
</script>

<main>
	<Hello/>
</main>
html标签
通常,字符串以纯文本形式插入,如果要插入html代码片段,可以使用{@html ...}实现
<p>{@html string}</p>
创建一个应用 
Svelte提供了Rollup和Webpack的插件,可以根据需要进行配置
import App from './App.svelte'

const app = new App({
	target: document.body,
	props: {
		name: 'world'
	}
})

export default app
赋值
Reactivity
Svelte能够让 DOM 与你的应用程序状态保持同步,你只需要改变数据,Svelte 会自动更新 DOM

<script lang="ts">
	import Hello from './Hello.svelte'
	let count: number = 0
	const handleClick = () => {
		count += 1
	}
</script>
<button on:click={handleClick}>Clicked {count} times</button>
声明
当组件的某个部分需要其他部分计算得出时(例如Vue中的计算属性),Svelte提供了reactive declarations
<script lang="ts">
    let count = 0
    $: doubled = count * 2
</script>
 在页面中使用{count * 2}也能达到效果,但当需要引用多次时,使用这种声明会更好 
语句
语句也可以通过计算
下面代码,当count值发生改变时,就会执行语句
<script lang="ts">
    let count = 0
    $: console.log(`the count is ${count}`);
</script>
 将一组语句组合成一个代码块 
<script lang="ts">
    let count = 0
    $: {
	console.log(`the count is ${count}`)
	alert(`I SAID THE COUNT IS ${count}`)
    }
</script>
 甚至可以将 $: 放在 if 代码块前面。下面代码当count>=10时,就是执行if里的代码块 
<script lang="ts">
    let count = 0
    $: if (count >= 10) {
	  alert(`count is dangerously high!`)
	  count = 9
    }
</script>
更新数组和对象 
Svelte的数据更新是由赋值语句触发的,所以使用数组的pushslicepop等不会触发自动更新。
下面代码不会更新

<script lang="ts">
	let arr:number[] = [1,2,3]

	function addNumber(){
		arr.push(arr.length + 1)
	}
	
</script>

<main>
	{arr}
	<button on:click={addNumber}>addNumber</button>
</main>
解决的办法是重新赋值 
<script lang="ts">
	import Hello from './Hello.svelte'
	let arr:number[] = [1,2,3]

	function addNumber(){
		arr.push(arr.length + 1)
        arr = arr
	}
</script>
 还有一种更惯用的方法 
function addNumber() {
	arr = [...arr, arr.length + 1]
}
 
赋值给数组和对象的 属性(properties)与对值本身进行赋值的方式相同。
function addNumber() {
    arr[arr.length] = arr.length + 1
}
 一个简单的经验法则是:被更新的变量的名称必须出现在赋值语句的左侧 
const foo = obj.foo;
// 不会触发obj.foo.bar 的引用
foo.bar = 'baz';

// 会触发obj.foo.bar 的引用
// obj.foo.bar = 'baz';
 就不会更新对 obj.foo.bar 的引用,除非使用 obj = obj 方式。 
Props
props用于父组件向子组件中传递数据 
<Hello name="world"/>
 子组件可以通过export 关键字来获取父组件传递的数据 
<script lang="ts">
    export let name: string
</script>

<h1>Hello {name}!</h1>
 为props设置默认值 
export let name: string = 'Shibin'
如果你的组件含有有一个对象属性,可以利用...对象解构语法将数据传递给子组件 
<Hello {...obj}/>
逻辑
条件渲染
if
{#if user.loggedIn}
	<button on:click={toggle}>
		Log out
	</button>
{/if}

{#if !user.loggedIn}
	<button on:click={toggle}>
		Log in
	</button>
{/if}
else
{#if user.loggedIn}
	<button on:click={toggle}>
		Log out
	</button>
{:else}
	<button on:click={toggle}>
		Log in
	</button>
{/if}
 else if 
{#if x > 10}
	<p>{x} is greater than 10</p>
{:else if 5 > x}
	<p>{x} is less than 5</p>
{:else}
	<p>{x} is between 5 and 10</p>
{/if}
列表渲染
<script lang="ts">
	let arr: number[] = [1, 2, 3]
</script>

<main>
	<ul>
		{#each arr as number}
			<li>{number}</li>
		{/each}
	</ul>
</main>
你也可以将index 作为第二个参数(key),类似于: 
{#each arr as number, index}
    <li>{index} {number}</li>
{/each}
一般来说,当你修改each 块中的值时,它将会在 尾端 进行添加或删除条目,并更新所有变化, 这可能不是你想要的效果。
为此,我们为 each 块指定一个唯一标识符,作为 key 值:
{#each things as thing (thing.id)}
	<Thing current={thing.color}/>
{/each}
 (thing.id) 告诉 Svelte 什么地方需要改变。 
Await
使用await在标签中处理异步数据
{#await promise}
	<p>...waiting</p>
{:then number}
	<p>The number is {number}</p>
{:catch error}
	<p style="color: red">{error.message}</p>
{/await}
 如果在请求完成之前不想程序执行任何操作,也可以忽略第一个块。 
{#await promise then value}
	<p>the value is {value}</p>
{/await}
事件 
在元素中使用on: directive可以为元素添加所任何事件
<div on:mousemove={handleMousemove}>
	The mouse position is {m.x} x {m.y}
</div>
 也可以声明内联事件处理函数 
<div on:mousemove="{e => m = { x: e.clientX, y: e.clientY }}">
	The mouse position is {m.x} x {m.y}
</div>
事件修饰符 
在Svelte中,DOM 事件处理程序具有额外的 修饰符
<button on:click|once={handleClick}>
	Click me
</button>
 
Svelte中事件修饰符有
preventDefault 调用event.preventDefault() ,在运行处理程序之前调用,组织浏览器的默认行为
stopPropagation 调用 event.stopPropagation(), 防止事件冒泡
passive 优化了对 touch/wheel 事件的滚动表现(Svelte 会在合适的地方自动添加滚动条)。
capture 在 capture 阶段而不是bubbling 阶段触发事件处理程序
once 运行一次事件处理程序后将其删除
self 仅当 event.target 是其本身时才执行
组合使用:on:click|once|capture={...}
组件事件 
组件可以向父组件发送自定义事件
<script lang="ts">
    import {createEventDispatcher} from 'svelte'
    export let name: string = 'wd';
    const dispatch = createEventDispatcher()
    function sayHello(){
        dispatch('message',{
            text: 'hello'
        })
    }
</script>

<h1>Hello {name}!</h1>
<button on:click={sayHello}>Say hello</button>
 父组件接收自定义事件 

注意, 组件事件不会冒泡,如果需要多层传递,则需要转发(自定义事件一层层传递)
事件转发也可以应用到DOM事件。
<script>
	function handleMessage(e) {
		alert(e.detail.text)
	}
</script>
<Hello on:message={handleMessage}/>
<button>
	Click me
</button>
<FancyButton on:click={handleClick}/>
绑定
表单绑定
<script lang="ts">
	let name: string = 'shibin'
</script>
<div>
	<input bind:value={name}>
	{name}
</div>
 number 
Svelte中可以绑定input到值,当type为number、range时,无需转换,默认就是number类型的
<script>
	let a = 1;
	let b = 2;
</script>

<label>
	<input type=number value={a} min=0 max=10>
	<input type=range value={a} min=0 max=10>
</label>

<label>
	<input type=number value={b} min=0 max=10>
	<input type=range value={b} min=0 max=10>
</label>

<p>{a} + {b} = {a + b}</p>
 单选和多选 
<script lang="ts">
  let yes: string = true
</script>
<input type=checkbox bind:checked={yes}>
{yes}
<label>
 多个复选框 
<script lang="ts">
let menu = [
	'Cookies and cream',
	'Mint choc chip',
	'Raspberry ripple'
];
let flavours = ['Mint choc chip'];

</script>
<h2>Flavours</h2>

{#each menu as flavour}
	<label>
		<input type=checkbox bind:group={flavours} value={flavour}>
		{flavour}
	</label>
{/each}
多个单选框 
<script lang="ts">
let scoops = 1
</script>

<label>
	<input type=radio bind:group={scoops} value={1}>
	One scoop
</label>

<label>
	<input type=radio bind:group={scoops} value={2}>
	Two scoops
</label>

<label>
	<input type=radio bind:group={scoops} value={3}>
	Three scoops
</label>
textarea 
同样适用bind:value进行绑定
<textarea bind:value={value}></textarea>
 值与变量名相同,我们也可以使用简写形式 
<textarea bind:value></textarea>
select
同样适用bind:value进行绑定
<select bind:value={selected} >
选择框含有一个名为 multiple 的属性,在这种情况下,它将会被设置为数组而不是单值(vue类似)。
Contenteditable绑定
支持 contenteditable="true"属性的标签,可以使用 textContent 与 innerHTML 属性的绑定:
<div
	contenteditable="true"
	bind:innerHTML={html}
></div>
this绑定
this可以绑定到任何标签 (或组件) 并允许你获取对渲染标签的引用
<script lang="ts">
	import { onMount } from "svelte"
	let canvas
	onMount(() => {
		const ctx = canvas.getContext('2d')
		// 

	})
</script>

<canvas bind:this={canvas} width={32} height={32} />
组件绑定
正如可以绑定到DOM元素的属性一样,你也可以将组件的属性绑定。例如,我们能绑定位于<Keypad>组件内的 value 属性,就如同一个表单标签一般:
<Keypad bind:value={pin} on:submit={handleSubmit}/>
生命周期
每个组件都有一个生命周期,从创建时开始,到销毁时结束
onMount
onMount 第一次渲染到 DOM 之后运行
<script lang="ts">
	import { onMount } from "svelte"
	let canvas
	onMount(() => {
		console.log(canvas)
	})
</script>
<canvas bind:this={canvas}></canvas>
onDestroy
onDestroy 组件销毁时执行
<script lang="ts">
	import { onMount, onDestroy } from "svelte";
	let canvas;
	let timer;
	onMount(() => {
		console.log(canvas);
		timer = setInterval(() => {
			console.log(new Date());
		}, 2000);
	});
	onDestroy(() => {
		clearInterval(timer);
	});
</script>

<canvas bind:this={canvas} />
beforeUpdate 和 afterUpdate
  • beforeUpdate 函数实现在DOM渲染完成前执行
  • afterUpdate 渲染完成后执行
trick
在Svelte中每当组件状态发生改变时,DOM不会立即更新,反而会会等待下一个 microtask 以查看是否还有其他变化的状态或组件需要应用更新。这样做避免了浏览器做无用功,使之更高效。
import { tick } from 'svelte';
...
// 等待dom更新
await tick();
// dom更新后的操作
this.selectionStart = selectionStart;
this.selectionEnd = selectionEnd;
...
Store
使用跟vue、react类似,
// store/index.ts
import { writable } from 'svelte/store'

export const count = writable(0)
// 订阅
const unsubscribe = count.subscribe(value => {
	count_value = value;
})

function increment() {
	// 触发更新
	count.update(n => n + 1)
}
// 重置
function reset() {
	count.set(0)
}
下面是一个例子
<script lang="ts">
	import {count} from './store'
	import Hello from './Hello.svelte'
	let count_value
	const unsubscribe = count.subscribe(value => {
		count_value = value
	})
</script>
<h1>The count is {count_value}</h1>
<Hello/>
<script lang="ts">
    import {count} from './store'
    export let name: string = 'wd';
    let count_value;

const unsubscribe = count.subscribe(value => {
	count_value = value;
});
function increment() {
	count.update(n => n + 1);
}
function reset() {
	count.set(0);
}
</script>

<h1>Hello {name}!</h1>
{count_value}
<button on:click={increment}>+</button>
<button on:click={reset}>reset</button>
自动订阅
调用subscribe后,当组件被销毁时,不会自动销毁调阅函数,需要手动调用unsubscribe去销毁
import { onDestroy } from 'svelte';
import { count } from './stores.js';
import Incrementer from './Incrementer.svelte';
import Decrementer from './Decrementer.svelte';
import Resetter from './Resetter.svelte';

let count_value;

const unsubscribe = count.subscribe(value => {
	count_value = value;
});

onDestroy(unsubscribe);
上面的,当组件有很多需要手动销毁时很麻烦,可以使用$ 来引用store中的值
<script>
	import { count } from './stores.js';
	import Incrementer from './Incrementer.svelte';
	import Decrementer from './Decrementer.svelte';
	import Resetter from './Resetter.svelte';

	console.log('count:' +$count)
</script>

<h1>The count is {$count}</h1>
自定义store
只要一个对象正确的使用 subscribe ,它就是可以称之为store
例如, 这个 count store 在前面的例子中包含 increment、 decrement 和 reset组件,以防止暴露 set 和update方法
function createCount() {
	const { subscribe, set, update } = writable(0);

	return {
		subscribe,
		increment: () => update(n => n + 1),
		decrement: () => update(n => n - 1),
		reset: () => set(0)
	};
}
绑定store
<script lang="ts">
	import {count} from './store'
</script>
<input type="number" bind:value={$count}>
<Hello/>